diff --git a/src/components/App.tsx b/src/components/App.tsx index 81d2b45..cb3fbf6 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,13 +1,13 @@ // Components import React, { useState } from 'react'; -import { Artifact, Network, Contract, Utxo } from 'cashscript'; +import { Artifact, Network } 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 { Wallet, ContractInfo } from './shared'; import NewContract from './NewContract'; import Contracts from './Contracts'; import TransactionBuilder from './TransactionBuilder'; @@ -16,9 +16,7 @@ function App() { const [network, setNetwork] = useState('chipnet') const [wallets, setWallets] = useState([]) const [artifacts, setArtifacts] = useState(undefined); - const [contracts, setContracts] = useState(undefined) - const [utxos, setUtxos] = useState(undefined) - const [balance, setBalance] = useState(undefined) + const [contracts, setContracts] = useState(undefined) const [code, setCode] = useState( `pragma cashscript >= 0.8.0; @@ -35,11 +33,22 @@ contract TransferWithTimeout(pubkey sender, pubkey recipient, int timeout) { } } `); - const contract = contracts?.[0] // TODO: delete this - async function updateUtxosContract () { - if (!contract) return - setBalance(await contract.getBalance()) - setUtxos(await contract.getUtxos()) + + async function updateUtxosContract (nameContract: string) { + const contractIndex = contracts?.findIndex(contractInfo => contractInfo.contract.name == nameContract) + if (contractIndex == undefined) return + const currentContract = contracts?.[contractIndex].contract + if (!currentContract) return + // create two separate lists (deep copies) and mutate utxos + // map best way to deep clone + const utxosList = contracts.map(contract => contract.utxos ?? []) + const contractsList = contracts.map(contract => contract.contract) + const contractUtxos = await currentContract.getUtxos(); + utxosList[contractIndex] = contractUtxos + const newContracts: ContractInfo[] = contractsList.map((contract,index) => + ({contract,utxos:utxosList[index]}) + ) + setContracts(newContracts) } return ( @@ -56,16 +65,16 @@ contract TransferWithTimeout(pubkey sender, pubkey recipient, int timeout) {
- + - + - + diff --git a/src/components/ContractCreation.tsx b/src/components/ContractCreation.tsx index 5390c0f..bb33a1f 100644 --- a/src/components/ContractCreation.tsx +++ b/src/components/ContractCreation.tsx @@ -1,21 +1,20 @@ import React, { useState, useEffect } from 'react' -import { Artifact, Contract, Argument, Network, ElectrumNetworkProvider, Utxo } from 'cashscript' +import { Artifact, Contract, Argument, Network, ElectrumNetworkProvider } from 'cashscript' import { InputGroup, Form, Button } from 'react-bootstrap' -import { readAsType } from './shared' +import { readAsType, ContractInfo } from './shared' interface Props { artifact?: Artifact - contracts?: Contract[] - setContracts: (contract?: Contract[]) => void + contracts?: ContractInfo[] + setContracts: (contracts?: ContractInfo[]) => void network: Network - utxos: Utxo[] | undefined - balance: bigint | undefined - updateUtxosContract: () => void + updateUtxosContract: (contractName: string) => void } const ContractCreation: React.FC = ({ artifact, contracts, setContracts, network, updateUtxosContract}) => { const [args, setArgs] = useState([]) const [nameContract, setNameContract] = useState(""); + const [createdContract, setCreatedContract] = useState(false); const resetInputFields = () => { // This code is suuper ugly but I haven't found any other way to clear the value @@ -24,18 +23,12 @@ const ContractCreation: React.FC = ({ artifact, contracts, setContracts, const el = document.getElementById(`constructor-arg-${i}`) if (el) (el as any).value = '' }) - // Set empty strings as default values const newArgs = artifact?.constructorInputs.map(() => '') || [] - setArgs(newArgs) + setNameContract("") } - useEffect(() => { - //updateUtxosContract() - setArgs([]) - }, [artifact]) - const inputFields = artifact?.constructorInputs.map((input, i) => ( = ({ artifact, contracts, setContracts, const provider = new ElectrumNetworkProvider(network) const newContract = new Contract(artifact, args, { provider }) newContract.name = nameContract - setContracts([newContract, ...contracts ?? []]) + const contractInfo = {contract: newContract, utxos: undefined} + setContracts([contractInfo, ...contracts ?? []]) alert("created contract!") - resetInputFields() + setCreatedContract(true) } catch (e: any) { alert(e.message) console.error(e.message) } } + useEffect(() => { + if(!createdContract) return + updateUtxosContract(nameContract) + resetInputFields() + setCreatedContract(false) + }, [createdContract]); + return (
void + updateUtxosContract: (contractName: string) => void } -const ContractFunction: React.FC = ({ contract, abi, network, wallets, contractUtxos, updateUtxosContract }) => { +const ContractFunction: React.FC = ({ contractInfo, abi, network, wallets, updateUtxosContract }) => { const [args, setArgs] = useState([]) const [outputs, setOutputs] = useState([{ to: '', amount: 0n }]) // transaction inputs, not the same as abi.inputs @@ -23,6 +22,9 @@ const ContractFunction: React.FC = ({ contract, abi, network, wallets, co const [noAutomaticChange, setNoAutomaticChange] = useState(false) const [namedUtxoList, setNamedUtxoList] = useState([]) + const contract = contractInfo.contract + const contractUtxos = contractInfo.utxos + useEffect(() => { // Set empty strings as default values const newArgs = abi?.inputs.map(() => '') || []; @@ -32,7 +34,7 @@ const ContractFunction: React.FC = ({ contract, abi, network, wallets, co useEffect(() => { if (!manualSelection) return; async function updateUtxos() { - if (contract === undefined || contractUtxos === undefined) return + if (contractInfo.contract === undefined || contractUtxos === undefined) return const namedUtxosContract: NamedUtxo[] = contractUtxos.map((utxo, index) => ({ ...utxo, name: `Contract UTXO ${index}`, isP2pkh: false })) let newNamedUtxoList = namedUtxosContract const walletUtxos = wallets.map(wallet => wallet?.utxos ?? []) @@ -47,7 +49,7 @@ const ContractFunction: React.FC = ({ contract, abi, network, wallets, co setNamedUtxoList(newNamedUtxoList); } updateUtxos() - }, [manualSelection, contractUtxos]) + }, [manualSelection, contractInfo]) function fillPrivKey(i: number, walletIndex: string) { const argsCopy = [...args]; @@ -291,7 +293,7 @@ const ContractFunction: React.FC = ({ contract, abi, network, wallets, co alert(`Transaction successfully sent: ${ExplorerString[network]}/tx/${txid}`) console.log(`Transaction successfully sent: ${ExplorerString[network]}/tx/${txid}`) - updateUtxosContract() + updateUtxosContract(contract.name) } catch (e: any) { alert(e.message) console.error(e.message) diff --git a/src/components/ContractFunctions.tsx b/src/components/ContractFunctions.tsx index 32526ab..ae92fae 100644 --- a/src/components/ContractFunctions.tsx +++ b/src/components/ContractFunctions.tsx @@ -1,19 +1,18 @@ import React from 'react' -import { Contract, Network, Utxo } from 'cashscript' +import { Network } from 'cashscript' import ContractFunction from './ContractFunction' -import { Wallet } from './shared' +import { Wallet, ContractInfo } from './shared' interface Props { - contract: Contract + contractInfo: ContractInfo network: Network wallets: Wallet[] - contractUtxos: Utxo[] | undefined - updateUtxosContract: () => void + updateUtxosContract: (contractName: string) => void } -const ContractFunctions: React.FC = ({ contract, network, wallets, contractUtxos, updateUtxosContract }) => { - const functions = contract.artifact?.abi.map(func => ( - +const ContractFunctions: React.FC = ({ contractInfo, network, wallets, updateUtxosContract }) => { + const functions = contractInfo.contract.artifact?.abi.map(func => ( + )) return ( diff --git a/src/components/ContractInfo.tsx b/src/components/ContractInfo.tsx deleted file mode 100644 index 7bd9faa..0000000 --- a/src/components/ContractInfo.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { Artifact, Contract, Network, Utxo } from 'cashscript' -import { ColumnFlex } from './shared' -import ContractCreation from './ContractCreation' -import { ElectrumClient, ElectrumTransport } from 'electrum-cash' - -interface Props { - artifact?: Artifact - network: Network - balance: bigint | undefined - utxos: Utxo[] | undefined - updateUtxosContract: () => void - contracts: Contract[] | undefined - setContracts: (contract: Contract[] | undefined) => void -} - -const ContractInfo: React.FC = ({ artifact, network, contracts, setContracts, utxos, balance, updateUtxosContract }) => { - const [electrumClient, setElectrumClient] = useState(undefined) - - /* - const contract = contracts?.[0] // TODO: delete this - - async function initElectrumSubscription(){ - if(electrumClient) electrumClient?.disconnect() - if(!contract?.address) return - - // connect to ElectrumClient for subscription - let electrumServerName: string= "" - if(network == "mainnet") electrumServerName = 'bch.imaginary.cash' - if(network == "chipnet")electrumServerName = 'chipnet.imaginary.cash' - if(network == "testnet4") electrumServerName = 'testnet4.imaginary.cash' - if(!electrumServerName) return // no imaginary server for testnet3 - - const newElectrumClient = new ElectrumClient('Electrum client example', '1.4.1', electrumServerName, ElectrumTransport.WSS.Port, ElectrumTransport.WSS.Scheme) - await newElectrumClient?.connect(); - // subscribe to contract address - const refetchContractBalance = async(data:any) => { - if(data) updateUtxosContract() - } - await newElectrumClient?.subscribe(refetchContractBalance, 'blockchain.address.subscribe', contract.address); - setElectrumClient(newElectrumClient) - } - - useEffect(() => { - initElectrumSubscription() - }, [contracts]) - */ - - return ( - - - - ) -} - -export default ContractInfo diff --git a/src/components/Contracts.tsx b/src/components/Contracts.tsx index fbd8b9f..595ec87 100644 --- a/src/components/Contracts.tsx +++ b/src/components/Contracts.tsx @@ -1,21 +1,21 @@ import React from 'react' -import { Contract, Utxo } from 'cashscript' import CopyText from './shared/CopyText' -import { Card } from 'react-bootstrap' +import { Card, Button } from 'react-bootstrap' +import { ContractInfo } from './shared' +import InfoUtxos from './InfoUtxos' interface Props { - contracts: Contract[] | undefined - setContracts: (contract: Contract[] | undefined) => void - balance: bigint | undefined - utxos: Utxo[] | undefined - updateUtxosContract: () => void + contracts: ContractInfo[] | undefined + setContracts: (contract: ContractInfo[] | undefined) => void + updateUtxosContract: (contractName: string) => void } -const Contracts: React.FC = ({ contracts, setContracts, utxos, balance, updateUtxosContract }) => { +const Contracts: React.FC = ({ contracts, setContracts, updateUtxosContract }) => { - const removeContract = (contractToRemove: Contract) => { + const removeContract = (contractInfo: ContractInfo) => { + const contractToRemove = contractInfo.contract const contractToRemoveAddress = contractToRemove.address; - const newContracts = contracts?.filter(contract => contract.address !== contractToRemoveAddress) + const newContracts = contracts?.filter(contractInfo => contractInfo.contract.address !== contractToRemoveAddress) setContracts(newContracts) } @@ -36,40 +36,45 @@ const Contracts: React.FC = ({ contracts, setContracts, utxos, balance, u {contracts == undefined ?

No Contracts created yet...

:null} - {contracts?.map(contract => ( - + {contracts?.map((contractInfo) => ( + -
{contract.name}
- removeContract(contract)} style={{padding: "0px 6px", width: "fit-content", cursor:"pointer"}}/> +
{contractInfo.contract.name}
+ removeContract(contractInfo)} style={{padding: "0px 6px", width: "fit-content", cursor:"pointer"}}/>
Contract address (p2sh32) - {contract.address} + {contractInfo.contract.address} Contract token address (p2sh32) - {contract.tokenAddress} + {contractInfo.contract.tokenAddress} Contract artifact -

{contract.artifact.contractName}

+

{contractInfo.contract.artifact.contractName}

Contract utxos - {utxos == undefined? + {contractInfo?.utxos == undefined?

loading ...

: - (<> -

{utxos.length} {utxos.length == 1 ? "utxo" : "utxos"}

- {utxos.length ? -
- Show utxos -
-
-
: null} - ) + (
+ {contractInfo?.utxos.length} {contractInfo?.utxos.length == 1 ? "utxo" : "utxos"} + {}} style={{cursor:"pointer", marginLeft:"10px"}}> + + + {contractInfo.utxos.length ? +
+ Show utxos +
+ +
+
: null} +
) } Total contract balance - {balance == undefined? + {contractInfo.utxos == undefined?

loading ...

: -

{balance.toString()} satoshis

+

{contractInfo.utxos?.reduce((acc, utxo) => acc + utxo.satoshis, 0n).toString()} satoshis

+ } Contract size -

{contract.bytesize} bytes (max 520), {contract.opcount} opcodes (max 201)

+

{contractInfo.contract.bytesize} bytes (max 520), {contractInfo.contract.opcount} opcodes (max 201)

diff --git a/src/components/NewContract.tsx b/src/components/NewContract.tsx index fd1cb0f..83b77f3 100644 --- a/src/components/NewContract.tsx +++ b/src/components/NewContract.tsx @@ -1,20 +1,19 @@ import React, { useState } from 'react' -import { Artifact, Contract, Network, Utxo } from 'cashscript' +import { Artifact, Network } from 'cashscript' import { Form } from 'react-bootstrap' -import ContractInfo from './ContractInfo'; +import ContractCreation from './ContractCreation'; +import { ContractInfo } from './shared' interface Props { artifacts?: Artifact[] network: Network setNetwork: (network: Network) => void - balance: bigint | undefined - utxos: Utxo[] | undefined - updateUtxosContract: () => void - contracts: Contract[] | undefined - setContracts: (contract: Contract[] | undefined) => void + updateUtxosContract: (contractName: string) => void + contracts: ContractInfo[] | undefined + setContracts: (contract: ContractInfo[] | undefined) => void } -const NewContract: React.FC = ({ artifacts, network, setNetwork, contracts, setContracts, utxos, balance, updateUtxosContract }) => { +const NewContract: React.FC = ({ artifacts, network, setNetwork, contracts, setContracts, updateUtxosContract }) => { const [selectedArifact, setSelectedArtifact] = useState(undefined); @@ -76,7 +75,7 @@ const NewContract: React.FC = ({ artifacts, network, setNetwork, contract Select target Network: {networkSelector}
{selectedArifact !== undefined ? - + : null } diff --git a/src/components/TransactionBuilder.tsx b/src/components/TransactionBuilder.tsx index 504afe0..5de8c3f 100644 --- a/src/components/TransactionBuilder.tsx +++ b/src/components/TransactionBuilder.tsx @@ -1,36 +1,34 @@ import React, {useState} from 'react' -import { Artifact, Contract, Network, Utxo } from 'cashscript' +import { Network } from 'cashscript' import ContractFunctions from './ContractFunctions'; -import { Wallet } from './shared' +import { Wallet, ContractInfo } from './shared' import { Form } from 'react-bootstrap' interface Props { - artifacts?: Artifact[] network: Network wallets: Wallet[] - utxos: Utxo[] | undefined - updateUtxosContract: () => void - contracts: Contract[] | undefined + updateUtxosContract: (contractName: string) => void + contracts: ContractInfo[] | undefined } -const TransactionBuilder: React.FC = ({ network, wallets, contracts, utxos, updateUtxosContract }) => { +const TransactionBuilder: React.FC = ({ network, wallets, contracts, updateUtxosContract }) => { - const [selectedContract, setSelectedContract] = useState(undefined); + const [selectedContract, setSelectedContract] = useState(undefined); const contractSelector = ( { const contractAddress = event.target.value - const newSelectedContract = contracts?.find(contract => contract?.address === contractAddress) + const newSelectedContract = contracts?.find(contractInfo => contractInfo.contract?.address === contractAddress) setSelectedContract(newSelectedContract) }} > - {contracts?.map(contract => ( - ))} @@ -57,7 +55,7 @@ const TransactionBuilder: React.FC = ({ network, wallets, contracts, utxo } {selectedContract !== undefined ? - + : null } diff --git a/src/components/Wallets.tsx b/src/components/Wallets.tsx index 4197d00..87be010 100644 --- a/src/components/Wallets.tsx +++ b/src/components/Wallets.tsx @@ -150,9 +150,12 @@ const WalletInfo: React.FC = ({network, wallets, setWallets}) => {
{wallet.utxos?.length} {wallet.utxos?.length == 1 ? "utxo" : "utxos"} updateUtxosWallet(wallet,index)} style={{cursor:"pointer", marginLeft:"10px"}}> - (refresh wallet utxos ⭯) +
+ Wallet Balance +
{wallet.utxos?.reduce((acc, utxo) => acc + utxo.satoshis, 0n).toString()} satoshis
+ Private Key
Show Private Key WIF: diff --git a/src/components/shared/index.tsx b/src/components/shared/index.tsx index 0eef66b..6b38f1c 100644 --- a/src/components/shared/index.tsx +++ b/src/components/shared/index.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled' -import { SignatureTemplate, Utxo } from 'cashscript'; +import { Contract, SignatureTemplate, Utxo } from 'cashscript'; import { decodeCashAddress, decodeCashAddressFormatWithoutPrefix } from '@bitauth/libauth'; export const ColumnFlex = styled.div` @@ -22,6 +22,11 @@ export interface Wallet { utxos: Utxo[] } +export interface ContractInfo { + contract: Contract + utxos: Utxo[] | undefined +} + export interface NamedUtxo extends Utxo { name: string; isP2pkh: boolean;