@@ -0,0 +1,23 @@ | |||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | |||
# dependencies | |||
/node_modules | |||
/.pnp | |||
.pnp.js | |||
# testing | |||
/coverage | |||
# production | |||
/build | |||
# misc | |||
.DS_Store | |||
.env.local | |||
.env.development.local | |||
.env.test.local | |||
.env.production.local | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* |
@@ -1,3 +1,34 @@ | |||
# freaPay | |||
~Webmodule~ to include a "Buy button" with streamlined crypto-pay transaction. | |||
~Webmodule~ to include a "Buy button" with streamlined crypto-pay transaction. | |||
## freaPay 0.1 | |||
![Built with Taquito][logo] | |||
A minimal React App with a Buy-Button. | |||
On triggering the Buy-Button a certain amount - given as React Component parameter - of any FA2 compatible contract based asset is transfered to a certain wallet, also given as React Component parameter. | |||
Based on the Taquito Boilerplate React Template | |||
**status: this project is in early development and is not recommended for anything really** | |||
### Getting Started | |||
1. Make sure you have https://nodejs.org/ installed on your computer | |||
2. Clone your new repository: | |||
`git clone <YOUR_REPOSITORY_URL>` | |||
3. Change your current working directory to the newly cloned repository directory. | |||
4. Install dependencies: | |||
`npm install` | |||
5. Start development server: | |||
`npm run start` | |||
6. Open https://localhost:3000 in your browser to see a sample application. | |||
[logo]: https://raw.githubusercontent.com/ecadlabs/taquito-boilerplate/master/assets/built-with-taquito.png "Built with Taquito" |
@@ -0,0 +1,47 @@ | |||
{ | |||
"name": "taquito-boilerplate-react", | |||
"version": "0.1.0", | |||
"private": true, | |||
"dependencies": { | |||
"@ledgerhq/hw-transport-u2f": "^5.22.0", | |||
"@taquito/beacon-wallet": "^9.2.0-stablelib.0", | |||
"@taquito/ledger-signer": "^9.2.0-stablelib.0", | |||
"@taquito/taquito": "^9.2.0-stablelib.0", | |||
"@testing-library/jest-dom": "^5.11.9", | |||
"@testing-library/react": "^11.2.5", | |||
"@testing-library/user-event": "^12.6.3", | |||
"@types/jest": "^26.0.20", | |||
"@types/ledgerhq__hw-transport-u2f": "^4.21.2", | |||
"@types/node": "^14.14.25", | |||
"@types/react": "^17.0.1", | |||
"@types/react-dom": "^17.0.0", | |||
"chokidar": "^3.5.2", | |||
"qrcode-generator": "^1.4.4", | |||
"react": "^17.0.1", | |||
"react-dom": "^17.0.1", | |||
"react-scripts": "4.0.2", | |||
"typescript": "^4.1.4" | |||
}, | |||
"scripts": { | |||
"start": "HTTPS=true react-scripts start", | |||
"build": "react-scripts build", | |||
"test": "react-scripts test", | |||
"eject": "react-scripts eject", | |||
"update-taquito": "ncu -u --target newest --filter '/^@taquito.*$/' && npm i" | |||
}, | |||
"eslintConfig": { | |||
"extends": "react-app" | |||
}, | |||
"browserslist": { | |||
"production": [ | |||
">0.2%", | |||
"not dead", | |||
"not op_mini all" | |||
], | |||
"development": [ | |||
"last 1 chrome version", | |||
"last 1 firefox version", | |||
"last 1 safari version" | |||
] | |||
} | |||
} |
@@ -0,0 +1,34 @@ | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="utf-8" /> | |||
<link rel="icon" href="%PUBLIC_URL%/favicon.png" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |||
<meta name="theme-color" content="#000000" /> | |||
<meta | |||
name="description" | |||
content="Web site created using create-react-app" | |||
/> | |||
<link | |||
rel="stylesheet" | |||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" | |||
integrity="sha512-1PKOgIY59xJ8Co8+NE6FZ+LOAZKjy+KY8iq0G4B3CyeY6wYHN3yt9PW0XpSriVlkMXe40PTKnXrLnZ9+fkDaog==" | |||
crossorigin="anonymous" | |||
/> | |||
<title>Taquito Boilerplate</title> | |||
</head> | |||
<body> | |||
<noscript>You need to enable JavaScript to run this app.</noscript> | |||
<div id="root"></div> | |||
<!-- | |||
This HTML file is a template. | |||
If you open it directly in the browser, you will see an empty page. | |||
You can add webfonts, meta tags, or analytics to this file. | |||
The build step will place the bundled scripts into the <body> tag. | |||
To begin the development, run `npm start` or `yarn start`. | |||
To create a production bundle, use `npm run build` or `yarn build`. | |||
--> | |||
</body> | |||
</html> |
@@ -0,0 +1,25 @@ | |||
{ | |||
"short_name": "React App", | |||
"name": "Create React App Sample", | |||
"icons": [ | |||
{ | |||
"src": "favicon.ico", | |||
"sizes": "64x64 32x32 24x24 16x16", | |||
"type": "image/x-icon" | |||
}, | |||
{ | |||
"src": "logo192.png", | |||
"type": "image/png", | |||
"sizes": "192x192" | |||
}, | |||
{ | |||
"src": "logo512.png", | |||
"type": "image/png", | |||
"sizes": "512x512" | |||
} | |||
], | |||
"start_url": ".", | |||
"display": "standalone", | |||
"theme_color": "#000000", | |||
"background_color": "#ffffff" | |||
} |
@@ -0,0 +1,3 @@ | |||
# https://www.robotstxt.org/robotstxt.html | |||
User-agent: * | |||
Disallow: |
@@ -0,0 +1,190 @@ | |||
html, | |||
body, | |||
#root { | |||
height: 100%; | |||
width: 100%; | |||
margin: 0px; | |||
padding: 0px; | |||
font-size: 1rem; | |||
} | |||
#root { | |||
display: grid; | |||
} | |||
.main-box { | |||
margin: auto; | |||
} | |||
.title { | |||
display: flex; | |||
flex-direction: row; | |||
justify-content: space-between; | |||
align-items: center; | |||
} | |||
h1 { | |||
color: #35636e; | |||
text-align: left; | |||
} | |||
header { | |||
font-size: 1.5rem; | |||
padding: 20px; | |||
background-color: #35636e; | |||
color: white; | |||
} | |||
#dialog { | |||
display: flex; | |||
flex-direction: column; | |||
width: 500px; | |||
border: 1px solid #35636e; | |||
border-radius: 3px; | |||
} | |||
#content { | |||
display: flex; | |||
flex-direction: column; | |||
padding: 20px; | |||
} | |||
#footer { | |||
display: flex; | |||
justify-content: flex-end; | |||
padding-top: 20px; | |||
} | |||
.buttons { | |||
display: flex; | |||
flex-direction: row; | |||
justify-content: space-around; | |||
padding: 0px 10px 20px 10px; | |||
} | |||
.button { | |||
margin-top: 0px; | |||
margin-bottom: 5px; | |||
color: #35636e; | |||
font-size: 1rem; | |||
padding: 15px; | |||
background-color: white; | |||
border-radius: 3px; | |||
border: solid 2px #35636e; | |||
outline: none; | |||
cursor: pointer; | |||
transition: 0.2s; | |||
} | |||
.button:hover { | |||
color: white; | |||
background-color: #35636e; | |||
} | |||
.button:active { | |||
margin-top: 5px; | |||
margin-bottom: 0px; | |||
} | |||
#public-token { | |||
word-break: break-word; | |||
} | |||
#public-token-copy, | |||
#public-token-copy__copied { | |||
float: right; | |||
text-align: center; | |||
margin: 15px 10px; | |||
padding: 5px; | |||
cursor: pointer; | |||
transition: 0.2s; | |||
} | |||
#public-token-copy:hover { | |||
border-radius: 3px; | |||
color: white; | |||
background-color: #35636e; | |||
} | |||
.text-align-center { | |||
text-align: center; | |||
} | |||
a { | |||
text-decoration: none; | |||
color: royalblue; | |||
} | |||
a:hover { | |||
font-style: italic; | |||
} | |||
#tabs { | |||
display: flex; | |||
flex-direction: row; | |||
justify-content: space-around; | |||
border: none; | |||
color: #35636e; | |||
margin: 0px; | |||
transition: 0.3s; | |||
} | |||
#tabs div { | |||
width: 80%; | |||
padding: 20px; | |||
margin: 0px 10px; | |||
border: solid 1px #35636e; | |||
border-bottom: none; | |||
text-align: center; | |||
border-top-left-radius: 3px; | |||
border-top-right-radius: 3px; | |||
cursor: pointer; | |||
} | |||
#tabs div.active { | |||
background-color: #35636e; | |||
color: white; | |||
} | |||
#transfer-inputs { | |||
display: flex; | |||
flex-direction: row; | |||
justify-content: center; | |||
align-items: center; | |||
padding: 0px 10px 20px 10px; | |||
} | |||
#transfer-inputs input[type="text"] { | |||
padding: 15px; | |||
border-top-left-radius: 3px; | |||
border-bottom-left-radius: 3px; | |||
border: solid 2px #35636e; | |||
outline: none; | |||
font-size: 1rem; | |||
border-right: none; | |||
margin-top: 0px; | |||
margin-bottom: 5px; | |||
} | |||
#transfer-inputs input[type="number"] { | |||
padding: 15px; | |||
width: 60px; | |||
border: solid 2px #35636e; | |||
border-top-right-radius: 3px; | |||
border-bottom-right-radius: 3px; | |||
border-left: none; | |||
outline: none; | |||
margin-right: 5px; | |||
font-size: 1rem; | |||
margin-top: 0px; | |||
margin-bottom: 5px; | |||
text-align: right; | |||
} | |||
/* Removes arrows in input number */ | |||
/* Chrome, Safari, Edge, Opera */ | |||
input::-webkit-outer-spin-button, | |||
input::-webkit-inner-spin-button { | |||
-webkit-appearance: none; | |||
margin: 0; | |||
} | |||
/* Firefox */ | |||
input[type="number"] { | |||
-moz-appearance: textfield; | |||
} |
@@ -0,0 +1,203 @@ | |||
import React, { useState } from "react"; | |||
import { TezosToolkit } from "@taquito/taquito"; | |||
import "./App.css"; | |||
import ConnectButton from "./components/ConnectWallet"; | |||
import DisconnectButton from "./components/DisconnectWallet"; | |||
import qrcode from "qrcode-generator"; | |||
import UpdateContract from "./components/UpdateContract"; | |||
import Transfers from "./components/Transfers"; | |||
import BuyButton from "./components/BuyButton"; | |||
enum BeaconConnection { | |||
NONE = "", | |||
LISTENING = "Listening to P2P channel", | |||
CONNECTED = "Channel connected", | |||
PERMISSION_REQUEST_SENT = "Permission request sent, waiting for response", | |||
PERMISSION_REQUEST_SUCCESS = "Wallet is connected" | |||
} | |||
const App = () => { | |||
const [Tezos, setTezos] = useState<TezosToolkit>( | |||
new TezosToolkit("https://mainnet.api.tez.ie/") | |||
); | |||
const [contract, setContract] = useState<any>(undefined); | |||
const [publicToken, setPublicToken] = useState<string | null>(""); | |||
const [wallet, setWallet] = useState<any>(null); | |||
const [userAddress, setUserAddress] = useState<string>(""); | |||
const [userBalance, setUserBalance] = useState<number>(0); | |||
const [storage, setStorage] = useState<number>(0); | |||
const [copiedPublicToken, setCopiedPublicToken] = useState<boolean>(false); | |||
const [beaconConnection, setBeaconConnection] = useState<boolean>(false); | |||
const [activeTab, setActiveTab] = useState<string>("transfer"); | |||
// Granadanet Increment/Decrement contract | |||
const contractAddress: string = "KT1K3XVNzsmur7VRgY8CAHPUENaErzzEpe4e"; | |||
const generateQrCode = (): { __html: string } => { | |||
const qr = qrcode(0, "L"); | |||
qr.addData(publicToken || ""); | |||
qr.make(); | |||
return { __html: qr.createImgTag(4) }; | |||
}; | |||
if (publicToken && (!userAddress || isNaN(userBalance))) { | |||
return ( | |||
<div className="main-box"> | |||
<h1>Taquito Boilerplate</h1> | |||
<div id="dialog"> | |||
<header>Try the Taquito Boilerplate App!</header> | |||
<div id="content"> | |||
<p className="text-align-center"> | |||
<i className="fas fa-broadcast-tower"></i> Connecting to | |||
your wallet | |||
</p> | |||
<div | |||
dangerouslySetInnerHTML={generateQrCode()} | |||
className="text-align-center" | |||
></div> | |||
<p id="public-token"> | |||
{copiedPublicToken ? ( | |||
<span id="public-token-copy__copied"> | |||
<i className="far fa-thumbs-up"></i> | |||
</span> | |||
) : ( | |||
<span | |||
id="public-token-copy" | |||
onClick={() => { | |||
if (publicToken) { | |||
navigator.clipboard.writeText(publicToken); | |||
setCopiedPublicToken(true); | |||
setTimeout(() => setCopiedPublicToken(false), 2000); | |||
} | |||
}} | |||
> | |||
<i className="far fa-copy"></i> | |||
</span> | |||
)} | |||
<span> | |||
Public token: <span>{publicToken}</span> | |||
</span> | |||
</p> | |||
<p className="text-align-center"> | |||
Status: {beaconConnection ? "Connected" : "Disconnected"} | |||
</p> | |||
</div> | |||
</div> | |||
<div id="footer"> | |||
<img src="built-with-taquito.png" alt="Built with Taquito" /> | |||
</div> | |||
</div> | |||
); | |||
} else if (userAddress && !isNaN(userBalance)) { | |||
return ( | |||
<div className="main-box"> | |||
<h1>Taquito Boilerplate</h1> | |||
<div id="tabs"> | |||
<div | |||
id="transfer" | |||
className={activeTab === "transfer" ? "active" : ""} | |||
onClick={() => setActiveTab("transfer")} | |||
> | |||
Make a transfer | |||
</div> | |||
<div | |||
id="contract" | |||
className={activeTab === "contract" ? "active" : ""} | |||
onClick={() => setActiveTab("contract")} | |||
> | |||
Interact with a contract | |||
</div> | |||
</div> | |||
<div id="dialog"> | |||
<div id="content"> | |||
<p> | |||
<i className="far fa-address-card"></i> {userAddress} | |||
</p> | |||
<p> | |||
<i className="fas fa-piggy-bank"></i> | |||
{(userBalance / 1000000).toLocaleString("en-US")} ꜩ | |||
</p> | |||
</div> | |||
<BuyButton | |||
Tezos={Tezos} | |||
FA2address="KT1BB1uMwVvJ1M3vVHXWALs1RWdgTp1rnXTR" | |||
receiver="tz1hJncvXvL2VyctPE685GJPXDaRJ7dtiwjm" | |||
amount={100} | |||
/> | |||
<DisconnectButton | |||
wallet={wallet} | |||
setPublicToken={setPublicToken} | |||
setUserAddress={setUserAddress} | |||
setUserBalance={setUserBalance} | |||
setWallet={setWallet} | |||
setTezos={setTezos} | |||
setBeaconConnection={setBeaconConnection} | |||
/> | |||
</div> | |||
<div id="footer"> | |||
<img src="built-with-taquito.png" alt="Built with Taquito" /> | |||
</div> | |||
</div> | |||
); | |||
} else if (!publicToken && !userAddress && !userBalance) { | |||
return ( | |||
<div className="main-box"> | |||
<div className="title"> | |||
<h1>Taquito Boilerplate</h1> | |||
<a href="https://app.netlify.com/start/deploy?repository=https://github.com/ecadlabs/taquito-boilerplate"> | |||
<img | |||
src="https://www.netlify.com/img/deploy/button.svg" | |||
alt="netlify-button" | |||
/> | |||
</a> | |||
</div> | |||
<div id="dialog"> | |||
<header>Welcome to Taquito Boilerplate App!</header> | |||
<div id="content"> | |||
<p>Hello!</p> | |||
<p> | |||
This is a template Tezos dApp built using Taquito. It's a starting | |||
point for you to hack on and build your own dApp for Tezos. | |||
<br /> | |||
If you have not done so already, go to the{" "} | |||
<a | |||
href="https://github.com/ecadlabs/taquito-boilerplate" | |||
target="_blank" | |||
rel="noopener noreferrer" | |||
> | |||
Taquito boilerplate Github page | |||
</a>{" "} | |||
and click the <em>"Use this template"</em> button. | |||
</p> | |||
<p>Go forth and Tezos!</p> | |||
</div> | |||
<ConnectButton | |||
Tezos={Tezos} | |||
setContract={setContract} | |||
setPublicToken={setPublicToken} | |||
setWallet={setWallet} | |||
setUserAddress={setUserAddress} | |||
setUserBalance={setUserBalance} | |||
setStorage={setStorage} | |||
contractAddress={contractAddress} | |||
setBeaconConnection={setBeaconConnection} | |||
wallet={wallet} | |||
/> | |||
</div> | |||
<div id="footer"> | |||
<img src="built-with-taquito.png" alt="Built with Taquito" /> | |||
</div> | |||
</div> | |||
); | |||
} else { | |||
return <div>An error has occurred</div>; | |||
} | |||
}; | |||
export default App; |
@@ -0,0 +1,48 @@ | |||
import React, { Dispatch, SetStateAction, useState, useEffect } from "react"; | |||
import { TezosToolkit } from "@taquito/taquito"; | |||
import { BeaconWallet } from "@taquito/beacon-wallet"; | |||
const BuyButton = ({ | |||
Tezos, | |||
FA2address, | |||
receiver, | |||
amount | |||
}: { | |||
Tezos: TezosToolkit, | |||
FA2address: string, | |||
receiver: string, | |||
amount: number | |||
}): JSX.Element => { | |||
const makePayment = async (): Promise<void> => { | |||
const contract = await Tezos.wallet.at(FA2address); | |||
const transfer_params = [ | |||
{ | |||
from_: "tz1LigkX55duWS2pZt4u5qTAWUu6Sb9bMxbg", | |||
txs: [{ | |||
to_: "tz1hJncvXvL2VyctPE685GJPXDaRJ7dtiwjm", | |||
token_id:0, | |||
amount: amount | |||
}] | |||
} | |||
]; | |||
const op = await contract.methods.transfer(transfer_params).send(); | |||
await op.confirmation(); | |||
} | |||
return ( | |||
<div className="buttons"> | |||
<button | |||
className="myButton" | |||
onClick={makePayment} | |||
> | |||
{FA2address} | |||
</button> | |||
</div> | |||
); | |||
}; | |||
export default BuyButton; |
@@ -0,0 +1,137 @@ | |||
import React, { Dispatch, SetStateAction, useState, useEffect } from "react"; | |||
import { TezosToolkit } from "@taquito/taquito"; | |||
import { BeaconWallet } from "@taquito/beacon-wallet"; | |||
import { | |||
NetworkType, | |||
BeaconEvent, | |||
defaultEventCallbacks | |||
} from "@airgap/beacon-sdk"; | |||
import TransportU2F from "@ledgerhq/hw-transport-u2f"; | |||
import { LedgerSigner } from "@taquito/ledger-signer"; | |||
type ButtonProps = { | |||
Tezos: TezosToolkit; | |||
setContract: Dispatch<SetStateAction<any>>; | |||
setWallet: Dispatch<SetStateAction<any>>; | |||
setUserAddress: Dispatch<SetStateAction<string>>; | |||
setUserBalance: Dispatch<SetStateAction<number>>; | |||
setStorage: Dispatch<SetStateAction<number>>; | |||
contractAddress: string; | |||
setBeaconConnection: Dispatch<SetStateAction<boolean>>; | |||
setPublicToken: Dispatch<SetStateAction<string | null>>; | |||
wallet: BeaconWallet; | |||
}; | |||
const ConnectButton = ({ | |||
Tezos, | |||
setContract, | |||
setWallet, | |||
setUserAddress, | |||
setUserBalance, | |||
setStorage, | |||
contractAddress, | |||
setBeaconConnection, | |||
setPublicToken, | |||
wallet | |||
}: ButtonProps): JSX.Element => { | |||
const [loadingNano, setLoadingNano] = useState<boolean>(false); | |||
const setup = async (userAddress: string): Promise<void> => { | |||
setUserAddress(userAddress); | |||
// updates balance | |||
const balance = await Tezos.tz.getBalance(userAddress); | |||
setUserBalance(balance.toNumber()); | |||
// creates contract instance | |||
const contract = await Tezos.wallet.at(contractAddress); | |||
const storage: any = await contract.storage(); | |||
setContract(contract); | |||
setStorage(storage.toNumber()); | |||
}; | |||
const connectWallet = async (): Promise<void> => { | |||
try { | |||
await wallet.requestPermissions({ | |||
network: { | |||
type: NetworkType.MAINNET, | |||
rpcUrl: "https://mainnet.api.tez.ie/" | |||
} | |||
}); | |||
// gets user's address | |||
const userAddress = await wallet.getPKH(); | |||
await setup(userAddress); | |||
setBeaconConnection(true); | |||
} catch (error) { | |||
console.log(error); | |||
} | |||
}; | |||
const connectNano = async (): Promise<void> => { | |||
try { | |||
setLoadingNano(true); | |||
const transport = await TransportU2F.create(); | |||
const ledgerSigner = new LedgerSigner(transport, "44'/1729'/0'/0'", true); | |||
Tezos.setSignerProvider(ledgerSigner); | |||
//Get the public key and the public key hash from the Ledger | |||
const userAddress = await Tezos.signer.publicKeyHash(); | |||
await setup(userAddress); | |||
} catch (error) { | |||
console.log("Error!", error); | |||
setLoadingNano(false); | |||
} | |||
}; | |||
useEffect(() => { | |||
(async () => { | |||
// creates a wallet instance | |||
const wallet = new BeaconWallet({ | |||
name: "Taquito Boilerplate", | |||
preferredNetwork: NetworkType.MAINNET, | |||
disableDefaultEvents: true, // Disable all events / UI. This also disables the pairing alert. | |||
eventHandlers: { | |||
// To keep the pairing alert, we have to add the following default event handlers back | |||
[BeaconEvent.PAIR_INIT]: { | |||
handler: defaultEventCallbacks.PAIR_INIT | |||
}, | |||
[BeaconEvent.PAIR_SUCCESS]: { | |||
handler: data => setPublicToken(data.publicKey) | |||
} | |||
} | |||
}); | |||
Tezos.setWalletProvider(wallet); | |||
setWallet(wallet); | |||
// checks if wallet was connected before | |||
const activeAccount = await wallet.client.getActiveAccount(); | |||
if (activeAccount) { | |||
const userAddress = await wallet.getPKH(); | |||
await setup(userAddress); | |||
setBeaconConnection(true); | |||
} | |||
})(); | |||
}, []); | |||
return ( | |||
<div className="buttons"> | |||
<button className="button" onClick={connectWallet}> | |||
<span> | |||
<i className="fas fa-wallet"></i> Connect with wallet | |||
</span> | |||
</button> | |||
<button className="button" disabled={loadingNano} onClick={connectNano}> | |||
{loadingNano ? ( | |||
<span> | |||
<i className="fas fa-spinner fa-spin"></i> Loading, please | |||
wait | |||
</span> | |||
) : ( | |||
<span> | |||
<i className="fab fa-usb"></i> Connect with Ledger Nano | |||
</span> | |||
)} | |||
</button> | |||
</div> | |||
); | |||
}; | |||
export default ConnectButton; |
@@ -0,0 +1,50 @@ | |||
import React, { Dispatch, SetStateAction } from "react"; | |||
import { BeaconWallet } from "@taquito/beacon-wallet"; | |||
import { TezosToolkit } from "@taquito/taquito"; | |||
interface ButtonProps { | |||
wallet: BeaconWallet | null; | |||
setPublicToken: Dispatch<SetStateAction<string | null>>; | |||
setUserAddress: Dispatch<SetStateAction<string>>; | |||
setUserBalance: Dispatch<SetStateAction<number>>; | |||
setWallet: Dispatch<SetStateAction<any>>; | |||
setTezos: Dispatch<SetStateAction<TezosToolkit>>; | |||
setBeaconConnection: Dispatch<SetStateAction<boolean>>; | |||
} | |||
const DisconnectButton = ({ | |||
wallet, | |||
setPublicToken, | |||
setUserAddress, | |||
setUserBalance, | |||
setWallet, | |||
setTezos, | |||
setBeaconConnection | |||
}: ButtonProps): JSX.Element => { | |||
const disconnectWallet = async (): Promise<void> => { | |||
//window.localStorage.clear(); | |||
setUserAddress(""); | |||
setUserBalance(0); | |||
setWallet(null); | |||
const tezosTK = new TezosToolkit("https://api.tez.ie/rpc/granadanet"); | |||
setTezos(tezosTK); | |||
setBeaconConnection(false); | |||
setPublicToken(null); | |||
console.log("disconnecting wallet"); | |||
if (wallet) { | |||
await wallet.client.removeAllAccounts(); | |||
await wallet.client.removeAllPeers(); | |||
await wallet.client.destroy(); | |||
} | |||
}; | |||
return ( | |||
<div className="buttons"> | |||
<button className="button" onClick={disconnectWallet}> | |||
<i className="fas fa-times"></i> Disconnect wallet | |||
</button> | |||
</div> | |||
); | |||
}; | |||
export default DisconnectButton; |
@@ -0,0 +1,70 @@ | |||
import React, { useState, Dispatch, SetStateAction } from "react"; | |||
import { TezosToolkit } from "@taquito/taquito"; | |||
const Transfers = ({ | |||
Tezos, | |||
setUserBalance, | |||
userAddress | |||
}: { | |||
Tezos: TezosToolkit; | |||
setUserBalance: Dispatch<SetStateAction<number>>; | |||
userAddress: string; | |||
}): JSX.Element => { | |||
const [recipient, setRecipient] = useState<string>(""); | |||
const [amount, setAmount] = useState<string>(""); | |||
const [loading, setLoading] = useState<boolean>(false); | |||
const sendTransfer = async (): Promise<void> => { | |||
if (recipient && amount) { | |||
setLoading(true); | |||
try { | |||
const op = await Tezos.wallet | |||
.transfer({ to: recipient, amount: parseInt(amount) }) | |||
.send(); | |||
await op.confirmation(); | |||
setRecipient(""); | |||
setAmount(""); | |||
const balance = await Tezos.tz.getBalance(userAddress); | |||
setUserBalance(balance.toNumber()); | |||
} catch (error) { | |||
console.log(error); | |||
} finally { | |||
setLoading(false); | |||
} | |||
} | |||
}; | |||
return ( | |||
<div id="transfer-inputs"> | |||
<input | |||
type="text" | |||
placeholder="Recipient" | |||
value={recipient} | |||
onChange={e => setRecipient(e.target.value)} | |||
/> | |||
<input | |||
type="number" | |||
placeholder="Amount" | |||
value={amount} | |||
onChange={e => setAmount(e.target.value)} | |||
/> | |||
<button | |||
className="button" | |||
disabled={!recipient && !amount} | |||
onClick={sendTransfer} | |||
> | |||
{loading ? ( | |||
<span> | |||
<i className="fas fa-spinner fa-spin"></i> Please wait | |||
</span> | |||
) : ( | |||
<span> | |||
<i className="far fa-paper-plane"></i> Send | |||
</span> | |||
)} | |||
</button> | |||
</div> | |||
); | |||
}; | |||
export default Transfers; |
@@ -0,0 +1,75 @@ | |||
import React, { useState, Dispatch, SetStateAction } from "react"; | |||
import { TezosToolkit, WalletContract } from "@taquito/taquito"; | |||
interface UpdateContractProps { | |||
contract: WalletContract | any; | |||
setUserBalance: Dispatch<SetStateAction<any>>; | |||
Tezos: TezosToolkit; | |||
userAddress: string; | |||
setStorage: Dispatch<SetStateAction<number>>; | |||
} | |||
const UpdateContract = ({ contract, setUserBalance, Tezos, userAddress, setStorage }: UpdateContractProps) => { | |||
const [loadingIncrement, setLoadingIncrement] = useState<boolean>(false); | |||
const [loadingDecrement, setLoadingDecrement] = useState<boolean>(false); | |||
const increment = async (): Promise<void> => { | |||
setLoadingIncrement(true); | |||
try { | |||
const op = await contract.methods.increment(1).send(); | |||
await op.confirmation(); | |||
const newStorage: any = await contract.storage(); | |||
if (newStorage) setStorage(newStorage.toNumber()); | |||
setUserBalance(await Tezos.tz.getBalance(userAddress)); | |||
} catch (error) { | |||
console.log(error); | |||
} finally { | |||
setLoadingIncrement(false); | |||
} | |||
}; | |||
const decrement = async (): Promise<void> => { | |||
setLoadingDecrement(true); | |||
try { | |||
const op = await contract.methods.decrement(1).send(); | |||
await op.confirmation(); | |||
const newStorage: any = await contract.storage(); | |||
if (newStorage) setStorage(newStorage.toNumber()); | |||
setUserBalance(await Tezos.tz.getBalance(userAddress)); | |||
} catch (error) { | |||
console.log(error); | |||
} finally { | |||
setLoadingDecrement(false); | |||
} | |||
}; | |||
if (!contract && !userAddress) return <div> </div>; | |||
return ( | |||
<div className="buttons"> | |||
<button className="button" disabled={loadingIncrement} onClick={increment}> | |||
{loadingIncrement ? ( | |||
<span> | |||
<i className="fas fa-spinner fa-spin"></i> Please wait | |||
</span> | |||
) : ( | |||
<span> | |||
<i className="fas fa-plus"></i> Increment by 1 | |||
</span> | |||
)} | |||
</button> | |||
<button className="button" onClick={decrement}> | |||
{loadingDecrement ? ( | |||
<span> | |||
<i className="fas fa-spinner fa-spin"></i> Please wait | |||
</span> | |||
) : ( | |||
<span> | |||
<i className="fas fa-minus"></i> Decrement by 1 | |||
</span> | |||
)} | |||
</button> | |||
</div> | |||
); | |||
}; | |||
export default UpdateContract; |
@@ -0,0 +1,16 @@ | |||
import React from "react"; | |||
import ReactDOM from "react-dom"; | |||
import App from "./App.tsx"; | |||
import * as serviceWorker from "./serviceWorker"; | |||
ReactDOM.render( | |||
<React.StrictMode> | |||
<App /> | |||
</React.StrictMode>, | |||
document.getElementById("root") | |||
); | |||
// If you want your app to work offline and load faster, you can change | |||
// unregister() to register() below. Note this comes with some pitfalls. | |||
// Learn more about service workers: https://bit.ly/CRA-PWA | |||
serviceWorker.unregister(); |
@@ -0,0 +1 @@ | |||
/// <reference types="react-scripts" /> |
@@ -0,0 +1,141 @@ | |||
// This optional code is used to register a service worker. | |||
// register() is not called by default. | |||
// This lets the app load faster on subsequent visits in production, and gives | |||
// it offline capabilities. However, it also means that developers (and users) | |||
// will only see deployed updates on subsequent visits to a page, after all the | |||
// existing tabs open on the page have been closed, since previously cached | |||
// resources are updated in the background. | |||
// To learn more about the benefits of this model and instructions on how to | |||
// opt-in, read https://bit.ly/CRA-PWA | |||
const isLocalhost = Boolean( | |||
window.location.hostname === 'localhost' || | |||
// [::1] is the IPv6 localhost address. | |||
window.location.hostname === '[::1]' || | |||
// 127.0.0.0/8 are considered localhost for IPv4. | |||
window.location.hostname.match( | |||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ | |||
) | |||
); | |||
export function register(config) { | |||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { | |||
// The URL constructor is available in all browsers that support SW. | |||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); | |||
if (publicUrl.origin !== window.location.origin) { | |||
// Our service worker won't work if PUBLIC_URL is on a different origin | |||
// from what our page is served on. This might happen if a CDN is used to | |||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374 | |||
return; | |||
} | |||
window.addEventListener('load', () => { | |||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; | |||
if (isLocalhost) { | |||
// This is running on localhost. Let's check if a service worker still exists or not. | |||
checkValidServiceWorker(swUrl, config); | |||
// Add some additional logging to localhost, pointing developers to the | |||
// service worker/PWA documentation. | |||
navigator.serviceWorker.ready.then(() => { | |||
console.log( | |||
'This web app is being served cache-first by a service ' + | |||
'worker. To learn more, visit https://bit.ly/CRA-PWA' | |||
); | |||
}); | |||
} else { | |||
// Is not localhost. Just register service worker | |||
registerValidSW(swUrl, config); | |||
} | |||
}); | |||
} | |||
} | |||
function registerValidSW(swUrl, config) { | |||
navigator.serviceWorker | |||
.register(swUrl) | |||
.then(registration => { | |||
registration.onupdatefound = () => { | |||
const installingWorker = registration.installing; | |||
if (installingWorker == null) { | |||
return; | |||
} | |||
installingWorker.onstatechange = () => { | |||
if (installingWorker.state === 'installed') { | |||
if (navigator.serviceWorker.controller) { | |||
// At this point, the updated precached content has been fetched, | |||
// but the previous service worker will still serve the older | |||
// content until all client tabs are closed. | |||
console.log( | |||
'New content is available and will be used when all ' + | |||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.' | |||
); | |||
// Execute callback | |||
if (config && config.onUpdate) { | |||
config.onUpdate(registration); | |||
} | |||
} else { | |||
// At this point, everything has been precached. | |||
// It's the perfect time to display a | |||
// "Content is cached for offline use." message. | |||
console.log('Content is cached for offline use.'); | |||
// Execute callback | |||
if (config && config.onSuccess) { | |||
config.onSuccess(registration); | |||
} | |||
} | |||
} | |||
}; | |||
}; | |||
}) | |||
.catch(error => { | |||
console.error('Error during service worker registration:', error); | |||
}); | |||
} | |||
function checkValidServiceWorker(swUrl, config) { | |||
// Check if the service worker can be found. If it can't reload the page. | |||
fetch(swUrl, { | |||
headers: { 'Service-Worker': 'script' }, | |||
}) | |||
.then(response => { | |||
// Ensure service worker exists, and that we really are getting a JS file. | |||
const contentType = response.headers.get('content-type'); | |||
if ( | |||
response.status === 404 || | |||
(contentType != null && contentType.indexOf('javascript') === -1) | |||
) { | |||
// No service worker found. Probably a different app. Reload the page. | |||
navigator.serviceWorker.ready.then(registration => { | |||
registration.unregister().then(() => { | |||
window.location.reload(); | |||
}); | |||
}); | |||
} else { | |||
// Service worker found. Proceed as normal. | |||
registerValidSW(swUrl, config); | |||
} | |||
}) | |||
.catch(() => { | |||
console.log( | |||
'No internet connection found. App is running in offline mode.' | |||
); | |||
}); | |||
} | |||
export function unregister() { | |||
if ('serviceWorker' in navigator) { | |||
navigator.serviceWorker.ready | |||
.then(registration => { | |||
registration.unregister(); | |||
}) | |||
.catch(error => { | |||
console.error(error.message); | |||
}); | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
{ | |||
"compilerOptions": { | |||
"target": "es6", | |||
"lib": [ | |||
"dom", | |||
"dom.iterable", | |||
"esnext" | |||
], | |||
"allowJs": true, | |||
"skipLibCheck": true, | |||
"esModuleInterop": true, | |||
"allowSyntheticDefaultImports": true, | |||
"strict": true, | |||
"forceConsistentCasingInFileNames": true, | |||
"module": "esnext", | |||
"moduleResolution": "node", | |||
"resolveJsonModule": true, | |||
"isolatedModules": true, | |||
"noEmit": true, | |||
"jsx": "react-jsx", | |||
"noFallthroughCasesInSwitch": true | |||
}, | |||
"include": [ | |||
"src" | |||
] | |||
} |