# 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* |
# freaPay | # 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" |
{ | |||||
"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" | |||||
] | |||||
} | |||||
} |
<!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> |
{ | |||||
"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" | |||||
} |
# https://www.robotstxt.org/robotstxt.html | |||||
User-agent: * | |||||
Disallow: |
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; | |||||
} |
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; |
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; |
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; |
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; |
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; |
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; |
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(); |
/// <reference types="react-scripts" /> |
// 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); | |||||
}); | |||||
} | |||||
} |
{ | |||||
"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" | |||||
] | |||||
} |