Skip to content

Instantly share code, notes, and snippets.

@TheCyberWatchers
Last active October 3, 2022 13:10
Show Gist options
  • Save TheCyberWatchers/72c28c53a9e4fce1edf185944324850c to your computer and use it in GitHub Desktop.
Save TheCyberWatchers/72c28c53a9e4fce1edf185944324850c to your computer and use it in GitHub Desktop.
---------------- index.css -----------------
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
input[type=number] {
font-size: calc(10px + 2vmin);
width: 4em;
}
button {
font-size: calc(10px + 2vmin);
}
textarea {
font-size: calc(10px + 2vmin);
}
.ContractInfo {
font-size: calc(10px + 1vmin);
text-align: left;
width: 90vw;
height: 7em;
padding: 2vw;
overflow-x: scroll;
white-space: pre;
}
---------------- index.js -----------------
import React from 'react';
import AppViews from './views/AppViews';
import DeployerViews from './views/DeployerViews';
import AttacherViews from './views/AttacherViews';
import {renderDOM, renderView} from './views/render';
import './index.css';
import * as backend from './build/index.main.mjs';
import { loadStdlib } from '@reach-sh/stdlib';
const reach = loadStdlib(process.env);
import { ALGO_MyAlgoConnect as MyAlgoConnect }
from '@reach-sh/stdlib';
reach.setWalletFallback(reach.walletFallback({
providerEnv: 'TestNet', MyAlgoConnect }));
const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2};
const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!'];
const {standardUnit} = reach;
const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {view: 'ConnectAccount', ...defaults};
}
async componentDidMount() {
const acc = await reach.getDefaultAccount();
const balAtomic = await reach.balanceOf(acc);
const bal = reach.formatCurrency(balAtomic, 4);
this.setState({acc, bal});
if (await reach.canFundFromFaucet()) {
this.setState({view: 'FundAccount'});
} else {
this.setState({view: 'DeployerOrAttacher'});
}
}
async fundAccount(fundAmount) {
await reach.fundFromFaucet(this.state.acc, reach.parseCurrency(fundAmount));
this.setState({view: 'DeployerOrAttacher'});
}
async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); }
selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); }
render() { return renderView(this, AppViews); }
}
class Player extends React.Component {
random() { return reach.hasRandom.random(); }
async getHand() { // Fun([], UInt)
const hand = await new Promise(resolveHandP => {
this.setState({view: 'GetHand', playable: true, resolveHandP});
});
this.setState({view: 'WaitingForResults', hand});
return handToInt[hand];
}
seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); }
informTimeout() { this.setState({view: 'Timeout'}); }
playHand(hand) { this.state.resolveHandP(hand); }
}
class Deployer extends Player {
constructor(props) {
super(props);
this.state = {view: 'SetWager'};
}
setWager(wager) { this.setState({view: 'Deploy', wager}); }
async deploy() {
const ctc = this.props.acc.contract(backend);
this.setState({view: 'Deploying', ctc});
this.wager = reach.parseCurrency(this.state.wager); // UInt
this.deadline = {ETH: 10, ALGO: 100, CFX: 1000}[reach.connector]; // UInt
backend.Alice(ctc, this);
const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
this.setState({view: 'WaitingForAttacher', ctcInfoStr});
}
render() { return renderView(this, DeployerViews); }
}
class Attacher extends Player {
constructor(props) {
super(props);
this.state = {view: 'Attach'};
}
attach(ctcInfoStr) {
const ctc = this.props.acc.contract(backend, JSON.parse(ctcInfoStr));
this.setState({view: 'Attaching'});
backend.Bob(ctc, this);
}
async acceptWager(wagerAtomic) { // Fun([UInt], Null)
const wager = reach.formatCurrency(wagerAtomic, 4);
return await new Promise(resolveAcceptedP => {
this.setState({view: 'AcceptTerms', wager, resolveAcceptedP});
});
}
termsAccepted() {
this.state.resolveAcceptedP();
this.setState({view: 'WaitingForTurn'});
}
render() { return renderView(this, AttacherViews); }
}
renderDOM(<App />);
---------------- index.rsh -----------------
'reach 0.1';
const [ isHand, ROCK, PAPER, SCISSORS ] = makeEnum(3);
const [ isOutcome, B_WINS, DRAW, A_WINS ] = makeEnum(3);
const winner = (handAlice, handBob) =>
((handAlice + (4 - handBob)) % 3);
assert(winner(ROCK, PAPER) == B_WINS);
assert(winner(PAPER, ROCK) == A_WINS);
assert(winner(ROCK, ROCK) == DRAW);
forall(UInt, handAlice =>
forall(UInt, handBob =>
assert(isOutcome(winner(handAlice, handBob)))));
forall(UInt, (hand) =>
assert(winner(hand, hand) == DRAW));
const Player = {
...hasRandom,
getHand: Fun([], UInt),
seeOutcome: Fun([UInt], Null),
informTimeout: Fun([], Null),
};
export const main = Reach.App(() => {
const Alice = Participant('Alice', {
...Player,
wager: UInt, // atomic units of currency
deadline: UInt, // time delta (blocks/rounds)
});
const Bob = Participant('Bob', {
...Player,
acceptWager: Fun([UInt], Null),
});
init();
const informTimeout = () => {
each([Alice, Bob], () => {
interact.informTimeout();
});
};
Alice.only(() => {
const wager = declassify(interact.wager);
const deadline = declassify(interact.deadline);
});
Alice.publish(wager, deadline)
.pay(wager);
commit();
Bob.only(() => {
interact.acceptWager(wager);
});
Bob.pay(wager)
.timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));
var outcome = DRAW;
invariant( balance() == 2 * wager && isOutcome(outcome) );
while ( outcome == DRAW ) {
commit();
Alice.only(() => {
const _handAlice = interact.getHand();
const [_commitAlice, _saltAlice] = makeCommitment(interact, _handAlice);
const commitAlice = declassify(_commitAlice);
});
Alice.publish(commitAlice)
.timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
commit();
unknowable(Bob, Alice(_handAlice, _saltAlice));
Bob.only(() => {
const handBob = declassify(interact.getHand());
});
Bob.publish(handBob)
.timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));
commit();
Alice.only(() => {
const saltAlice = declassify(_saltAlice);
const handAlice = declassify(_handAlice);
});
Alice.publish(saltAlice, handAlice)
.timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
checkCommitment(commitAlice, saltAlice, handAlice);
outcome = winner(handAlice, handBob);
continue;
}
assert(outcome == A_WINS || outcome == B_WINS);
transfer(2 * wager).to(outcome == A_WINS ? Alice : Bob);
commit();
each([Alice, Bob], () => {
interact.seeOutcome(outcome);
});
});
/*/*/*/*/*/*/*/*/* VIEWS *\*\*\*\*\*\*\*\*\*\
---------------- AppViews.js ----------------------
import React from 'react';
const exports = {};
exports.Wrapper = class extends React.Component {
render() {
const {content} = this.props;
return (
<div className="App">
<header className="App-header" id="root">
<h1>Rock, Paper, Scissors</h1>
{content}
</header>
</div>
);
}
}
exports.ConnectAccount = class extends React.Component {
render() {
return (
<div>
Please wait while we connect to your account.
If this takes more than a few seconds, there may be something wrong.
</div>
)
}
}
exports.FundAccount = class extends React.Component {
render() {
const {bal, standardUnit, defaultFundAmt, parent} = this.props;
const amt = (this.state || {}).amt || defaultFundAmt;
return (
<div>
<h2>Fund account</h2>
<br />
Balance: {bal} {standardUnit}
<hr />
Would you like to fund your account with additional {standardUnit}?
<br />
(This only works on certain devnets)
<br />
<input
type='number'
placeholder={defaultFundAmt}
onChange={(e) => this.setState({amt: e.currentTarget.value})}
/>
<button onClick={() => parent.fundAccount(amt)}>Fund Account</button>
<button onClick={() => parent.skipFundAccount()}>Skip</button>
</div>
);
}
}
exports.DeployerOrAttacher = class extends React.Component {
render() {
const {parent} = this.props;
return (
<div>
Please select a role:
<br />
<p>
<button
onClick={() => parent.selectDeployer()}
>Deployer</button>
<br /> Set the wager, deploy the contract.
</p>
<p>
<button
onClick={() => parent.selectAttacher()}
>Attacher</button>
<br /> Attach to the Deployer's contract.
</p>
</div>
);
}
}
export default exports;
---------------- AttacherViews.js -----------------
import React from 'react';
import PlayerViews from './PlayerViews';
const exports = {...PlayerViews};
exports.Wrapper = class extends React.Component {
render() {
const {content} = this.props;
return (
<div className="Attacher">
<h2>Attacher (Bob)</h2>
{content}
</div>
);
}
}
exports.Attach = class extends React.Component {
render() {
const {parent} = this.props;
const {ctcInfoStr} = this.state || {};
return (
<div>
Please paste the contract info to attach to:
<br />
<textarea spellCheck="false"
className='ContractInfo'
onChange={(e) => this.setState({ctcInfoStr: e.currentTarget.value})}
placeholder='{}'
/>
<br />
<button
disabled={!ctcInfoStr}
onClick={() => parent.attach(ctcInfoStr)}
>Attach</button>
</div>
);
}
}
exports.Attaching = class extends React.Component {
render() {
return (
<div>
Attaching, please wait...
</div>
);
}
}
exports.AcceptTerms = class extends React.Component {
render() {
const {wager, standardUnit, parent} = this.props;
const {disabled} = this.state || {};
return (
<div>
The terms of the game are:
<br /> Wager: {wager} {standardUnit}
<br />
<button
disabled={disabled}
onClick={() => {
this.setState({disabled: true});
parent.termsAccepted();
}}
>Accept terms and pay wager</button>
</div>
);
}
}
exports.WaitingForTurn = class extends React.Component {
render() {
return (
<div>
Waiting for the other player...
<br />Think about which move you want to play.
</div>
);
}
}
export default exports;
---------------- DeployerViews.js -----------------
import React from 'react';
import PlayerViews from './PlayerViews';
const exports = {...PlayerViews};
const sleep = (milliseconds) => new Promise(resolve => setTimeout(resolve, milliseconds));
exports.Wrapper = class extends React.Component {
render() {
const {content} = this.props;
return (
<div className="Deployer">
<h2>Deployer (Alice)</h2>
{content}
</div>
);
}
}
exports.SetWager = class extends React.Component {
render() {
const {parent, defaultWager, standardUnit} = this.props;
const wager = (this.state || {}).wager || defaultWager;
return (
<div>
<input
type='number'
placeholder={defaultWager}
onChange={(e) => this.setState({wager: e.currentTarget.value})}
/> {standardUnit}
<br />
<button
onClick={() => parent.setWager(wager)}
>Set wager</button>
</div>
);
}
}
exports.Deploy = class extends React.Component {
render() {
const {parent, wager, standardUnit} = this.props;
return (
<div>
Wager (pay to deploy): <strong>{wager}</strong> {standardUnit}
<br />
<button
onClick={() => parent.deploy()}
>Deploy</button>
</div>
);
}
}
exports.Deploying = class extends React.Component {
render() {
return (
<div>Deploying... please wait.</div>
);
}
}
exports.WaitingForAttacher = class extends React.Component {
async copyToClipboard(button) {
const {ctcInfoStr} = this.props;
navigator.clipboard.writeText(ctcInfoStr);
const origInnerHTML = button.innerHTML;
button.innerHTML = 'Copied!';
button.disabled = true;
await sleep(1000);
button.innerHTML = origInnerHTML;
button.disabled = false;
}
render() {
const {ctcInfoStr} = this.props;
return (
<div>
Waiting for Attacher to join...
<br /> Please give them this contract info:
<pre className='ContractInfo'>
{ctcInfoStr}
</pre>
<button
onClick={(e) => this.copyToClipboard(e.currentTarget)}
>Copy to clipboard</button>
</div>
)
}
}
export default exports;
---------------- PlayerViews.js -------------------
import React from 'react';
const exports = {};
// Player views must be extended.
// It does not have its own Wrapper view.
exports.GetHand = class extends React.Component {
render() {
const {parent, playable, hand} = this.props;
return (
<div>
{hand ? 'It was a draw! Pick again.' : ''}
<br />
{!playable ? 'Please wait...' : ''}
<br />
<button
disabled={!playable}
onClick={() => parent.playHand('ROCK')}
>Rock</button>
<button
disabled={!playable}
onClick={() => parent.playHand('PAPER')}
>Paper</button>
<button
disabled={!playable}
onClick={() => parent.playHand('SCISSORS')}
>Scissors</button>
</div>
);
}
}
exports.WaitingForResults = class extends React.Component {
render() {
return (
<div>
Waiting for results...
</div>
);
}
}
exports.Done = class extends React.Component {
render() {
const {outcome} = this.props;
return (
<div>
Thank you for playing. The outcome of this game was:
<br />{outcome || 'Unknown'}
</div>
);
}
}
exports.Timeout = class extends React.Component {
render() {
return (
<div>
There's been a timeout. (Someone took too long.)
</div>
);
}
}
export default exports;
---------------- render.js ------------------------
import ReactDOM from 'react-dom';
import React from 'react';
export function renderDOM(app) {
ReactDOM.render(
<React.StrictMode>{app}</React.StrictMode>,
document.getElementById('root')
);
}
export function renderView(parent, Views) {
parent.state = parent.state || {};
const {view, ContentView} = parent.state;
const View = view === 'Wrapper'
? ContentView
: Views[view];
const Wrapper = Views['Wrapper'];
const props = {...parent.props, ...parent.state, parent};
const content = <View {...props} />;
return <Wrapper {...{content}} />;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment