Skip to content

Commit

Permalink
Merge pull request #24 from CashScript/rework
Browse files Browse the repository at this point in the history
Rework layout with navigation tabs
  • Loading branch information
mr-zwets authored Jun 1, 2024
2 parents dbea916 + d09ea59 commit 90ceea5
Show file tree
Hide file tree
Showing 22 changed files with 389 additions and 271 deletions.
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
# CashScript Playground
The CashScript Playground is a way to write CashScript contracts and immediately interact with them in the browser. It is inspired by Ethereum's Remix, but slightly less feature-rich.
The CashScript Playground is an online IDE for writing CashScript smart contracts and immediately interact with them in the browser. The playground the easiest way to quickly get started with CashScript without any setup. The CashScript playground is inspired by Ethereum's Remix.

In the left panel you can write CashScript contracts and compile them. After compiling, a form is generated to create a new instance of this CashScript contract. It will then display some information about the contract and generate new forms to call contract functions to send BCH transactions. Press the arrow buttons to switch to a wallet view where you can generate wallets for testing purposes.
The CashScript Playground is available at [playground.cashscript.org](https://playground.cashscript.org/).

A live demo is available at [playground.cashscript.org](https://playground.cashscript.org/).
![Featured-screenhot](./screenshots/featured-screenshot.png)

In the left panel you can write CashScript contracts and compile them. After compiling, a contract Artifact is generated which can then be used to initialise an instance of the contract, with specific contract arguments.

## Limitations
Special transaction options such as OP_RETURN, hardcoded fees or relative timelocks are not supported by the playground.

The CashScript-Playground is connected to the Bitcoin Cash chipnet by default, you can get testnet coins from the [testnet faucet](https://tbch.googol.cash/). You can also connect the playground to mainnet but be sure **never to send large amounts** of money to contracts or wallets generated by the CashScript Playground!
The playground also does not support the combining multiple different smart contracts in one transaction.

## Example
![Example-0](./screenshots/nftUnlockedCovenant-0.png)
## Disclaimer

The CashScript-Playground is connected to the Bitcoin Cash chipnet by default, you can get testnet coins from the [testnet faucet](https://tbch.googol.cash/). You can also connect the playground to mainnet but be sure **never to send large amounts** of money to contracts or wallets generated by the CashScript Playground!

![Example-1](./screenshots/nftUnlockedCovenant-1.png)
## ScreenShots
![Screenshot-1](./screenshots/screenshot-1.png)

![Example-2](./screenshots/nftUnlockedCovenant-2.png)
![Screenshot-2](./screenshots/screenshot-2.png)

## Running locally
```
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
"@types/react": "18.0.30",
"@types/react-dom": "18.0.11",
"bootstrap": "^4.5.2",
"cashc": "^0.10.0-next.3",
"cashscript": "^0.10.0-next.3",
"cashc": "^0.10.0-next.5",
"cashscript": "^0.10.0-next.5",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"next": "13.2.4",
"next": "^14.2.3",
"react": "18.2.0",
"react-bootstrap": "^1.3.0",
"react-bootstrap": "^2.10.2",
"react-dom": "18.2.0",
"react-overlays": "^5.2.1",
"react-qrbtf": "^1.2.1",
"sass": "^1.60.0",
"typescript": "5.0.2",
Expand Down
1 change: 1 addition & 0 deletions public/downloadIcon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/trash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/featured-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed screenshots/nftUnlockedCovenant-0.png
Binary file not shown.
Binary file removed screenshots/nftUnlockedCovenant-1.png
Binary file not shown.
Binary file removed screenshots/nftUnlockedCovenant-2.png
Binary file not shown.
Binary file added screenshots/screenshot-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/screenshot-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 45 additions & 4 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,56 @@
// Components
import React, { useState } from 'react';
import { Artifact, Network, Contract, Utxo } from 'cashscript';
import Header from './Header'
import Main from './Main'
import Footer from './Footer';
import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';
import WalletInfo from './Wallets';
import { Wallet } from './shared';
import ContractInfo from './ContractInfo';
import ContractFunctions from './ContractFunctions'

function App() {
const [network, setNetwork] = useState<Network>('chipnet')
const [wallets, setWallets] = useState<Wallet[]>([])
const [artifact, setArtifact] = useState<Artifact | undefined>(undefined);
const [contract, setContract] = useState<Contract | undefined>(undefined)
const [utxos, setUtxos] = useState<Utxo[] | undefined>(undefined)
const [balance, setBalance] = useState<bigint | undefined>(undefined)

async function updateUtxosContract () {
if (!contract) return
setBalance(await contract.getBalance())
setUtxos(await contract.getUtxos())
}

return (
<div className="App" style={{ backgroundColor: '#eee', color: '#000' }}>
<Header />
<Main />
<>
<div className="App" style={{ backgroundColor: '#eee', color: '#000', padding: '0px 32px' }}>
<Header />
<Tabs
defaultActiveKey="editor"
id="uncontrolled-tab-example"
className="mb-2 mt-4 justify-content-center"
style={{ display: "inline-flex", marginLeft: "calc(100vw - 1000px)" }}
>
<Tab eventKey="editor" title="Editor">
<Main artifact={artifact} setArtifact={setArtifact} />
</Tab>
<Tab eventKey="contract" title="Contract">
<ContractInfo artifact={artifact} network={network} setNetwork={setNetwork} utxos={utxos} balance={balance} contract={contract} setContract={setContract} updateUtxosContract={updateUtxosContract} />
</Tab>
<Tab eventKey="wallets" title="Wallets">
<WalletInfo network={network} wallets={wallets} setWallets={setWallets}/>
</Tab>
<Tab eventKey="transactionBuilder" title="TransactionBuilder">
<ContractFunctions artifact={artifact} contract={contract} network={network} wallets={wallets} contractUtxos={utxos} updateUtxosContract={updateUtxosContract} />
</Tab>
</Tabs>
</div>
<Footer />
</div>
</>
);
}

Expand Down
58 changes: 58 additions & 0 deletions src/components/ArtifactsInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react'
import { Artifact } from 'cashscript'

interface Props {
artifact?: Artifact
}

const ContractInfo: React.FC<Props> = ({ artifact }) => {

const downloadArtifact = () => {
if(!artifact) return
const element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(artifact, null, 2)));
element.setAttribute('download', `${artifact.contractName}.json`);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}

return (
<div style={{
flex: 2,
margin: '16px',
border: '2px solid black',
borderTop: '1px solid black',
fontSize: '100%',
lineHeight: 'inherit',
overflow: 'auto',
background: '#fffffe',
padding: '8px 16px',
color: '#000'
}}>{ artifact?
(<div>
<h2>Artifact {artifact.contractName}</h2>
<strong>Last Updated</strong>
<p>{artifact.updatedAt}</p>
<strong>Artifact Bytecode</strong>
<details>
<summary>
show full Bytecode
</summary>
{artifact.bytecode}
</details>
<strong>Compiler Version</strong>
<p>{artifact.compiler.version}</p>
<strong>Download Artifact</strong>
<p onClick={downloadArtifact}>
download JSON file
<img src='./downloadIcon.svg' style={{marginLeft:"5px", verticalAlign: "text-bottom", cursor:"pointer"}}/>
</p>
</div>) :
<div>Compile a CashScript contract to get started!</div> }
</div>
)
}

export default ContractInfo
48 changes: 29 additions & 19 deletions src/components/ContractCreation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ interface Props {
setContract: (contract?: Contract) => void
network: Network
setNetwork: (network: Network) => void
setShowWallets: (showWallets: boolean) => void
utxos: Utxo[] | undefined
balance: bigint | undefined
updateUtxosContract: () => void
}

const ContractCreation: React.FC<Props> = ({ artifact, contract, setContract, network, setNetwork, setShowWallets, balance, utxos, updateUtxosContract}) => {
const ContractCreation: React.FC<Props> = ({ artifact, contract, setContract, network, setNetwork, balance, utxos, updateUtxosContract}) => {
const [args, setArgs] = useState<Argument[]>([])

useEffect(() => {
Expand Down Expand Up @@ -69,13 +68,14 @@ const ContractCreation: React.FC<Props> = ({ artifact, contract, setContract, ne
const createButton = <Button variant="secondary" size="sm" onClick={() => createContract()}>Create</Button>

const constructorForm = artifact &&
(<InputGroup size="sm">
{inputFields}
{networkSelector}
<InputGroup.Append>
(<>
<InputGroup size="sm">{inputFields}</InputGroup>
<p style={{margin: "4px 0"}}>And select target Network:</p>
<InputGroup style={{width:"350px"}}>
{networkSelector}
{createButton}
</InputGroup.Append>
</InputGroup>)
</InputGroup>
</>)

function createContract() {
if (!artifact) return
Expand All @@ -91,7 +91,7 @@ const ContractCreation: React.FC<Props> = ({ artifact, contract, setContract, ne

return (
<div style={{
height: '100%',
height: 'calc(100vh - 170px)',
border: '2px solid black',
borderBottom: '1px solid black',
fontSize: '100%',
Expand All @@ -101,25 +101,35 @@ const ContractCreation: React.FC<Props> = ({ artifact, contract, setContract, ne
padding: '8px 16px',
color: '#000'
}}>
<h2>{artifact?.contractName} <button onClick={() => setShowWallets(true)} style={{ float: 'right', border: 'none', backgroundColor: 'transparent', outline: 'none' }}></button></h2>
<h2>{artifact?.contractName}</h2>
<p>Initialise contract by providing contract arguments:</p>
{constructorForm}
{contract !== undefined && balance !== undefined &&
{contract !== undefined &&
<div style={{ margin: '5px', width: '100%' }}>
<div style={{ float: 'left', width: '70%' }}>
<strong>Contract address (p2sh32)</strong>
<CopyText>{contract.address}</CopyText>
<strong>Contract token address (p2sh32)</strong>
<CopyText>{contract.tokenAddress}</CopyText>
<strong>Contract utxos</strong>
<p>{utxos?.length} {utxos?.length == 1 ? "utxo" : "utxos"}</p>
<details onClick={() => updateUtxosContract()}>
<summary>Show utxos</summary>
<div>
<InfoUtxos utxos={utxos}/>
</div>
</details>
{utxos == undefined?
<p>loading ...</p>:
(<>
<p>{utxos.length} {utxos.length == 1 ? "utxo" : "utxos"}</p>
{utxos.length ?
<details>
<summary>Show utxos</summary>
<div>
<InfoUtxos utxos={utxos}/>
</div>
</details> : null}
</>)
}
<strong>Total contract balance</strong>
<p>{balance.toString()} satoshis</p>
{balance == undefined?
<p>loading ...</p>:
<p>{balance.toString()} satoshis</p>
}
<strong>Contract size</strong>
<p>{contract.bytesize} bytes (max 520), {contract.opcount} opcodes (max 201)</p>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ContractFunction.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'
import { Contract, AbiFunction, Argument, Network, Recipient, SignatureTemplate, ElectrumNetworkProvider, Utxo } from 'cashscript'
import { Contract, AbiFunction, Argument, Network, Recipient, SignatureTemplate, Utxo } from 'cashscript'
import { Form, InputGroup, Button, Card } from 'react-bootstrap'
import { readAsType, ExplorerString, Wallet, NamedUtxo } from './shared'

Expand Down Expand Up @@ -229,7 +229,7 @@ const ContractFunction: React.FC<Props> = ({ contract, abi, network, wallets, co
</InputGroup>
</div>
<Form style={{ marginTop: '5px', marginBottom: '5px', display: "inline-block" }}>
<Form.Check
<Form.Check
type="switch"
id={"outputHasFT" + abi?.name + "index" + index}
label="add tokens to output"
Expand Down
10 changes: 6 additions & 4 deletions src/components/ContractFunctions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ const ContractFunctions: React.FC<Props> = ({ artifact, contract, network, walle

return (
<div style={{
height: '100%',
height: 'calc(100vh - 170px)',
margin: '16px',
border: '2px solid black',
borderTop: '1px solid black',
fontSize: '100%',
Expand All @@ -29,11 +30,12 @@ const ContractFunctions: React.FC<Props> = ({ artifact, contract, network, walle
padding: '8px 16px',
color: '#000'
}}>
{contract &&
<div>
{contract ?
(<div>
<h2>Functions</h2>
{functions}
</div>
</div>) :
<div>No contract initialised yet...</div>
}
</div>
)
Expand Down
27 changes: 9 additions & 18 deletions src/components/ContractInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import React, { useState, useEffect } from 'react'
import { Artifact, Contract, Network, Utxo } from 'cashscript'
import { ColumnFlex, Wallet } from './shared'
import { ColumnFlex } from './shared'
import ContractCreation from './ContractCreation'
import ContractFunctions from './ContractFunctions'
import { ElectrumClient, ElectrumTransport } from 'electrum-cash'

interface Props {
artifact?: Artifact
network: Network
setNetwork: (network: Network) => void
style: any
setShowWallets:(showWallets: boolean) => void
wallets: Wallet[]
balance: bigint | undefined
utxos: Utxo[] | undefined
updateUtxosContract: () => void
contract: Contract | undefined
setContract: (contract: Contract | undefined) => void
}

const ContractInfo: React.FC<Props> = ({ artifact, network, setNetwork, setShowWallets, style, wallets }) => {
const [contract, setContract] = useState<Contract | undefined>(undefined)
const [balance, setBalance] = useState<bigint | undefined>(undefined)
const [utxos, setUtxos] = useState<Utxo[] | undefined>([])
const ContractInfo: React.FC<Props> = ({ artifact, network, setNetwork, contract, setContract, utxos, balance, updateUtxosContract }) => {
const [electrumClient, setElectrumClient] = useState<ElectrumClient | undefined>(undefined)

useEffect(() => setContract(undefined), [artifact])
Expand Down Expand Up @@ -47,19 +45,12 @@ const ContractInfo: React.FC<Props> = ({ artifact, network, setNetwork, setShowW
initElectrumSubscription()
}, [contract])

async function updateUtxosContract () {
if (!contract) return
setBalance(await contract.getBalance())
setUtxos(await contract.getUtxos())
}

return (
<ColumnFlex
id="preview"
style={{ ...style, flex: 1, margin: '16px' }}
style={{ flex: 1, margin: '16px' }}
>
<ContractCreation artifact={artifact} contract={contract} setContract={setContract} network={network} setNetwork={setNetwork} setShowWallets={setShowWallets} utxos={utxos} balance={balance} updateUtxosContract={updateUtxosContract}/>
<ContractFunctions artifact={artifact} contract={contract} network={network} wallets={wallets} contractUtxos={utxos} updateUtxosContract={updateUtxosContract} />
<ContractCreation artifact={artifact} contract={contract} setContract={setContract} network={network} setNetwork={setNetwork} utxos={utxos} balance={balance} updateUtxosContract={updateUtxosContract}/>
</ColumnFlex>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const Editor: React.FC<Props> = ({ code, setCode, compile, needRecompile }) => {
return (
<ColumnFlex
id="editor"
style={{ flex: 1, margin: '16px', border: '2px solid black', background: 'white' }}
style={{ flex: 3, margin: '16px', border: '2px solid black', background: 'white' }}
>
<ControlledEditor
language="sol"
Expand Down
7 changes: 3 additions & 4 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ interface Props {}
const Header: React.FC<Props> = () => {
return (
<header style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
display: 'inline-flex',
color: 'black',
padding: '24px 44px',
padding: '24px 28px',
width: "fit-content",
fontSize: '30px'
}}>
<div className="header-title">CashScript Playground</div>
Expand Down
Loading

0 comments on commit 90ceea5

Please sign in to comment.