├── packages ├── react-app │ ├── .eslintignore │ ├── src │ │ ├── contracts │ │ │ └── contracts.js │ │ ├── react-app-env.d.ts │ │ ├── helpers │ │ │ ├── index.js │ │ │ ├── WalletConnectPayloadSignHelper.js │ │ │ ├── PolygonRelayerAccountHelper.js │ │ │ ├── LiFiTokenPriceHelper.js │ │ │ ├── utils.ts │ │ │ ├── EditorHelper.js │ │ │ ├── messageFormatter.ts │ │ │ ├── ChainHelper.js │ │ │ ├── TokenSettingsHelper.js │ │ │ ├── EIP618Helper.js │ │ │ ├── NetworkSettingsHelper.js │ │ │ ├── PolygonNativeMetaTransaction.js │ │ │ ├── SepaPaymentQrCodeHelper.jsx │ │ │ ├── communicator.ts │ │ │ ├── PolygonTransferWithAuthorizationHelper.js │ │ │ └── ERC20Helper.js │ │ ├── ethereumLogo.png │ │ ├── index.css │ │ ├── views │ │ │ └── index.js │ │ ├── setupTests.js │ │ ├── components │ │ │ ├── NetworkDisplay.jsx │ │ │ ├── Blockie.jsx │ │ │ ├── GenericModal.jsx │ │ │ ├── Contract │ │ │ │ ├── utils.js │ │ │ │ └── DisplayVariable.jsx │ │ │ ├── Punk.jsx │ │ │ ├── PasteButton.jsx │ │ │ ├── Header.jsx │ │ │ ├── PunkBlockie.jsx │ │ │ ├── TokenDisplay.jsx │ │ │ ├── AmountDollarSwitch.jsx │ │ │ ├── MoneriumOnChainCrossChainRadio.jsx │ │ │ ├── WalletConnectV2ConnectionError.jsx │ │ │ ├── MoneriumDescription.jsx │ │ │ ├── GasGauge.jsx │ │ │ ├── LogoOnLogo.jsx │ │ │ ├── ThemeSwitch.jsx │ │ │ ├── TokenBalance.jsx │ │ │ ├── MoneriumPunkNotConnected.jsx │ │ │ ├── WalletConnectActiveSessions.jsx │ │ │ ├── MoneriumHeader.jsx │ │ │ ├── Balance.jsx │ │ │ ├── SelectorWithSettings.jsx │ │ │ ├── MoneriumCrossChainAddressSelector.jsx │ │ │ ├── Reload.jsx │ │ │ ├── Provider.jsx │ │ │ ├── TokenDetailedDisplay.jsx │ │ │ ├── CustomRPC.jsx │ │ │ ├── BytesStringInput.jsx │ │ │ ├── index.js │ │ │ ├── Faucet.jsx │ │ │ ├── Account.jsx │ │ │ ├── TransactionResponses.jsx │ │ │ ├── MoneriumBalances.jsx │ │ │ ├── Address.jsx │ │ │ ├── ERC20Balance.jsx │ │ │ ├── TransactionHistory.jsx │ │ │ └── QRPunkBlockie.jsx │ │ ├── App.test.js │ │ ├── themes │ │ │ ├── light-theme.less │ │ │ └── dark-theme.less │ │ ├── hooks │ │ │ ├── Nonce.js │ │ │ ├── Debounce.js │ │ │ ├── Poller.js │ │ │ ├── ResolveName.js │ │ │ ├── OnBlock.js │ │ │ ├── index.js │ │ │ ├── TokenList.js │ │ │ ├── GasPrice.js │ │ │ ├── ContractExistsAtAddress.js │ │ │ ├── EventListener.js │ │ │ ├── Balance.js │ │ │ ├── ExternalContractLoader.js │ │ │ ├── CustomContractLoader.js │ │ │ ├── ExchangePrice.js │ │ │ ├── LookupAddress.js │ │ │ ├── UserProvider.js │ │ │ ├── LocalStorage.js │ │ │ ├── ContractLoader.js │ │ │ └── ContractReader.js │ │ ├── App.css │ │ └── index.jsx │ ├── public │ │ ├── robots.txt │ │ ├── ARB.png │ │ ├── BAL.png │ │ ├── DAI.png │ │ ├── ENS.png │ │ ├── ETH.png │ │ ├── EURe.png │ │ ├── GTC.png │ │ ├── OP.png │ │ ├── USDC.png │ │ ├── USDT.png │ │ ├── WETH.png │ │ ├── aero.png │ │ ├── beng.png │ │ ├── pepe.png │ │ ├── punk.png │ │ ├── sDAI.png │ │ ├── strm.png │ │ ├── xDAI.png │ │ ├── MATIC.png │ │ ├── bonke.png │ │ ├── boomer.png │ │ ├── bshib.png │ │ ├── degen.png │ │ ├── punks.png │ │ ├── rocky.png │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── tenderly.png │ │ ├── MoneriumLogo.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── scaffold-eth.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── open_in_new.svg │ │ ├── manifest.json │ │ ├── greenCheckmark.svg │ │ ├── polygon-bgfill-icon.svg │ │ ├── ethereum-bgfill-icon.svg │ │ ├── gnosis-bgfill-icon.svg │ │ ├── MoneriumEURe.svg │ │ ├── index.html │ │ └── paste-svgrepo-com.svg │ ├── .sample.env │ ├── .prettierrc │ ├── scripts │ │ ├── watch.js │ │ ├── s3.js │ │ └── ipfs.js │ ├── tsconfig.json │ ├── gulpfile.js │ ├── .eslintrc.js │ └── package.json ├── subgraph │ ├── src │ │ ├── schema.graphql │ │ ├── subgraph.template.yaml │ │ └── mapping.ts │ ├── abis │ │ └── YourContract.json │ └── package.json └── hardhat │ ├── contracts │ └── YourContract.sol │ ├── scripts │ ├── watch.js │ └── publish.js │ ├── .eslintrc.js │ ├── test │ └── myTest.js │ └── package.json ├── docker └── graph-node │ ├── bin │ ├── debug │ ├── create │ ├── remove │ ├── reassign │ └── deploy │ ├── hooks │ └── post_checkout │ ├── tag.sh │ ├── build.sh │ ├── docker-compose.yml │ ├── setup.sh │ ├── wait_for │ ├── cloudbuild.yaml │ ├── Dockerfile │ ├── README.md │ └── start ├── .gitpod.yml ├── .gitignore ├── LICENSE ├── README.md └── package.json /packages/react-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # folders 2 | build/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /packages/react-app/src/contracts/contracts.js: -------------------------------------------------------------------------------- 1 | module.exports = ["YourContract"]; -------------------------------------------------------------------------------- /packages/react-app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as Transactor } from "./Transactor"; 2 | -------------------------------------------------------------------------------- /packages/react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /packages/react-app/.sample.env: -------------------------------------------------------------------------------- 1 | REACT_APP_PROVIDER=https://rinkeby.infura.io/v3/2717afb6bf164045b5d5468031b93f87 2 | -------------------------------------------------------------------------------- /packages/react-app/public/ARB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/ARB.png -------------------------------------------------------------------------------- /packages/react-app/public/BAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/BAL.png -------------------------------------------------------------------------------- /packages/react-app/public/DAI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/DAI.png -------------------------------------------------------------------------------- /packages/react-app/public/ENS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/ENS.png -------------------------------------------------------------------------------- /packages/react-app/public/ETH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/ETH.png -------------------------------------------------------------------------------- /packages/react-app/public/EURe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/EURe.png -------------------------------------------------------------------------------- /packages/react-app/public/GTC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/GTC.png -------------------------------------------------------------------------------- /packages/react-app/public/OP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/OP.png -------------------------------------------------------------------------------- /packages/react-app/public/USDC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/USDC.png -------------------------------------------------------------------------------- /packages/react-app/public/USDT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/USDT.png -------------------------------------------------------------------------------- /packages/react-app/public/WETH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/WETH.png -------------------------------------------------------------------------------- /packages/react-app/public/aero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/aero.png -------------------------------------------------------------------------------- /packages/react-app/public/beng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/beng.png -------------------------------------------------------------------------------- /packages/react-app/public/pepe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/pepe.png -------------------------------------------------------------------------------- /packages/react-app/public/punk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/punk.png -------------------------------------------------------------------------------- /packages/react-app/public/sDAI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/sDAI.png -------------------------------------------------------------------------------- /packages/react-app/public/strm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/strm.png -------------------------------------------------------------------------------- /packages/react-app/public/xDAI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/xDAI.png -------------------------------------------------------------------------------- /packages/react-app/public/MATIC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/MATIC.png -------------------------------------------------------------------------------- /packages/react-app/public/bonke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/bonke.png -------------------------------------------------------------------------------- /packages/react-app/public/boomer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/boomer.png -------------------------------------------------------------------------------- /packages/react-app/public/bshib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/bshib.png -------------------------------------------------------------------------------- /packages/react-app/public/degen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/degen.png -------------------------------------------------------------------------------- /packages/react-app/public/punks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/punks.png -------------------------------------------------------------------------------- /packages/react-app/public/rocky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/rocky.png -------------------------------------------------------------------------------- /packages/react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/favicon.ico -------------------------------------------------------------------------------- /packages/react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/logo192.png -------------------------------------------------------------------------------- /packages/react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/logo512.png -------------------------------------------------------------------------------- /packages/react-app/public/tenderly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/tenderly.png -------------------------------------------------------------------------------- /packages/react-app/src/ethereumLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/src/ethereumLogo.png -------------------------------------------------------------------------------- /packages/react-app/public/MoneriumLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/MoneriumLogo.png -------------------------------------------------------------------------------- /packages/react-app/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/favicon-16x16.png -------------------------------------------------------------------------------- /packages/react-app/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/favicon-32x32.png -------------------------------------------------------------------------------- /packages/react-app/public/scaffold-eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/scaffold-eth.png -------------------------------------------------------------------------------- /packages/react-app/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/apple-touch-icon.png -------------------------------------------------------------------------------- /packages/react-app/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /packages/react-app/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaffold-eth/punk-wallet/HEAD/packages/react-app/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /packages/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | code { 6 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 7 | monospace; 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-app/src/views/index.js: -------------------------------------------------------------------------------- 1 | export { default as ExampleUI } from "./ExampleUI"; 2 | export { default as Hints } from "./Hints"; 3 | export { default as Subgraph } from "./Subgraph"; 4 | -------------------------------------------------------------------------------- /packages/react-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "printWidth": 120, 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /docker/graph-node/bin/debug: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ -f "$1" ] 4 | then 5 | exec rust-gdb -c "$1" /usr/local/cargo/bin/graph-node 6 | else 7 | echo "usage: debug " 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /docker/graph-node/hooks/post_checkout: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | echo "Setting SOURCE_BRANCH to ${SOURCE_BRANCH}" 7 | 8 | sed -i "s@^ENV SOURCE_BRANCH \"master\"@ENV SOURCE_BRANCH \"${SOURCE_BRANCH}\"@g" Dockerfile 9 | -------------------------------------------------------------------------------- /packages/react-app/public/open_in_new.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/react-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import "@testing-library/jest-dom/extend-expect"; 6 | -------------------------------------------------------------------------------- /packages/react-app/src/components/NetworkDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function NetworkDisplay({network, style}) { 4 | return ( 5 |
6 | {network.name} 7 |
8 | ); 9 | } -------------------------------------------------------------------------------- /packages/react-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "@testing-library/react"; 3 | import App from "./App"; 4 | 5 | test("renders learn react link", () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /docker/graph-node/bin/create: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo "usage: create " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_create", "params": {"name":"%s"}, "id":"1"}' "$1") 11 | curl -s -H "content-type: application/json" --data "$data" "$api" 12 | -------------------------------------------------------------------------------- /docker/graph-node/bin/remove: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo "usage: create " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_remove", "params": {"name":"%s"}, "id":"1"}' "$1") 11 | curl -s -H "content-type: application/json" --data "$data" "$api" 12 | -------------------------------------------------------------------------------- /packages/subgraph/src/schema.graphql: -------------------------------------------------------------------------------- 1 | type Purpose @entity { 2 | id: ID! 3 | sender: Sender! 4 | purpose: String! 5 | createdAt: BigInt! 6 | transactionHash: String! 7 | } 8 | 9 | type Sender @entity { 10 | id: ID! 11 | address: Bytes! 12 | purposes: [Purpose!] @derivedFrom(field: "sender") 13 | createdAt: BigInt! 14 | purposeCount: BigInt! 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-app/src/themes/light-theme.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/color/colorPalette.less"; 2 | @import "~antd/dist/antd.less"; 3 | @import "~antd/lib/style/themes/default.less"; 4 | 5 | // These are shared variables that can be extracted to their own file 6 | // @primary-color: #00adb5; 7 | @border-radius-base: 4px; 8 | 9 | .highlight { 10 | background-color: #f9f9f9; 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Nonce.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function useNonce(mainnetProvider, address) { 4 | const [nonce, setNonce] = useState(0); 5 | 6 | const Nonce = () => { 7 | async function getNonce() { 8 | setNonce(await mainnetProvider.getTransactionCount(address)); 9 | } 10 | if (address) getNonce(); 11 | }; 12 | Nonce(); 13 | return nonce; 14 | } 15 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/WalletConnectPayloadSignHelper.js: -------------------------------------------------------------------------------- 1 | export const signPayload = (ethersWallet, payload) => { 2 | 3 | } 4 | 5 | export const signTransaction = (ethersWallet, txParams) => { 6 | // Ethers uses gasLimit instead of gas 7 | if (txParams.gas) { 8 | txParams.gasLimit = txParams.gas; 9 | delete txParams.gas; 10 | } 11 | 12 | return ethersWallet.signTransaction(txParams); 13 | } -------------------------------------------------------------------------------- /docker/graph-node/bin/reassign: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: reassign " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | echo Assigning to "$3" 11 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_reassign", "params": {"name":"%s", "ipfs_hash":"%s", "node_id":"%s"}, "id":"1"}' "$1" "$2" "$3") 12 | curl -s -H "content-type: application/json" --data "$data" "$api" 13 | -------------------------------------------------------------------------------- /docker/graph-node/bin/deploy: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 3 ]; then 4 | echo "usage: deploy " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | echo "Deploying $1 (deployment $2)" 11 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_deploy", "params": {"name":"%s", "ipfs_hash":"%s", "node_id":"%s"}, "id":"1"}' "$1" "$2" "$3") 12 | curl -s -H "content-type: application/json" --data "$data" "$api" 13 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/YourContract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.6.0 <0.9.0; 2 | //SPDX-License-Identifier: MIT 3 | 4 | import "hardhat/console.sol"; 5 | 6 | //import "@openzeppelin/contracts/access/Ownable.sol"; //https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol 7 | 8 | contract YourContract { 9 | event Wave(address sender); 10 | 11 | function wave() public { 12 | emit Wave(msg.sender); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Debounce.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default function useDebounce(value, delay) { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /packages/subgraph/abis/YourContract.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "address", 8 | "name": "sender", 9 | "type": "address" 10 | } 11 | ], 12 | "name": "Wave", 13 | "type": "event" 14 | }, 15 | { 16 | "inputs": [], 17 | "name": "wave", 18 | "outputs": [], 19 | "stateMutability": "nonpayable", 20 | "type": "function" 21 | } 22 | ] -------------------------------------------------------------------------------- /packages/react-app/src/components/Blockie.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Blockies from "react-blockies"; 3 | 4 | // provides a blockie image for the address using "react-blockies" library 5 | 6 | export default function Blockie(props) { 7 | if (!props.address || typeof props.address.toLowerCase !== "function") { 8 | return ; 9 | } 10 | // eslint-disable-next-line react/jsx-props-no-spreading 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | :root { 6 | --link-color: rgb(24, 144, 255); 7 | } 8 | 9 | /* 10 | Stacking DIVs on top of each other? 11 | https://stackoverflow.com/questions/1909648/stacking-divs-on-top-of-each-other 12 | */ 13 | .outer { 14 | display: grid; 15 | grid-template: 1fr / 1fr; 16 | place-items: center; 17 | } 18 | .outer > * { 19 | grid-column: 1 / 1; 20 | grid-row: 1 / 1; 21 | } 22 | .outer .below { 23 | z-index: 0; 24 | } 25 | .outer .top { 26 | z-index: 1; 27 | } -------------------------------------------------------------------------------- /packages/react-app/src/components/GenericModal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Modal } from "antd"; 3 | 4 | const showModal = (error) => { 5 | const popUp = () => { 6 | const title = "Coudn't parse the payment link/QR code!"; 7 | 8 | Modal.confirm({ 9 | width: "90%", 10 | title: title, 11 | maskClosable: true, 12 | cancelButtonProps: { style: { display: "none" } }, 13 | content:
{error}
, 14 | }); 15 | }; 16 | 17 | return popUp(); 18 | }; 19 | 20 | export default showModal; 21 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/PolygonRelayerAccountHelper.js: -------------------------------------------------------------------------------- 1 | import { sendTransaction } from "./EIP1559Helper"; 2 | 3 | const { ethers } = require("ethers"); 4 | 5 | const RELAYER_PK = process.env.REACT_APP_RELAYER_PK; 6 | 7 | export const sendTransactionViaRelayerAccount = async (txParams, origin, provider) => { 8 | const relayerWallet = new ethers.Wallet(RELAYER_PK, provider); 9 | 10 | const result = await sendTransaction(txParams, undefined, undefined, relayerWallet); 11 | 12 | result.origin = origin; 13 | 14 | return result; 15 | } 16 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/LiFiTokenPriceHelper.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const API_BASE_URL = "https://li.quest/v1/token"; 4 | 5 | export const getTokenPrice = async (chainId, address) => { 6 | try { 7 | const apiURL = API_BASE_URL + `?chain=${chainId}&token=${address}`; 8 | 9 | const tokenInfo = (await axios.get(apiURL)).data; 10 | 11 | return tokenInfo.priceUSD; 12 | } catch (error) { 13 | console.error(`Couldn't fetch token price for ${chainId} ${address} , maybe there is a rate limit`, error); 14 | 15 | return 0; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("🛠 Compiling & Deploying..."); 6 | exec("yarn deploy", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Contracts..."); 14 | watch("./contracts", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/react-app/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("Compiling & Generating..."); 6 | exec("npx gulp less", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Themes..."); 14 | watch("./src/themes", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/hardhat/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true, 4 | }, 5 | extends: ["airbnb", "plugin:prettier/recommended"], 6 | plugins: ["babel"], 7 | rules: { 8 | "prettier/prettier": ["error"], 9 | "import/extensions": [ 10 | "error", 11 | "ignorePackages", 12 | { 13 | js: "never", 14 | ts: "never", 15 | }, 16 | ], 17 | "import/prefer-default-export": "off", 18 | "prefer-destructuring": "off", 19 | "prefer-template": "off", 20 | "no-console": "off", 21 | "func-names": "off", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/utils.js: -------------------------------------------------------------------------------- 1 | import { formatUnits } from "@ethersproject/units"; 2 | import React from "react"; 3 | import { Address } from ".."; 4 | 5 | const tryToDisplay = thing => { 6 | if (thing && thing.toNumber) { 7 | try { 8 | return thing.toNumber(); 9 | } catch (e) { 10 | return "Ξ" + formatUnits(thing, "ether"); 11 | } 12 | } 13 | if (thing && thing.indexOf && thing.indexOf("0x") === 0 && thing.length === 42) { 14 | return
; 15 | } 16 | return JSON.stringify(thing); 17 | }; 18 | 19 | export default tryToDisplay; 20 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Punk.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Punk({ address, size }) { 4 | const part1 = address.substr(2, 20); 5 | const part2 = address.substr(22); 6 | 7 | const x = parseInt(part1, 16) % 100; 8 | const y = parseInt(part2, 16) % 100; 9 | 10 | return ( 11 |
12 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "PunkWallet.io", 3 | "start_url": ".", 4 | "name": "PunkWallet.io", 5 | "icons": [ 6 | { 7 | "src": "favicon.ico", 8 | "sizes": "64x64 32x32 24x24 16x16", 9 | "type": "image/x-icon" 10 | }, 11 | { 12 | "src": "logo192.png", 13 | "type": "image/png", 14 | "sizes": "192x192" 15 | }, 16 | { 17 | "src": "logo512.png", 18 | "type": "image/png", 19 | "sizes": "512x512" 20 | } 21 | ], 22 | "start_url": ".", 23 | "display": "browser", 24 | "theme_color": "#000000", 25 | "background_color": "#ffffff" 26 | } 27 | -------------------------------------------------------------------------------- /packages/react-app/src/components/PasteButton.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Button, Tooltip } from "antd"; 4 | import { SnippetsOutlined } from "@ant-design/icons"; 5 | 6 | export default function PasteButton({ setState, disabled }) { 7 | return ( 8 | 9 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/OnBlock.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | // helper hook to call a function regularly in time intervals 4 | const DEBUG = false; 5 | 6 | export default function useOnBlock(provider, fn, args) { 7 | const savedCallback = useRef(); 8 | // Remember the latest fn. 9 | useEffect(() => { 10 | savedCallback.current = fn; 11 | }, [fn]); 12 | 13 | // Turn on the listener if we have a function & a provider 14 | useEffect(() => { 15 | if (fn && provider) { 16 | const listener = blockNumber => { 17 | if (DEBUG) console.log(blockNumber, fn, args, provider.listeners()); 18 | 19 | if (args && args.length > 0) { 20 | savedCallback.current(...args); 21 | } else { 22 | savedCallback.current(); 23 | } 24 | }; 25 | 26 | provider.on("block", listener); 27 | 28 | return () => { 29 | provider.off("block", listener); 30 | }; 31 | } 32 | }, [provider]); 33 | } 34 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/messageFormatter.ts: -------------------------------------------------------------------------------- 1 | import { ErrorResponse, SDKRequestData, RequestId, SuccessResponse, MethodToResponse, Methods } from "./types"; 2 | import { getSDKVersion, generateRequestId } from "./utils"; 3 | 4 | class MessageFormatter { 5 | static makeRequest = (method: M, params: P): SDKRequestData => { 6 | const id = generateRequestId(); 7 | 8 | return { 9 | id, 10 | method, 11 | params, 12 | env: { 13 | sdkVersion: getSDKVersion(), 14 | }, 15 | }; 16 | }; 17 | 18 | static makeResponse = (id: RequestId, data: MethodToResponse[Methods], version: string): SuccessResponse => ({ 19 | id, 20 | success: true, 21 | version, 22 | data, 23 | }); 24 | 25 | static makeErrorResponse = (id: RequestId, error: string, version: string): ErrorResponse => ({ 26 | id, 27 | success: false, 28 | error, 29 | version, 30 | }); 31 | } 32 | 33 | export { MessageFormatter }; 34 | -------------------------------------------------------------------------------- /packages/react-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | }, 5 | extends: ["airbnb", "plugin:prettier/recommended", "prettier/react"], 6 | plugins: ["babel"], 7 | rules: { 8 | "prettier/prettier": ["error"], 9 | "import/extensions": [ 10 | "error", 11 | "ignorePackages", 12 | { 13 | js: "never", 14 | jsx: "never", 15 | ts: "never", 16 | tsx: "never", 17 | }, 18 | ], 19 | "import/prefer-default-export": "off", 20 | "prefer-destructuring": "off", 21 | "prefer-template": "off", 22 | "react/prop-types": "off", 23 | "react/destructuring-assignment": "off", 24 | "no-console": "off", 25 | "jsx-a11y/accessible-emoji": ["off"], 26 | "jsx-a11y/click-events-have-key-events": ["off"], 27 | "jsx-a11y/no-static-element-interactions": ["off"], 28 | "no-underscore-dangle": "off", 29 | "no-nested-ternary": "off", 30 | "no-restricted-syntax": "off", 31 | "no-plusplus": "off", 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/react-app/src/components/LogoOnLogo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function LogoOnLogo ({ src1, src2, sizeMultiplier1 = 1, sizeMultiplier2 = 0.375, showImage2 = true, onClickAction = () => {} }) { 4 | return ( 5 |
{onClickAction()}} > 6 | {src1} 14 | {showImage2 && {src2}} 25 |
26 | ); 27 | }; -------------------------------------------------------------------------------- /docker/graph-node/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | graph-node: 4 | image: graphprotocol/graph-node 5 | ports: 6 | - '8000:8000' 7 | - '8001:8001' 8 | - '8020:8020' 9 | - '8030:8030' 10 | - '8040:8040' 11 | depends_on: 12 | - ipfs 13 | - postgres 14 | environment: 15 | postgres_host: postgres 16 | postgres_user: graph-node 17 | postgres_pass: let-me-in 18 | postgres_db: graph-node 19 | ipfs: 'ipfs:5001' 20 | ethereum: 'localhost:http://host.docker.internal:8545' 21 | RUST_LOG: info 22 | ipfs: 23 | image: ipfs/go-ipfs:v0.4.23 24 | ports: 25 | - '5001:5001' 26 | volumes: 27 | - ./data/ipfs:/data/ipfs 28 | postgres: 29 | image: postgres 30 | ports: 31 | - '5432:5432' 32 | command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"] 33 | environment: 34 | POSTGRES_USER: graph-node 35 | POSTGRES_PASSWORD: let-me-in 36 | POSTGRES_DB: graph-node 37 | volumes: 38 | - ./data/postgres:/var/lib/postgresql/data 39 | -------------------------------------------------------------------------------- /packages/subgraph/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { BigInt, Address } from "@graphprotocol/graph-ts" 2 | import { 3 | YourContract, 4 | SetPurpose 5 | } from "../generated/YourContract/YourContract" 6 | import { Purpose, Sender } from "../generated/schema" 7 | 8 | export function handleSetPurpose(event: SetPurpose): void { 9 | 10 | let senderString = event.params.sender.toHexString() 11 | 12 | let sender = Sender.load(senderString) 13 | 14 | if (sender == null) { 15 | sender = new Sender(senderString) 16 | sender.address = event.params.sender 17 | sender.createdAt = event.block.timestamp 18 | sender.purposeCount = BigInt.fromI32(1) 19 | } 20 | else { 21 | sender.purposeCount = sender.purposeCount.plus(BigInt.fromI32(1)) 22 | } 23 | 24 | let purpose = new Purpose(event.transaction.hash.toHex() + "-" + event.logIndex.toString()) 25 | 26 | purpose.purpose = event.params.purpose 27 | purpose.sender = senderString 28 | purpose.createdAt = event.block.timestamp 29 | purpose.transactionHash = event.transaction.hash.toHex() 30 | 31 | purpose.save() 32 | sender.save() 33 | 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Austin Griffith 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/react-app/src/components/ThemeSwitch.jsx: -------------------------------------------------------------------------------- 1 | import { Switch } from "antd"; 2 | import React, { useEffect, useState } from "react"; 3 | import { useThemeSwitcher } from "react-css-theme-switcher"; 4 | 5 | export default function ThemeSwitcher() { 6 | const theme = window.localStorage.getItem("theme"); 7 | const [isDarkMode, setIsDarkMode] = useState(!(!theme || theme === "light")); 8 | const { switcher, currentTheme, status, themes } = useThemeSwitcher(); 9 | 10 | useEffect(() => { 11 | window.localStorage.setItem("theme", currentTheme); 12 | }, [currentTheme]); 13 | 14 | const toggleTheme = isChecked => { 15 | setIsDarkMode(isChecked); 16 | switcher({ theme: isChecked ? themes.dark : themes.light }); 17 | }; 18 | 19 | // Avoid theme change flicker 20 | // if (status === "loading") { 21 | // return null; 22 | // } 23 | 24 | return ( 25 |
26 | {currentTheme === "light" ? "☀️" : "🌜"} 27 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/react-app/src/index.jsx: -------------------------------------------------------------------------------- 1 | import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client"; 2 | import React from "react"; 3 | import { ThemeSwitcherProvider } from "react-css-theme-switcher"; 4 | import ReactDOM from "react-dom"; 5 | import App from "./App"; 6 | import "./index.css"; 7 | import { SafeInjectProvider } from "./contexts/SafeInjectContext"; 8 | 9 | const themes = { 10 | dark: `${process.env.PUBLIC_URL}/dark-theme.css`, 11 | light: `${process.env.PUBLIC_URL}/light-theme.css`, 12 | }; 13 | 14 | const prevTheme = window.localStorage.getItem("theme"); 15 | 16 | const subgraphUri = "http://localhost:8000/subgraphs/name/scaffold-eth/your-contract"; 17 | 18 | const client = new ApolloClient({ 19 | uri: subgraphUri, 20 | cache: new InMemoryCache(), 21 | }); 22 | 23 | ReactDOM.render( 24 | 25 | 26 | 27 | 28 | 29 | 30 | , 31 | document.getElementById("root"), 32 | ); 33 | -------------------------------------------------------------------------------- /packages/react-app/public/greenCheckmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docker/graph-node/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if ! which docker 2>&1 > /dev/null; then 6 | echo "Please install 'docker' first" 7 | exit 1 8 | fi 9 | 10 | if ! which docker-compose 2>&1 > /dev/null; then 11 | echo "Please install 'docker-compose' first" 12 | exit 1 13 | fi 14 | 15 | if ! which jq 2>&1 > /dev/null; then 16 | echo "Please install 'jq' first" 17 | exit 1 18 | fi 19 | 20 | # Create the graph-node container 21 | docker-compose up --no-start graph-node 22 | 23 | # Start graph-node so we can inspect it 24 | docker-compose start graph-node 25 | 26 | # Identify the container ID 27 | CONTAINER_ID=$(docker container ls | grep graph-node | cut -d' ' -f1) 28 | 29 | # Inspect the container to identify the host IP address 30 | HOST_IP=$(docker inspect "$CONTAINER_ID" | jq -r .[0].NetworkSettings.Networks[].Gateway) 31 | 32 | echo "Host IP: $HOST_IP" 33 | 34 | # Inject the host IP into docker-compose.yml 35 | sed -i -e "s/host.docker.internal/$HOST_IP/g" docker-compose.yml 36 | 37 | function stop_graph_node { 38 | # Ensure graph-node is stopped 39 | docker-compose stop graph-node 40 | } 41 | 42 | trap stop_graph_node EXIT 43 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useBalance } from "./Balance"; 2 | export { default as useContractExistsAtAddress } from "./ContractExistsAtAddress"; 3 | export { default as useContractLoader } from "./ContractLoader"; 4 | export { default as useContractReader } from "./ContractReader"; 5 | export { default as useCustomContractLoader } from "./CustomContractLoader"; 6 | export { default as useDebounce } from "./Debounce"; 7 | export { default as useEventListener } from "./EventListener"; 8 | export { default as useExchangePrice } from "./ExchangePrice"; 9 | export { default as useExternalContractLoader } from "./ExternalContractLoader"; 10 | export { default as useGasPrice } from "./GasPrice"; 11 | export { default as useLocalStorage } from "./LocalStorage"; 12 | export { default as useLookupAddress } from "./LookupAddress"; 13 | export { default as useNonce } from "./Nonce"; 14 | export { default as useOnBlock } from "./OnBlock"; 15 | export { default as usePoller } from "./Poller"; 16 | export { default as useResolveName } from "./ResolveName"; 17 | export { default as useTokenList } from "./TokenList"; 18 | export { default as useUserProvider } from "./UserProvider"; 19 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/ChainHelper.js: -------------------------------------------------------------------------------- 1 | const isEIP3091Explorer = explorer => explorer.standard === "EIP3091" || explorer.name === "basescan"; 2 | 3 | export const getBLockExplorers = chain => chain.explorers.filter(explorer => isEIP3091Explorer(explorer)); 4 | 5 | export const getBLockExplorer = (chain, name) => 6 | getBLockExplorers(chain).find(blockExplorer => blockExplorer.name === name); 7 | 8 | const getChains = async () => { 9 | try { 10 | const response = await fetch("https://chainid.network/chains.json"); 11 | 12 | if (response.ok) { 13 | return response.json(); 14 | } 15 | } catch (error) { 16 | console.error("Couldn't fetch chains.json", error); 17 | } 18 | 19 | return null; 20 | }; 21 | 22 | const getLocalChains = () => { 23 | return require("../constants/chains.json"); 24 | }; 25 | 26 | export async function initChains(useLocal) { 27 | if (useLocal) { 28 | return getLocalChains(); 29 | } 30 | const chains = (await getChains()) || getLocalChains(); 31 | 32 | return chains; 33 | } 34 | 35 | export const getChain = async (chainId, useLocal) => { 36 | const chains = await initChains(useLocal); 37 | 38 | return chains.find(chain => chain.chainId === chainId); 39 | }; 40 | -------------------------------------------------------------------------------- /packages/react-app/scripts/s3.js: -------------------------------------------------------------------------------- 1 | const s3FolderUpload = require("s3-folder-upload"); 2 | const fs = require("fs"); 3 | 4 | const directoryName = "build"; 5 | 6 | const BUCKETNAME = "punkwallet.io"; // <<---- SET YOUR BUCKET NAME AND CREATE aws.json ** see below vvvvvvvvvv 7 | 8 | if (!BUCKETNAME) { 9 | console.log("☢️ Enter a bucket name in packages/react-app/scripts/s3.js "); 10 | process.exit(1); 11 | } 12 | 13 | let credentials = {}; 14 | try { 15 | credentials = JSON.parse(fs.readFileSync("aws.json")); 16 | } catch (e) { 17 | console.log(e); 18 | console.log( 19 | '☢️ Create an aws.json credentials file in packages/react-app/ like { "accessKeyId": "xxx", "secretAccessKey": "xxx", "region": "xxx" } ', 20 | ); 21 | process.exit(1); 22 | } 23 | console.log("credentials", credentials); 24 | 25 | credentials.bucket = BUCKETNAME; 26 | 27 | // optional options to be passed as parameter to the method 28 | const options = { 29 | useFoldersForFileTypes: false, 30 | useIAMRoleCredentials: false, 31 | }; 32 | 33 | // optional cloudfront invalidation rule 34 | const invalidation = { 35 | awsDistributionId: "E3D4GB8Y5ZDQB0", 36 | awsInvalidationPath: "/*" 37 | } 38 | 39 | s3FolderUpload(directoryName, credentials, options , invalidation ); 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🏗 scaffold-eth - 🧑‍🎤 [PunkWallet.io](https://PunkWallet.io) 2 | 3 | > An open source Ethereum web wallet. 4 | 5 | 🎥 [watch this 10m demo](https://youtu.be/lYRd1k1RBAQ) 6 | 7 | --- 8 | 9 | > PunkWallet.io is a forkable burner wallet with Wallet Connect up front. 10 | 11 | ![image](https://user-images.githubusercontent.com/2653167/153722202-5368187d-4189-499e-94a3-1ee41596f445.png) 12 | 13 | 14 | 15 | > It's easy to send ETH an many different networks: 16 | 17 | ![image](https://user-images.githubusercontent.com/2653167/153722191-e0e99867-2724-489d-a2a6-d471a580cc24.png) 18 | 19 | 20 | --- 21 | 22 | # 🏃‍♀️ Fork the code and make your own wallet or on your own network!!! 23 | 24 | required: [Node](https://nodejs.org/dist/latest-v12.x/) plus [Yarn](https://classic.yarnpkg.com/en/docs/install/) and [Git](https://git-scm.com/downloads) 25 | 26 | 27 | ```bash 28 | git clone https://github.com/scaffold-eth/punk-wallet 29 | 30 | cd punk-wallet 31 | ``` 32 | 33 | ```bash 34 | 35 | yarn install 36 | 37 | ``` 38 | 39 | ```bash 40 | 41 | yarn start 42 | 43 | ``` 44 | 45 | > in a second terminal window: 46 | 47 | ```bash 48 | cd punk-wallet 49 | yarn chain 50 | 51 | ``` 52 | 53 | 📝 Edit your frontend `App.jsx` in `packages/react-app/src` 54 | 55 | 📱 Open http://localhost:3000 to see the app 56 | -------------------------------------------------------------------------------- /packages/react-app/public/polygon-bgfill-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/react-app/src/components/TokenBalance.jsx: -------------------------------------------------------------------------------- 1 | import { formatEther } from "@ethersproject/units"; 2 | import { useTokenBalance } from "eth-hooks"; 3 | import React, { useState } from "react"; 4 | 5 | export default function TokenBalance(props) { 6 | const [dollarMode, setDollarMode] = useState(true); 7 | 8 | const tokenContract = props.contracts && props.contracts[props.name]; 9 | const balance = useTokenBalance(tokenContract, props.address, 1777); 10 | 11 | let floatBalance = parseFloat("0.00"); 12 | 13 | let usingBalance = balance; 14 | 15 | if (typeof props.balance !== "undefined") { 16 | usingBalance = props.balance; 17 | } 18 | 19 | if (usingBalance) { 20 | const etherBalance = formatEther(usingBalance); 21 | parseFloat(etherBalance).toFixed(2); 22 | floatBalance = parseFloat(etherBalance); 23 | } 24 | 25 | let displayBalance = floatBalance.toFixed(4); 26 | 27 | if (props.dollarMultiplier && dollarMode) { 28 | displayBalance = "$" + (floatBalance * props.dollarMultiplier).toFixed(2); 29 | } 30 | 31 | return ( 32 | { 40 | setDollarMode(!dollarMode); 41 | }} 42 | > 43 | {props.img} {displayBalance} 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/TokenList.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | /* 4 | ~ What it does? ~ 5 | 6 | Gets a tokenlist (see more at https://tokenlists.org/), returning the .tokens only 7 | 8 | ~ How can I use? ~ 9 | 10 | const tokenList = useTokenList(); <- default returns the Unsiwap tokens 11 | const tokenList = useTokenList("https://gateway.ipfs.io/ipns/tokens.uniswap.org"); 12 | 13 | ~ Features ~ 14 | 15 | - Optional - specify chainId to filter by chainId 16 | */ 17 | 18 | const useTokenList = (tokenListUri, chainId) => { 19 | const [tokenList, setTokenList] = useState([]); 20 | 21 | const _tokenListUri = tokenListUri || "https://gateway.ipfs.io/ipns/tokens.uniswap.org"; 22 | 23 | useEffect(() => { 24 | const getTokenList = async () => { 25 | try { 26 | const tokenList = await fetch(_tokenListUri); 27 | const tokenListJson = await tokenList.json(); 28 | let _tokenList; 29 | 30 | if (chainId) { 31 | _tokenList = tokenListJson.tokens.filter(function (t) { 32 | return t.chainId === chainId; 33 | }); 34 | } else { 35 | _tokenList = tokenListJson; 36 | } 37 | 38 | setTokenList(_tokenList.tokens); 39 | } catch (e) { 40 | console.log(e); 41 | } 42 | }; 43 | getTokenList(); 44 | }, [tokenListUri]); 45 | 46 | return tokenList; 47 | }; 48 | 49 | export default useTokenList; 50 | -------------------------------------------------------------------------------- /packages/react-app/public/ethereum-bgfill-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/GasPrice.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { usePoller } from "eth-hooks"; 3 | import { useEffect, useState } from "react"; 4 | import { ETHERSCAN_KEY } from "../constants"; 5 | import { ethers } from "ethers"; 6 | 7 | export default function useGasPrice(targetNetwork, speed, providerToAsk) { 8 | const [gasPrice, setGasPrice] = useState(); 9 | 10 | useEffect(() => { 11 | setGasPrice(); 12 | loadGasPrice(); 13 | }, [targetNetwork]); 14 | 15 | const loadGasPrice = async () => { 16 | 17 | if (targetNetwork.gasPrice) { 18 | setGasPrice(targetNetwork.gasPrice); 19 | } else { 20 | if(providerToAsk){ 21 | try{ 22 | const gasPriceResult = await providerToAsk.getGasPrice(); 23 | if(gasPriceResult) setGasPrice(gasPriceResult) 24 | }catch(e){ 25 | console.log("error getting gas",e) 26 | } 27 | }else{ 28 | axios 29 | .get("https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=" + ETHERSCAN_KEY) 30 | .then(response => { 31 | const newGasPrice = ethers.utils.parseUnits(response.data.result["ProposeGasPrice"], "gwei") 32 | if (newGasPrice !== gasPrice) { 33 | setGasPrice(newGasPrice); 34 | } 35 | }) 36 | .catch(error => console.log(error)); 37 | } 38 | } 39 | 40 | 41 | }; 42 | 43 | usePoller(loadGasPrice, 4200); 44 | return gasPrice; 45 | } 46 | -------------------------------------------------------------------------------- /packages/hardhat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/hardhat", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "eslint": "^7.5.0", 8 | "eslint-config-airbnb": "^18.2.0", 9 | "eslint-config-prettier": "^6.11.0", 10 | "eslint-plugin-babel": "^5.3.1", 11 | "eslint-plugin-prettier": "^3.1.4" 12 | }, 13 | "dependencies": { 14 | "@nomiclabs/hardhat-ethers": "^2.0.0", 15 | "@nomiclabs/hardhat-etherscan": "^2.1.1", 16 | "@nomiclabs/hardhat-waffle": "^2.0.0", 17 | "@openzeppelin/contracts": "^3.2.0", 18 | "@tenderly/hardhat-tenderly": "^1.0.10", 19 | "chai": "^4.2.0", 20 | "chalk": "^4.1.0", 21 | "ethereum-waffle": "^3.1.1", 22 | "ethers": "^5.0.17", 23 | "hardhat": "^2.0.11", 24 | "node-watch": "^0.7.0", 25 | "qrcode-terminal": "^0.12.0", 26 | "ramda": "^0.27.1" 27 | }, 28 | "scripts": { 29 | "chain": "hardhat node", 30 | "fork": "hardhat node --fork https://mainnet.infura.io/v3/460f40a260564ac4a4f4b3fffb032dad", 31 | "test": "hardhat test --network hardhat", 32 | "compile": "hardhat compile", 33 | "deploy": "hardhat run scripts/deploy.js", 34 | "postdeploy": "hardhat run scripts/publish.js", 35 | "watch": "node scripts/watch.js", 36 | "accounts": "hardhat accounts", 37 | "balance": "hardhat balance", 38 | "send": "hardhat send", 39 | "generate": "hardhat generate", 40 | "account": "hardhat account" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractExistsAtAddress.js: -------------------------------------------------------------------------------- 1 | import { isAddress } from "@ethersproject/address"; 2 | import { useEffect, useState } from "react"; 3 | 4 | /* 5 | ~ What it does? ~ 6 | 7 | Checks whether a contract exists on the blockchain, returns true if it exists, otherwise false 8 | 9 | ~ How can I use? ~ 10 | 11 | const contractIsDeployed = useContractExistsAtAddress(localProvider, contractAddress); 12 | 13 | ~ Features ~ 14 | 15 | - Provide contractAddress to check if the contract is deployed 16 | - Change provider to check contract address on different chains (ex. mainnetProvider) 17 | */ 18 | 19 | const useContractExistsAtAddress = (provider, contractAddress) => { 20 | const [contractIsDeployed, setContractIsDeployed] = useState(false); 21 | 22 | // We can look at the blockchain and see what's stored at `contractAddress` 23 | // If we find code then we know that a contract exists there. 24 | // If we find nothing (0x0) then there is no contract deployed to that address 25 | useEffect(() => { 26 | // eslint-disable-next-line consistent-return 27 | const checkDeployment = async () => { 28 | if (!isAddress(contractAddress)) return false; 29 | const bytecode = await provider.getCode(contractAddress); 30 | setContractIsDeployed(bytecode !== "0x0"); 31 | }; 32 | if (provider) checkDeployment(); 33 | }, [provider, contractAddress]); 34 | 35 | return contractIsDeployed; 36 | }; 37 | 38 | export default useContractExistsAtAddress; 39 | -------------------------------------------------------------------------------- /packages/react-app/src/components/MoneriumPunkNotConnected.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { Button} from "antd"; 4 | import { ExportOutlined } from "@ant-design/icons"; 5 | 6 | import { linkAddress, getShortAddress } from "../helpers/MoneriumHelper"; 7 | 8 | export default function MoneriumPunkNotConnected( { moneriumClient, currentPunkAddress, initClientData } ) { 9 | const [linkButtonLoading, setLinkButtonLoading] = useState(false); 10 | 11 | return ( 12 |
13 |
14 | Your current Punk Wallet {getShortAddress(currentPunkAddress)} is not linked to your Monerium account. 15 |
16 |
17 | 31 |
32 |
33 | ); 34 | } -------------------------------------------------------------------------------- /packages/react-app/public/gnosis-bgfill-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Contract/DisplayVariable.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/accessible-emoji */ 2 | /* eslint-disable jsx-a11y/anchor-is-valid */ 3 | import { Col, Divider, Row } from "antd"; 4 | import React, { useCallback, useEffect, useState } from "react"; 5 | import tryToDisplay from "./utils"; 6 | 7 | const DisplayVariable = ({ contractFunction, functionInfo, refreshRequired, triggerRefresh }) => { 8 | const [variable, setVariable] = useState(""); 9 | 10 | const refresh = useCallback(async () => { 11 | try { 12 | const funcResponse = await contractFunction(); 13 | setVariable(funcResponse); 14 | triggerRefresh(false); 15 | } catch (e) { 16 | console.log(e); 17 | } 18 | }, [setVariable, contractFunction, triggerRefresh]); 19 | 20 | useEffect(() => { 21 | refresh(); 22 | }, [refresh, refreshRequired, contractFunction]); 23 | 24 | return ( 25 |
26 | 27 | 36 | {functionInfo.name} 37 | 38 | 39 |

{tryToDisplay(variable)}

40 | 41 | 42 |

43 | 44 | 🔄 45 | 46 |

47 | 48 |
49 | 50 |
51 | ); 52 | }; 53 | 54 | export default DisplayVariable; 55 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/EventListener.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | /* 4 | ~ What it does? ~ 5 | 6 | Enables you to keep track of events 7 | 8 | ~ How can I use? ~ 9 | 10 | const setPurposeEvents = useEventListener(readContracts, "YourContract", "SetPurpose", localProvider, 1); 11 | 12 | ~ Features ~ 13 | 14 | - Provide readContracts by loading contracts (see more on ContractLoader.js) 15 | - Specify the name of the contract, in this case it is "YourContract" 16 | - Specify the name of the event in the contract, in this case we keep track of "SetPurpose" event 17 | - Specify the provider 18 | */ 19 | 20 | export default function useEventListener(contracts, contractName, eventName, provider, startBlock, args) { 21 | const [updates, setUpdates] = useState([]); 22 | 23 | useEffect(() => { 24 | if (typeof provider !== "undefined" && typeof startBlock !== "undefined") { 25 | // if you want to read _all_ events from your contracts, set this to the block number it is deployed 26 | provider.resetEventsBlock(startBlock); 27 | } 28 | if (contracts && contractName && contracts[contractName]) { 29 | try { 30 | contracts[contractName].on(eventName, (...args) => { 31 | const blockNumber = args[args.length - 1].blockNumber; 32 | setUpdates(messages => [{ blockNumber, ...args.pop().args }, ...messages]); 33 | }); 34 | return () => { 35 | contracts[contractName].removeListener(eventName); 36 | }; 37 | } catch (e) { 38 | console.log(e); 39 | } 40 | } 41 | }, [provider, startBlock, contracts, contractName, eventName]); 42 | 43 | return updates; 44 | } 45 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/TokenSettingsHelper.js: -------------------------------------------------------------------------------- 1 | export const TOKEN_SETTINGS_STORAGE_KEY = "TokenSettings"; 2 | 3 | export const getSelectedErc20Token = (selectedToken, erc20Tokens) => { 4 | if (!selectedToken || !erc20Tokens) { 5 | return undefined; 6 | } 7 | 8 | return erc20Tokens.find(token => token.name === selectedToken.name); 9 | } 10 | 11 | export const getTokens = (nativeToken, erc20Tokens) => { 12 | let tokens; 13 | 14 | if (!nativeToken && !erc20Tokens) { 15 | return tokens 16 | } 17 | 18 | if (nativeToken) { 19 | tokens = [nativeToken]; 20 | } 21 | 22 | if (erc20Tokens) { 23 | if (tokens) { 24 | tokens.push(...erc20Tokens); 25 | } 26 | else { 27 | tokens = [...erc20Tokens] 28 | } 29 | } 30 | 31 | return tokens; 32 | } 33 | 34 | export const migrateSelectedTokenStorageSetting = (networkName, tokenSettingsHelper) => { 35 | // Old code 36 | // const [tokenName, setTokenName] = useLocalStorage(targetNetwork.name + "TokenName"); 37 | 38 | try { 39 | const oldKey = networkName + "TokenName"; 40 | 41 | const storedOldValue = localStorage.getItem(oldKey); 42 | 43 | if (!storedOldValue) { 44 | return; 45 | } 46 | 47 | localStorage.removeItem(oldKey); 48 | 49 | // value was stored with double quotes, e.g.: "DAI" 50 | const oldValue = storedOldValue.replace(/"/g, ''); 51 | 52 | // when the default native token was selected, the storage value was: "" 53 | if (oldValue != '""') { 54 | tokenSettingsHelper.updateSelectedName(oldValue); 55 | } 56 | } 57 | catch (error) { 58 | console.error("Coudn't migrate selected token name setting", error); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /packages/react-app/src/hooks/Balance.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | import useOnBlock from "./OnBlock"; 3 | import usePoller from "./Poller"; 4 | 5 | /* 6 | ~ What it does? ~ 7 | 8 | Gets your balance in ETH from given address and provider 9 | 10 | ~ How can I use? ~ 11 | 12 | const yourLocalBalance = useBalance(localProvider, address); 13 | 14 | ~ Features ~ 15 | 16 | - Provide address and get balance corresponding to given address 17 | - Change provider to access balance on different chains (ex. mainnetProvider) 18 | - If no pollTime is passed, the balance will update on every new block 19 | */ 20 | 21 | const DEBUG = false; 22 | 23 | export default function useBalance(provider, address, pollTime = 0) { 24 | const [balance, setBalance] = useState(); 25 | 26 | useEffect(() => { 27 | setBalance(); 28 | }, [provider]); 29 | 30 | const pollBalance = useCallback( 31 | async (provider, address) => { 32 | if (provider && address) { 33 | const newBalance = await provider.getBalance(address); 34 | if (newBalance !== balance) { 35 | setBalance(newBalance); 36 | } 37 | } 38 | }, 39 | [provider, address], 40 | ); 41 | 42 | // Only pass a provider to watch on a block if there is no pollTime 43 | useOnBlock(pollTime === 0 && provider, () => { 44 | if (provider && address && pollTime === 0) { 45 | pollBalance(provider, address); 46 | } 47 | }); 48 | 49 | // Use a poller if a pollTime is provided 50 | usePoller( 51 | async () => { 52 | if (provider && address && pollTime > 0) { 53 | if (DEBUG) console.log("polling!", address); 54 | pollBalance(); 55 | } 56 | }, 57 | pollTime, 58 | provider && address, 59 | ); 60 | 61 | return balance; 62 | } 63 | -------------------------------------------------------------------------------- /docker/graph-node/wait_for: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # POSIX compatible clone of wait-for-it.sh 4 | # This copy is from https://github.com/eficode/wait-for/commits/master 5 | # at commit 8d9b4446 6 | 7 | TIMEOUT=15 8 | QUIET=0 9 | 10 | echoerr() { 11 | if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi 12 | } 13 | 14 | usage() { 15 | exitcode="$1" 16 | cat << USAGE >&2 17 | Usage: 18 | $cmdname host:port [-t timeout] [-- command args] 19 | -q | --quiet Do not output any status messages 20 | -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout 21 | -- COMMAND ARGS Execute command with args after the test finishes 22 | USAGE 23 | exit "$exitcode" 24 | } 25 | 26 | wait_for() { 27 | for i in `seq $TIMEOUT` ; do 28 | nc -z "$HOST" "$PORT" > /dev/null 2>&1 29 | 30 | result=$? 31 | if [ $result -eq 0 ] ; then 32 | if [ $# -gt 0 ] ; then 33 | exec "$@" 34 | fi 35 | exit 0 36 | fi 37 | sleep 1 38 | done 39 | echo "Operation timed out" >&2 40 | exit 1 41 | } 42 | 43 | while [ $# -gt 0 ] 44 | do 45 | case "$1" in 46 | *:* ) 47 | HOST=$(printf "%s\n" "$1"| cut -d : -f 1) 48 | PORT=$(printf "%s\n" "$1"| cut -d : -f 2) 49 | shift 1 50 | ;; 51 | -q | --quiet) 52 | QUIET=1 53 | shift 1 54 | ;; 55 | -t) 56 | TIMEOUT="$2" 57 | if [ "$TIMEOUT" = "" ]; then break; fi 58 | shift 2 59 | ;; 60 | --timeout=*) 61 | TIMEOUT="${1#*=}" 62 | shift 1 63 | ;; 64 | --) 65 | shift 66 | break 67 | ;; 68 | --help) 69 | usage 0 70 | ;; 71 | *) 72 | echoerr "Unknown argument: $1" 73 | usage 1 74 | ;; 75 | esac 76 | done 77 | 78 | if [ "$HOST" = "" -o "$PORT" = "" ]; then 79 | echoerr "Error: you need to provide a host and port to test." 80 | usage 2 81 | fi 82 | 83 | wait_for "$@" 84 | -------------------------------------------------------------------------------- /packages/react-app/public/MoneriumEURe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ExternalContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useEffect, useState } from "react"; 5 | 6 | /* 7 | when you want to load an existing contract using just the provider, address, and ABI 8 | */ 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Enables you to load an existing mainnet DAI contract using the provider, address and abi 14 | 15 | ~ How can I use? ~ 16 | 17 | const mainnetDAIContract = useExternalContractLoader(mainnetProvider, DAI_ADDRESS, DAI_ABI) 18 | 19 | ~ Features ~ 20 | 21 | - Specify mainnetProvider 22 | - Specify DAI_ADDRESS and DAI_ABI, you can load/write them using constants.js 23 | */ 24 | export default function useExternalContractLoader(provider, address, ABI, optionalBytecode) { 25 | const [contract, setContract] = useState(); 26 | useEffect(() => { 27 | async function loadContract() { 28 | if (typeof provider !== "undefined" && address && ABI) { 29 | try { 30 | // we need to check to see if this provider has a signer or not 31 | let signer; 32 | const accounts = await provider.listAccounts(); 33 | if (accounts && accounts.length > 0) { 34 | signer = provider.getSigner(); 35 | } else { 36 | signer = provider; 37 | } 38 | 39 | const customContract = new Contract(address, ABI, signer); 40 | if (optionalBytecode) customContract.bytecode = optionalBytecode; 41 | 42 | setContract(customContract); 43 | } catch (e) { 44 | console.log("ERROR LOADING EXTERNAL CONTRACT AT " + address + " (check provider, address, and ABI)!!", e); 45 | } 46 | } 47 | } 48 | loadContract(); 49 | }, [provider, address, ABI, optionalBytecode]); 50 | return contract; 51 | } 52 | -------------------------------------------------------------------------------- /packages/react-app/src/components/WalletConnectActiveSessions.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Input } from "antd"; 3 | 4 | import { getWalletConnectV2ActiveSessions, disconnectWallectConnectV2Session } from "../helpers/WalletConnectV2Helper"; 5 | 6 | export default function WalletConnectActiveSessions({ web3wallet }) { 7 | return getWalletConnectV2ActiveSessions(web3wallet).map((activeSession, index) => ( 8 |
9 | 10 |
11 | )); 12 | } 13 | 14 | const WalletConnectActiveSession = ({ web3wallet, activeSession }) => { 15 | const walletConnectPeerMeta = activeSession?.peer?.metadata; 16 | 17 | return ( 18 |
19 | {walletConnectPeerMeta?.icons[0] ? ( 20 | 21 | {walletConnectPeerMeta?.icons[0] && ( 22 | {walletConnectPeerMeta.name 27 | )} 28 | 29 | ) : ( 30 | 31 | )} 32 | )"} 35 | value={walletConnectPeerMeta?.name ? walletConnectPeerMeta.name : "Wallet Connect"} 36 | disabled={true} 37 | /> 38 | 39 | { 42 | await disconnectWallectConnectV2Session(web3wallet, activeSession.topic); 43 | }} 44 | > 45 | 🗑 46 | 47 |
48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/CustomContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useEffect, useState } from "react"; 5 | 6 | /* 7 | when you want to load a local contract's abi but supply a custom address 8 | */ 9 | 10 | /* 11 | ~ What it does? ~ 12 | 13 | Enables you to load a local contract with custom address 14 | 15 | ~ How can I use? ~ 16 | 17 | const customContract = useCustomContractLoader(localProvider, "YourContract", customAddress) 18 | 19 | ~ Features ~ 20 | 21 | - Specify the localProvider 22 | - Specify the name of the contract, in this case it is "YourContract" 23 | - Specify the customAddress of your contract 24 | */ 25 | 26 | export default function useCustomContractLoader(provider, contractName, address) { 27 | const [contract, setContract] = useState(); 28 | useEffect(() => { 29 | async function loadContract() { 30 | if (typeof provider !== "undefined" && contractName && address) { 31 | try { 32 | // we need to check to see if this provider has a signer or not 33 | let signer; 34 | const accounts = await provider.listAccounts(); 35 | if (accounts && accounts.length > 0) { 36 | signer = provider.getSigner(); 37 | } else { 38 | signer = provider; 39 | } 40 | 41 | const customContract = new Contract(address, require(`../contracts/${contractName}.abi.js`), signer); 42 | try { 43 | customContract.bytecode = require(`../contracts/${contractName}.bytecode.js`); 44 | } catch (e) { 45 | console.log(e); 46 | } 47 | 48 | setContract(customContract); 49 | } catch (e) { 50 | console.log("ERROR LOADING CONTRACTS!!", e); 51 | } 52 | } 53 | } 54 | loadContract(); 55 | }, [provider, contractName, address]); 56 | return contract; 57 | } 58 | -------------------------------------------------------------------------------- /packages/react-app/src/components/MoneriumHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Button } from "antd"; 4 | import { LoginOutlined, LogoutOutlined } from "@ant-design/icons"; 5 | 6 | import { LogoOnLogo } from "./"; 7 | 8 | import { getNewMoneriumClient, getAuthFlowURI, cleanStorage} from "../helpers/MoneriumHelper"; 9 | 10 | export default function MoneriumHeader( { moneriumConnected, setMoneriumConnected, setPunkConnectedToMonerium, setMoneriumClient, setClientData} ) { 11 | const disconnectClient = () => { 12 | cleanStorage(); 13 | 14 | setMoneriumConnected(false); 15 | setPunkConnectedToMonerium(false); 16 | setClientData(null); 17 | 18 | setMoneriumClient(getNewMoneriumClient()); 19 | } 20 | 21 | return ( 22 |
23 |
window.open('https://monerium.com/', '_blank')}> 26 | 27 | 33 | 34 |
35 | MONERIUM 36 |
37 | 38 | open_in_new.svg 42 |
43 | 44 |
45 | 58 |
59 |
60 | ); 61 | } -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ExchangePrice.js: -------------------------------------------------------------------------------- 1 | import { Fetcher, Route, Token, WETH } from "@uniswap/sdk"; 2 | import { usePoller } from "eth-hooks"; 3 | import { useEffect, useState } from "react"; 4 | 5 | export default function useExchangePrice(targetNetwork, mainnetProvider, pollTime) { 6 | const [price, setPrice] = useState(0); 7 | 8 | useEffect(() => { 9 | setPrice(0); 10 | pollPrice(); 11 | }, [targetNetwork]); 12 | 13 | const pollPrice = () => { 14 | async function getPrice() { 15 | if (targetNetwork && targetNetwork.price && targetNetwork.price.indexOf("uniswap") >= 0) { 16 | const DAI = new Token( 17 | mainnetProvider.network ? mainnetProvider.network.chainId : 1, 18 | "0x6B175474E89094C44Da98b954EedeAC495271d0F", 19 | 18, 20 | ); 21 | const pair = await Fetcher.fetchPairData(DAI, WETH[DAI.chainId], mainnetProvider); 22 | const route = new Route([pair], WETH[DAI.chainId]); 23 | const priceOfETHinDAI = parseFloat(route.midPrice.toSignificant(6)); 24 | let contractAddress = targetNetwork.price.replace("uniswap", ""); 25 | contractAddress = contractAddress.replace(":", ""); 26 | if (contractAddress) { 27 | const TOKEN = new Token(mainnetProvider.network ? mainnetProvider.network.chainId : 1, contractAddress, 18); 28 | const pair = await Fetcher.fetchPairData(WETH[TOKEN.chainId], TOKEN, mainnetProvider); 29 | const route = new Route([pair], TOKEN); 30 | const price = parseFloat(route.midPrice.toSignificant(6) * priceOfETHinDAI); 31 | setPrice(price); 32 | } else { 33 | setPrice(priceOfETHinDAI); 34 | } 35 | } else if (targetNetwork.price) { 36 | setPrice(targetNetwork.price); 37 | } else { 38 | setPrice(0); 39 | } 40 | } 41 | if (targetNetwork.price && !targetNetwork.price.indexOf) { 42 | setPrice(targetNetwork.price); 43 | } else { 44 | getPrice(); 45 | } 46 | }; 47 | usePoller(pollPrice, pollTime || 37777); 48 | 49 | return price; 50 | } 51 | -------------------------------------------------------------------------------- /packages/react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | PunkWallet.io 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /packages/react-app/public/paste-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 16 | 18 | 20 | 22 | 24 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/EIP618Helper.js: -------------------------------------------------------------------------------- 1 | import showModal from "../components/GenericModal"; 2 | import { NETWORKS, ERROR_MESSAGES } from "../constants"; 3 | import { parse } from "eth-url-parser"; 4 | import BigNumber from 'bignumber.js'; 5 | 6 | const { CHAIN_ERROR } = ERROR_MESSAGES; 7 | 8 | const handleNetworkChange = (chainId, networkSettingsHelper, setTargetNetwork) => { 9 | if (chainId) { 10 | const incomingNetwork = Object.values(NETWORKS).find(network => network.chainId === parseInt(chainId)); 11 | 12 | if (incomingNetwork) { 13 | const currentNetwork = networkSettingsHelper.getSelectedItem(); 14 | 15 | if (currentNetwork.chainId !== incomingNetwork.chainId) { 16 | networkSettingsHelper.updateSelectedName(incomingNetwork.name); 17 | setTargetNetwork(networkSettingsHelper.getSelectedItem(true)); 18 | } 19 | } else { 20 | showModal(CHAIN_ERROR.NOT_SUPPORTED + " : " + chainId); 21 | } 22 | } else { 23 | showModal(CHAIN_ERROR.NOT_PROVIDED); 24 | } 25 | }; 26 | 27 | export const parseEIP618 = (eip681URL, networkSettingsHelper, setTargetNetwork, setToAddress, setAmount) => { 28 | const eip681Object = parse(eip681URL); 29 | 30 | const chainId = eip681Object.chain_id; 31 | 32 | handleNetworkChange(chainId, networkSettingsHelper, setTargetNetwork); 33 | 34 | const functionName = eip681Object.function_name; 35 | 36 | let amount; 37 | let toAddress; 38 | 39 | if (functionName == "transfer") { 40 | const tokenAddress = eip681Object.target_address; 41 | 42 | if (tokenAddress) { 43 | localStorage.setItem("switchToTokenAddress", tokenAddress); 44 | amount = eip681Object.parameters?.uint256; 45 | } 46 | 47 | toAddress = eip681Object.parameters?.address; 48 | } else { 49 | localStorage.setItem("switchToEth", true); 50 | 51 | amount = eip681Object.parameters?.value; 52 | toAddress = eip681Object.target_address; 53 | } 54 | 55 | if (amount) { 56 | localStorage.setItem("amount", new BigNumber(amount).toFixed()); 57 | } 58 | else { 59 | setAmount(""); 60 | } 61 | 62 | if (toAddress) { 63 | setToAddress(toAddress); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Balance.jsx: -------------------------------------------------------------------------------- 1 | import { formatEther } from "@ethersproject/units"; 2 | import React, { useState } from "react"; 3 | import { useBalance } from "../hooks"; 4 | 5 | /* 6 | ~ What it does? ~ 7 | 8 | Displays a balance of given address in ether & dollar 9 | 10 | ~ How can I use? ~ 11 | 12 | 17 | 18 | ~ If you already have the balance as a bignumber ~ 19 | 23 | 24 | ~ Features ~ 25 | 26 | - Provide address={address} and get balance corresponding to given address 27 | - Provide provider={mainnetProvider} to access balance on mainnet or any other network (ex. localProvider) 28 | - Provide price={price} of ether and get your balance converted to dollars 29 | */ 30 | 31 | export default function Balance(props) { 32 | // const [listening, setListening] = useState(false); 33 | 34 | const dollarMode = props.dollarMode; 35 | const setDollarMode = props.setDollarMode; 36 | 37 | const balance = useBalance(props.provider, props.address); 38 | 39 | let floatBalance = parseFloat("0.00"); 40 | 41 | let usingBalance = balance; 42 | 43 | if (typeof props.balance !== "undefined") { 44 | usingBalance = props.balance; 45 | } 46 | if (typeof props.value !== "undefined") { 47 | usingBalance = props.value; 48 | } 49 | 50 | if (usingBalance) { 51 | const etherBalance = formatEther(usingBalance); 52 | parseFloat(etherBalance).toFixed(2); 53 | floatBalance = parseFloat(etherBalance); 54 | } 55 | 56 | let displayBalance = floatBalance.toFixed(4); 57 | 58 | const price = props.price || props.dollarMultiplier; 59 | 60 | if (price && dollarMode) { 61 | displayBalance = "$" + (floatBalance * price).toFixed(2); 62 | } 63 | 64 | return ( 65 | { 73 | setDollarMode(!dollarMode); 74 | }} 75 | > 76 | {displayBalance} 77 | 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /packages/react-app/src/components/SelectorWithSettings.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | import { Select } from "antd"; 4 | import { SettingOutlined } from "@ant-design/icons"; 5 | 6 | const option = (item, itemCoreDisplay, style) => ( 7 | 8 | {itemCoreDisplay(item)} 9 | 10 | ); 11 | 12 | const SETTINGS_NAME = "SettingsName"; 13 | const settingsOption = () => ( 14 |
15 | 16 |
17 | ); 18 | 19 | export default function SelectorWithSettings({ 20 | settingsHelper, 21 | itemCoreDisplay, 22 | settingsModalOpen, 23 | onChange, 24 | optionStyle, 25 | }) { 26 | const selectedItem = settingsHelper.getSelectedItem(); 27 | const selectedItemName = selectedItem ? selectedItem.name : settingsHelper.items[0].name; 28 | 29 | const [currentValue, setCurrentValue] = useState(selectedItemName); 30 | 31 | useEffect(() => { 32 | // This is only needed once, after migrating an old storage key 33 | if (selectedItem && selectedItem.name != currentValue) { 34 | setCurrentValue(selectedItem.name); 35 | } 36 | }, [selectedItem]); 37 | 38 | const options = settingsHelper.sortedItems.map(item => option(item, itemCoreDisplay, optionStyle)); 39 | 40 | options.push(option({ name: SETTINGS_NAME }, settingsOption, { fontSize: 32 })); 41 | 42 | const listHeight = window.innerHeight * 0.6; 43 | 44 | return ( 45 |
46 | 65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/LookupAddress.js: -------------------------------------------------------------------------------- 1 | import { getAddress, isAddress } from "@ethersproject/address"; 2 | import { useEffect, useState } from "react"; 3 | 4 | // resolved if(name){} to not save "" into cache 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Gets ENS name from given address and provider 10 | 11 | ~ How can I use? ~ 12 | 13 | const ensName = useLookupAddress(mainnetProvider, address); 14 | 15 | ~ Features ~ 16 | 17 | - Provide address and get ENS name corresponding to given address 18 | */ 19 | 20 | const lookupAddress = async (provider, address) => { 21 | if (isAddress(address)) { 22 | // console.log(`looking up ${address}`) 23 | try { 24 | // Accuracy of reverse resolution is not enforced. 25 | // We then manually ensure that the reported ens name resolves to address 26 | const reportedName = await provider.lookupAddress(address); 27 | 28 | const resolvedAddress = await provider.resolveName(reportedName); 29 | 30 | if (getAddress(address) === getAddress(resolvedAddress)) { 31 | return reportedName; 32 | } 33 | return getAddress(address); 34 | } catch (e) { 35 | return getAddress(address); 36 | } 37 | } 38 | return 0; 39 | }; 40 | 41 | const useLookupAddress = (provider, address) => { 42 | const [ensName, setEnsName] = useState(address); 43 | // const [ensCache, setEnsCache] = useLocalStorage('ensCache_'+address); Writing directly due to sync issues 44 | 45 | useEffect(() => { 46 | let cache = window.localStorage.getItem("ensCache_" + address); 47 | cache = cache && JSON.parse(cache); 48 | 49 | if (cache && cache.timestamp > Date.now()) { 50 | setEnsName(cache.name); 51 | } else if (provider) { 52 | lookupAddress(provider, address).then(name => { 53 | if (name) { 54 | setEnsName(name); 55 | window.localStorage.setItem( 56 | "ensCache_" + address, 57 | JSON.stringify({ 58 | timestamp: Date.now() + 360000, 59 | name, 60 | }), 61 | ); 62 | } 63 | }); 64 | } 65 | }, [provider, address, setEnsName]); 66 | 67 | return ensName; 68 | }; 69 | 70 | export default useLookupAddress; 71 | -------------------------------------------------------------------------------- /docker/graph-node/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | options: 2 | machineType: "N1_HIGHCPU_32" 3 | timeout: 1800s 4 | steps: 5 | - name: 'gcr.io/cloud-builders/docker' 6 | args: ['build', '--target', 'graph-node-build', 7 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 8 | '--build-arg', 'REPO_NAME=$REPO_NAME', 9 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 10 | '--build-arg', 'TAG_NAME=$TAG_NAME', 11 | '-t', 'gcr.io/$PROJECT_ID/graph-node-build:$SHORT_SHA', 12 | '-f', 'docker/Dockerfile', '.'] 13 | - name: 'gcr.io/cloud-builders/docker' 14 | args: ['build', '--target', 'graph-node', 15 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 16 | '--build-arg', 'REPO_NAME=$REPO_NAME', 17 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 18 | '--build-arg', 'TAG_NAME=$TAG_NAME', 19 | '-t', 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA', 20 | '-f', 'docker/Dockerfile', '.'] 21 | - name: 'gcr.io/cloud-builders/docker' 22 | args: ['build', '--target', 'graph-node-debug', 23 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 24 | '--build-arg', 'REPO_NAME=$REPO_NAME', 25 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 26 | '--build-arg', 'TAG_NAME=$TAG_NAME', 27 | '-t', 'gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA', 28 | '-f', 'docker/Dockerfile', '.'] 29 | - name: 'gcr.io/cloud-builders/docker' 30 | args: ['tag', 31 | 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA', 32 | 'lutter/graph-node:$SHORT_SHA'] 33 | - name: 'gcr.io/cloud-builders/docker' 34 | entrypoint: 'bash' 35 | args: ['docker/tag.sh'] 36 | secretEnv: ['PASSWORD'] 37 | env: 38 | - 'SHORT_SHA=$SHORT_SHA' 39 | - 'TAG_NAME=$TAG_NAME' 40 | - 'PROJECT_ID=$PROJECT_ID' 41 | - 'DOCKER_HUB_USER=$_DOCKER_HUB_USER' 42 | - 'BRANCH_NAME=$BRANCH_NAME' 43 | images: 44 | - 'gcr.io/$PROJECT_ID/graph-node-build:$SHORT_SHA' 45 | - 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA' 46 | - 'gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA' 47 | substitutions: 48 | # The owner of the access token whose encrypted value is in PASSWORD 49 | _DOCKER_HUB_USER: "lutter" 50 | secrets: 51 | - kmsKeyName: projects/the-graph-staging/locations/global/keyRings/docker/cryptoKeys/docker-hub-push 52 | secretEnv: 53 | PASSWORD: 'CiQAdfFldbmUiHgGP1lPq6bAOfd+VQ/dFwyohB1IQwiwQg03ZE8STQDvWKpv6eJHVUN1YoFC5FcooJrH+Stvx9oMD7jBjgxEH5ngIiAysWP3E4Pgxt/73xnaanbM1EQ94eVFKCiY0GaEKFNu0BJx22vCYmU4' 54 | -------------------------------------------------------------------------------- /packages/react-app/src/components/MoneriumCrossChainAddressSelector.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { Radio, Select, Space } from "antd"; 4 | 5 | import { NETWORKS } from "../constants"; 6 | 7 | import { PunkBlockie } from "./"; 8 | 9 | import { 10 | capitalizeFirstLetter, 11 | getAvailableTargetChainNames, 12 | getNetworkColor, 13 | getShortAddress, 14 | } from "../helpers/MoneriumHelper"; 15 | 16 | const optionSize = 28; 17 | 18 | export default function MoneriumCrossChainAddressSelector({ 19 | clientData, 20 | currentPunkAddress, 21 | targetChain, 22 | setTargetChain, 23 | targetAddress, 24 | setTargetAddress, 25 | networkName, 26 | }) { 27 | const radios = getAvailableTargetChainNames(networkName).map(targetChainName => radio(targetChainName)); 28 | 29 | const options = clientData.addressesArray.map(address => option(address)); 30 | 31 | return ( 32 |
33 |
34 | setTargetChain(e.target.value)} value={targetChain}> 35 | 36 | {radios} 37 | 38 | 39 |
40 |
41 | 50 |
51 |
52 | ); 53 | } 54 | 55 | const radio = networkName => { 56 | return ( 57 | 58 | {capitalizeFirstLetter(networkName)} 59 | 60 | ); 61 | }; 62 | 63 | const option = address => ( 64 | 65 | {punkWithShortAddress(address, optionSize)} 66 | 67 | ); 68 | 69 | // ToDo: This is duplicated in TransactionDisplay 70 | const punkWithShortAddress = (address, size = 32) => ( 71 |
77 |
78 | 79 |
80 |
{getShortAddress(address)}
81 |
82 | ); 83 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Reload.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { ReloadOutlined } from "@ant-design/icons"; 4 | 5 | import { ethers } from "ethers"; 6 | import { JsonRpcProvider} from "@ethersproject/providers"; 7 | import { formatEther } from "@ethersproject/units"; 8 | 9 | import { NETWORKS } from "../constants"; 10 | 11 | export default function Reload({ currentPunkAddress, localProvider, networkSettingsHelper, setTargetNetwork }) { 12 | const [checkingBalances, setCheckingBalances] = useState(); 13 | 14 | return ( 15 | { 25 | checkBalances(currentPunkAddress, checkingBalances, setCheckingBalances, localProvider, networkSettingsHelper, setTargetNetwork); 26 | }} 27 | > 28 | 29 | 30 | ); 31 | } 32 | 33 | // ToDo: It could be nice to run this automatically when someone first visits the page, so we would switch to the network with some balance 34 | 35 | // a function to check your balance on every network and switch networks if found... 36 | const checkBalances = async (address, checkingBalances, setCheckingBalances, localProvider, networkSettingsHelper, setTargetNetwork) => { 37 | if (!checkingBalances) { 38 | setCheckingBalances(true); 39 | setTimeout(() => { 40 | setCheckingBalances(false); 41 | }, 5000); 42 | //getting current balance 43 | const currentBalance = await localProvider.getBalance(address); 44 | if (currentBalance && ethers.utils.formatEther(currentBalance) == "0.0") { 45 | console.log("No balance found... searching..."); 46 | for (const n in NETWORKS) { 47 | try { 48 | const tempProvider = new JsonRpcProvider(NETWORKS[n].rpcUrl); 49 | const tempBalance = await tempProvider.getBalance(address); 50 | const result = tempBalance && formatEther(tempBalance); 51 | if (result != 0) { 52 | console.log("Found a balance in ", n); 53 | networkSettingsHelper.updateSelectedName(n); 54 | setTargetNetwork(networkSettingsHelper.getSelectedItem(true)); 55 | break; 56 | } 57 | } catch (e) { 58 | console.log(e); 59 | } 60 | } 61 | } else { 62 | window.location.reload(true); 63 | } 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/UserProvider.js: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from "@ethersproject/providers"; 2 | import BurnerProvider from "burner-provider"; 3 | import { useMemo } from "react"; 4 | 5 | import { NETWORKS } from "../constants"; 6 | 7 | /* 8 | ~ What it does? ~ 9 | 10 | Gets user provider 11 | 12 | ~ How can I use? ~ 13 | 14 | const userProvider = useUserProvider(injectedProvider, localProvider); 15 | 16 | ~ Features ~ 17 | 18 | - Specify the injected provider from Metamask 19 | - Specify the local provider 20 | - Usage examples: 21 | const address = useUserAddress(userProvider); 22 | const tx = Transactor(userProvider, gasPrice) 23 | */ 24 | 25 | const useUserProvider = (injectedProvider, localProvider) => 26 | useMemo(() => { 27 | if (injectedProvider) { 28 | console.log("🦊 Using injected provider"); 29 | return injectedProvider; 30 | } 31 | if (!localProvider) return undefined; 32 | 33 | const burnerConfig = {}; 34 | 35 | if (window.location.pathname) { 36 | if (window.location.pathname.indexOf("/pk") >= 0) { 37 | let incomingPK = window.location.hash.replace("#", ""); 38 | 39 | if (incomingPK.startsWith("metaPrivateKey")) { 40 | incomingPK = localStorage.getItem(incomingPK); 41 | } 42 | 43 | let rawPK; 44 | if (incomingPK.length === 64 || incomingPK.length === 66) { 45 | console.log("🔑 Incoming Private Key..."); 46 | rawPK = incomingPK; 47 | burnerConfig.privateKey = rawPK; 48 | window.history.pushState({}, "", "/"); 49 | const currentPrivateKey = window.localStorage.getItem("metaPrivateKey"); 50 | if (currentPrivateKey && currentPrivateKey !== rawPK) { 51 | window.localStorage.setItem("metaPrivateKey_backup" + Date.now(), currentPrivateKey); 52 | } 53 | window.localStorage.setItem("metaPrivateKey", rawPK); 54 | } 55 | } 56 | } 57 | 58 | console.log("🔥 Using burner provider", burnerConfig); 59 | if (localProvider.connection && localProvider.connection.url) { 60 | burnerConfig.rpcUrl = localProvider.connection.url; 61 | return new Web3Provider(new BurnerProvider(burnerConfig)); 62 | } 63 | // eslint-disable-next-line no-underscore-dangle 64 | const networkName = localProvider._network && localProvider._network.name; 65 | burnerConfig.rpcUrl = NETWORKS.ethereum.rpcUrl; 66 | return new Web3Provider(new BurnerProvider(burnerConfig)); 67 | }, [injectedProvider, localProvider]); 68 | 69 | export default useUserProvider; 70 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/LocalStorage.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | // Hook from useHooks! (https://usehooks.com/useLocalStorage/) 3 | 4 | export default function useLocalStorage(key, initialValue, ttl) { 5 | 6 | // State to store our value 7 | // Pass initial state function to useState so logic is only executed once 8 | const [storedValue, setStoredValue] = useState(() => getItemFromLocalStorage(key, initialValue, ttl)); 9 | 10 | useEffect(() => { 11 | setStoredValue(getItemFromLocalStorage(key, initialValue, ttl)); 12 | }, [key]); 13 | 14 | // Return a wrapped version of useState's setter function that ... 15 | // ... persists the new value to localStorage. 16 | const setValue = value => { 17 | try { 18 | // Allow value to be a function so we have same API as useState 19 | const valueToStore = value instanceof Function ? value(storedValue) : value; 20 | // Save state 21 | setStoredValue(valueToStore); 22 | // Save to local storage 23 | if (ttl) { 24 | const now = new Date(); 25 | 26 | // `item` is an object which contains the original value 27 | // as well as the time when it's supposed to expire 28 | const item = { 29 | value: valueToStore, 30 | expiry: now.getTime() + ttl, 31 | }; 32 | window.localStorage.setItem(key, JSON.stringify(item)); 33 | } else { 34 | window.localStorage.setItem(key, JSON.stringify(valueToStore)); 35 | } 36 | } catch (error) { 37 | // A more advanced implementation would handle the error case 38 | console.log(error); 39 | } 40 | }; 41 | 42 | return [storedValue, setValue]; 43 | } 44 | 45 | const getItemFromLocalStorage = (key, initialValue, ttl) => { 46 | try { 47 | // Get from local storage by key 48 | const item = window.localStorage.getItem(key); 49 | 50 | const parsedItem = item ? JSON.parse(item) : initialValue; 51 | 52 | if (typeof parsedItem === "object" && parsedItem !== null && "expiry" in parsedItem && "value" in parsedItem) { 53 | const now = new Date(); 54 | if (ttl && now.getTime() > parsedItem.expiry) { 55 | // If the item is expired, delete the item from storage 56 | // and return null 57 | window.localStorage.removeItem(key); 58 | return initialValue; 59 | } 60 | return parsedItem.value; 61 | } 62 | // Parse stored json or if none return initialValue 63 | return parsedItem; 64 | } catch (error) { 65 | // If error also return initialValue 66 | console.log(error); 67 | return initialValue; 68 | } 69 | } -------------------------------------------------------------------------------- /docker/graph-node/Dockerfile: -------------------------------------------------------------------------------- 1 | # Full build with debuginfo for graph-node 2 | # 3 | # The expectation if that the docker build uses the parent directory as PWD 4 | # by running something like the following 5 | # docker build --target STAGE -f docker/Dockerfile . 6 | 7 | FROM rust:latest as graph-node-build 8 | 9 | ARG COMMIT_SHA=unknown 10 | ARG REPO_NAME=unknown 11 | ARG BRANCH_NAME=unknown 12 | ARG TAG_NAME=unknown 13 | 14 | ADD . /graph-node 15 | 16 | RUN cd /graph-node \ 17 | && RUSTFLAGS="-g" cargo install --locked --path node \ 18 | && cargo clean \ 19 | && objcopy --only-keep-debug /usr/local/cargo/bin/graph-node /usr/local/cargo/bin/graph-node.debug \ 20 | && strip -g /usr/local/cargo/bin/graph-node \ 21 | && cd /usr/local/cargo/bin \ 22 | && objcopy --add-gnu-debuglink=graph-node.debug graph-node \ 23 | && echo "REPO_NAME='$REPO_NAME'" > /etc/image-info \ 24 | && echo "TAG_NAME='$TAG_NAME'" >> /etc/image-info \ 25 | && echo "BRANCH_NAME='$BRANCH_NAME'" >> /etc/image-info \ 26 | && echo "COMMIT_SHA='$COMMIT_SHA'" >> /etc/image-info \ 27 | && echo "CARGO_VERSION='$(cargo --version)'" >> /etc/image-info \ 28 | && echo "RUST_VERSION='$(rustc --version)'" >> /etc/image-info 29 | 30 | # The graph-node runtime image with only the executable 31 | FROM debian:buster-slim as graph-node 32 | ENV RUST_LOG "" 33 | ENV GRAPH_LOG "" 34 | ENV EARLY_LOG_CHUNK_SIZE "" 35 | ENV ETHEREUM_RPC_PARALLEL_REQUESTS "" 36 | ENV ETHEREUM_BLOCK_CHUNK_SIZE "" 37 | 38 | ENV postgres_host "" 39 | ENV postgres_user "" 40 | ENV postgres_pass "" 41 | ENV postgres_db "" 42 | # The full URL to the IPFS node 43 | ENV ipfs "" 44 | # The etherum network(s) to connect to. Set this to a space-separated 45 | # list of the networks where each entry has the form NAME:URL 46 | ENV ethereum "" 47 | # The role the node should have, one of index-node, query-node, or 48 | # combined-node 49 | ENV node_role "combined-node" 50 | # The name of this node 51 | ENV node_id "default" 52 | 53 | # HTTP port 54 | EXPOSE 8000 55 | # WebSocket port 56 | EXPOSE 8001 57 | # JSON-RPC port 58 | EXPOSE 8020 59 | 60 | RUN apt-get update \ 61 | && apt-get install -y libpq-dev ca-certificates netcat 62 | 63 | ADD docker/wait_for docker/start /usr/local/bin/ 64 | COPY --from=graph-node-build /usr/local/cargo/bin/graph-node /usr/local/bin 65 | COPY --from=graph-node-build /etc/image-info /etc/image-info 66 | COPY docker/Dockerfile /Dockerfile 67 | CMD start 68 | 69 | # Debug image to access core dumps 70 | FROM graph-node-build as graph-node-debug 71 | RUN apt-get update \ 72 | && apt-get install -y curl gdb postgresql-client 73 | 74 | COPY docker/Dockerfile /Dockerfile 75 | COPY docker/bin/* /usr/local/bin/ 76 | -------------------------------------------------------------------------------- /docker/graph-node/README.md: -------------------------------------------------------------------------------- 1 | # Graph Node Docker Image 2 | 3 | Preconfigured Docker image for running a Graph Node. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | docker run -it \ 9 | -e postgres_host=[:] \ 10 | -e postgres_user= \ 11 | -e postgres_pass= \ 12 | -e postgres_db= \ 13 | -e ipfs=: \ 14 | -e ethereum=: \ 15 | graphprotocol/graph-node:latest 16 | ``` 17 | 18 | ### Example usage 19 | 20 | ```sh 21 | docker run -it \ 22 | -e postgres_host=host.docker.internal:5432 23 | -e postgres_user=graph-node \ 24 | -e postgres_pass=oh-hello \ 25 | -e postgres_db=graph-node \ 26 | -e ipfs=host.docker.internal:5001 \ 27 | -e ethereum=mainnet:http://localhost:8545/ \ 28 | graphprotocol/graph-node:latest 29 | ``` 30 | 31 | ## Docker Compose 32 | 33 | The Docker Compose setup requires an Ethereum network name and node 34 | to connect to. By default, it will use `mainnet:http://host.docker.internal:8545` 35 | in order to connect to an Ethereum node running on your host machine. 36 | You can replace this with anything else in `docker-compose.yaml`. 37 | 38 | > **Note for Linux users:** On Linux, `host.docker.internal` is not 39 | > currently supported. Instead, you will have to replace it with the 40 | > IP address of your Docker host (from the perspective of the Graph 41 | > Node container). 42 | > To do this, run: 43 | > 44 | > ``` 45 | > CONTAINER_ID=$(docker container ls | grep graph-node | cut -d' ' -f1) 46 | > docker exec $CONTAINER_ID /bin/bash -c 'ip route | awk \'/^default via /{print $3}\'' 47 | > ``` 48 | > 49 | > This will print the host's IP address. Then, put it into `docker-compose.yml`: 50 | > 51 | > ``` 52 | > sed -i -e 's/host.docker.internal//g' docker-compose.yml 53 | > ``` 54 | 55 | After you have set up an Ethereum node—e.g. Ganache or Parity—simply 56 | clone this repository and run 57 | 58 | ```sh 59 | docker-compose up 60 | ``` 61 | 62 | This will start IPFS, Postgres and Graph Node in Docker and create persistent 63 | data directories for IPFS and Postgres in `./data/ipfs` and `./data/postgres`. You 64 | can access these via: 65 | 66 | - Graph Node: 67 | - GraphiQL: `http://localhost:8000/` 68 | - HTTP: `http://localhost:8000/subgraphs/name/` 69 | - WebSockets: `ws://localhost:8001/subgraphs/name/` 70 | - Admin: `http://localhost:8020/` 71 | - IPFS: 72 | - `127.0.0.1:5001` or `/ip4/127.0.0.1/tcp/5001` 73 | - Postgres: 74 | - `postgresql://graph-node:let-me-in@localhost:5432/graph-node` 75 | 76 | Once this is up and running, you can use 77 | [`graph-cli`](https://github.com/graphprotocol/graph-cli) to create and 78 | deploy your subgraph to the running Graph Node. 79 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Provider.jsx: -------------------------------------------------------------------------------- 1 | import { Badge, Button } from "antd"; 2 | import { useBlockNumber, usePoller } from "eth-hooks"; 3 | import React, { useState } from "react"; 4 | // import { WalletOutlined } from '@ant-design/icons'; 5 | import Address from "./Address"; 6 | 7 | export default function Provider(props) { 8 | const [showMore, setShowMore] = useState(false); 9 | const [status, setStatus] = useState("processing"); 10 | const [network, setNetwork] = useState(); 11 | const [signer, setSigner] = useState(); 12 | const [address, setAddress] = useState(); 13 | 14 | const blockNumber = useBlockNumber(props.provider); 15 | 16 | usePoller(async () => { 17 | if (props.provider && typeof props.provider.getNetwork === "function") { 18 | try { 19 | const newNetwork = await props.provider.getNetwork(); 20 | setNetwork(newNetwork); 21 | if (newNetwork.chainId > 0) { 22 | setStatus("success"); 23 | } else { 24 | setStatus("warning"); 25 | } 26 | } catch (e) { 27 | console.log(e); 28 | setStatus("processing"); 29 | } 30 | try { 31 | const newSigner = await props.provider.getSigner(); 32 | setSigner(newSigner); 33 | const newAddress = await newSigner.getAddress(); 34 | setAddress(newAddress); 35 | // eslint-disable-next-line no-empty 36 | } catch (e) {} 37 | } 38 | }, 1377); 39 | 40 | if ( 41 | typeof props.provider === "undefined" || 42 | typeof props.provider.getNetwork !== "function" || 43 | !network || 44 | !network.chainId 45 | ) { 46 | return ( 47 | 56 | ); 57 | } 58 | 59 | let showExtra = ""; 60 | if (showMore) { 61 | showExtra = ( 62 | 63 | 64 | id: 65 | {network ? network.chainId : ""} 66 | 67 | 68 | name: 69 | {network ? network.name : ""} 70 | 71 | 72 | ); 73 | } 74 | 75 | let showWallet = ""; 76 | if (typeof signer !== "undefined" && address) { 77 | showWallet = ( 78 | 79 | 80 |
81 | 82 | 83 | ); 84 | } 85 | 86 | return ( 87 | 96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractLoader.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | /* eslint-disable global-require */ 3 | import { Contract } from "@ethersproject/contracts"; 4 | import { useEffect, useState } from "react"; 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Loads your local contracts and gives options to read values from contracts 10 | or write transactions into them 11 | 12 | ~ How can I use? ~ 13 | 14 | const readContracts = useContractLoader(localProvider) // or 15 | const writeContracts = useContractLoader(userProvider) 16 | 17 | ~ Features ~ 18 | 19 | - localProvider enables reading values from contracts 20 | - userProvider enables writing transactions into contracts 21 | - Example of keeping track of "purpose" variable by loading contracts into readContracts 22 | and using ContractReader.js hook: 23 | const purpose = useContractReader(readContracts,"YourContract", "purpose") 24 | - Example of using setPurpose function from our contract and writing transactions by Transactor.js helper: 25 | tx( writeContracts.YourContract.setPurpose(newPurpose) ) 26 | */ 27 | 28 | const loadContract = (contractName, signer) => { 29 | const newContract = new Contract( 30 | require(`../contracts/${contractName}.address.js`), 31 | require(`../contracts/${contractName}.abi.js`), 32 | signer, 33 | ); 34 | try { 35 | newContract.bytecode = require(`../contracts/${contractName}.bytecode.js`); 36 | } catch (e) { 37 | console.log(e); 38 | } 39 | return newContract; 40 | }; 41 | 42 | export default function useContractLoader(providerOrSigner) { 43 | const [contracts, setContracts] = useState(); 44 | useEffect(() => { 45 | async function loadContracts() { 46 | if (typeof providerOrSigner !== "undefined") { 47 | try { 48 | // we need to check to see if this providerOrSigner has a signer or not 49 | let signer; 50 | let accounts; 51 | if (providerOrSigner && typeof providerOrSigner.listAccounts === "function") { 52 | accounts = await providerOrSigner.listAccounts(); 53 | } 54 | 55 | if (accounts && accounts.length > 0) { 56 | signer = providerOrSigner.getSigner(); 57 | } else { 58 | signer = providerOrSigner; 59 | } 60 | 61 | const contractList = require("../contracts/contracts.js"); 62 | 63 | const newContracts = contractList.reduce((accumulator, contractName) => { 64 | accumulator[contractName] = loadContract(contractName, signer); 65 | return accumulator; 66 | }, {}); 67 | setContracts(newContracts); 68 | } catch (e) { 69 | console.log("ERROR LOADING CONTRACTS!!", e); 70 | } 71 | } 72 | } 73 | loadContracts(); 74 | }, [providerOrSigner]); 75 | return contracts; 76 | } 77 | -------------------------------------------------------------------------------- /packages/react-app/src/hooks/ContractReader.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import useOnBlock from "./OnBlock"; 3 | import usePoller from "./Poller"; 4 | 5 | const DEBUG = false; 6 | 7 | /* 8 | ~ What it does? ~ 9 | 10 | Enables you to read values from contracts and keep track of them in the local React states 11 | 12 | ~ How can I use? ~ 13 | 14 | const purpose = useContractReader(readContracts,"YourContract", "purpose") 15 | 16 | ~ Features ~ 17 | 18 | - Provide readContracts by loading contracts (see more on ContractLoader.js) 19 | - Specify the name of the contract, in this case it is "YourContract" 20 | - Specify the name of the variable in the contract, in this case we keep track of "purpose" variable 21 | - Pass an args array if the function requires 22 | - Pass pollTime - if no pollTime is specified, the function will update on every new block 23 | */ 24 | 25 | export default function useContractReader(contracts, contractName, functionName, args, pollTime, formatter, onChange) { 26 | let adjustPollTime = 0; 27 | if (pollTime) { 28 | adjustPollTime = pollTime; 29 | } else if (!pollTime && typeof args === "number") { 30 | // it's okay to pass poll time as last argument without args for the call 31 | adjustPollTime = args; 32 | } 33 | 34 | const [value, setValue] = useState(); 35 | useEffect(() => { 36 | if (typeof onChange === "function") { 37 | setTimeout(onChange.bind(this, value), 1); 38 | } 39 | }, [value, onChange]); 40 | 41 | const updateValue = async () => { 42 | try { 43 | let newValue; 44 | if (DEBUG) console.log("CALLING ", contractName, functionName, "with args", args); 45 | if (args && args.length > 0) { 46 | newValue = await contracts[contractName][functionName](...args); 47 | if (DEBUG) 48 | console.log("contractName", contractName, "functionName", functionName, "args", args, "RESULT:", newValue); 49 | } else { 50 | newValue = await contracts[contractName][functionName](); 51 | } 52 | if (formatter && typeof formatter === "function") { 53 | newValue = formatter(newValue); 54 | } 55 | // console.log("GOT VALUE",newValue) 56 | if (newValue !== value) { 57 | setValue(newValue); 58 | } 59 | } catch (e) { 60 | console.log(e); 61 | } 62 | }; 63 | 64 | // Only pass a provider to watch on a block if we have a contract and no PollTime 65 | useOnBlock(contracts && contracts[contractName] && adjustPollTime === 0 && contracts[contractName].provider, () => { 66 | if (contracts && contracts[contractName] && adjustPollTime === 0) { 67 | updateValue(); 68 | } 69 | }); 70 | 71 | // Use a poller if a pollTime is provided 72 | usePoller( 73 | async () => { 74 | if (contracts && contracts[contractName] && adjustPollTime > 0) { 75 | if (DEBUG) console.log("polling!", contractName, functionName); 76 | updateValue(); 77 | } 78 | }, 79 | adjustPollTime, 80 | contracts && contracts[contractName], 81 | ); 82 | 83 | return value; 84 | } 85 | -------------------------------------------------------------------------------- /packages/react-app/scripts/ipfs.js: -------------------------------------------------------------------------------- 1 | const ipfsAPI = require("ipfs-http-client"); 2 | const chalk = require("chalk"); 3 | const { clearLine } = require("readline"); 4 | 5 | const { globSource } = ipfsAPI; 6 | 7 | const infura = { host: "ipfs.infura.io", port: "5001", protocol: "https" }; 8 | // run your own ipfs daemon: https://docs.ipfs.io/how-to/command-line-quick-start/#install-ipfs 9 | // const localhost = { host: "localhost", port: "5001", protocol: "http" }; 10 | 11 | const ipfs = ipfsAPI(infura); 12 | 13 | const ipfsGateway = "https://ipfs.io/ipfs/"; 14 | 15 | const addOptions = { 16 | pin: true, 17 | }; 18 | 19 | const pushDirectoryToIPFS = async path => { 20 | try { 21 | const response = await ipfs.add(globSource(path, { recursive: true }), addOptions); 22 | return response; 23 | } catch (e) { 24 | return {}; 25 | } 26 | }; 27 | 28 | const publishHashToIPNS = async ipfsHash => { 29 | try { 30 | const response = await ipfs.name.publish(`/ipfs/${ipfsHash}`); 31 | return response; 32 | } catch (e) { 33 | return {}; 34 | } 35 | }; 36 | 37 | const nodeMayAllowPublish = ipfsClient => { 38 | // You must have your own IPFS node in order to publish an IPNS name 39 | // This contains a blacklist of known nodes which do not allow users to publish IPNS names. 40 | const nonPublishingNodes = ["ipfs.infura.io"]; 41 | const { host } = ipfsClient.getEndpointConfig(); 42 | return !nonPublishingNodes.some(nodeUrl => host.includes(nodeUrl)); 43 | }; 44 | 45 | const deploy = async () => { 46 | console.log("🛰 Sending to IPFS..."); 47 | const { cid } = await pushDirectoryToIPFS("./build"); 48 | if (!cid) { 49 | console.log(`📡 App deployment failed`); 50 | return false; 51 | } 52 | console.log(`📡 App deployed to IPFS with hash: ${chalk.cyan(cid.toString())}`); 53 | 54 | console.log(); 55 | 56 | let ipnsName = ""; 57 | if (nodeMayAllowPublish(ipfs)) { 58 | console.log(`✍️ Publishing /ipfs/${cid.toString()} to IPNS...`); 59 | process.stdout.write(" Publishing to IPNS can take up to roughly two minutes.\r"); 60 | ipnsName = (await publishHashToIPNS(cid.toString())).name; 61 | clearLine(process.stdout, 0); 62 | if (!ipnsName) { 63 | console.log(" Publishing IPNS name on node failed."); 64 | } 65 | console.log(`🔖 App published to IPNS with name: ${chalk.cyan(ipnsName)}`); 66 | console.log(); 67 | } 68 | 69 | console.log("🚀 Deployment to IPFS complete!"); 70 | console.log(); 71 | 72 | console.log(`Use the link${ipnsName && "s"} below to access your app:`); 73 | console.log(` IPFS: ${chalk.cyan(`${ipfsGateway}${cid.toString()}`)}`); 74 | if (ipnsName) { 75 | console.log(` IPNS: ${chalk.cyan(`${ipfsGateway}${ipnsName}`)}`); 76 | console.log(); 77 | console.log( 78 | "Each new deployment will have a unique IPFS hash while the IPNS name will always point at the most recent deployment.", 79 | ); 80 | console.log( 81 | "It is recommended that you share the IPNS link so that people always see the newest version of your app.", 82 | ); 83 | } 84 | console.log(); 85 | return true; 86 | }; 87 | 88 | deploy(); 89 | -------------------------------------------------------------------------------- /packages/react-app/src/components/TokenDetailedDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { Button, message } from "antd"; 4 | import { CopyOutlined, DeleteOutlined } from "@ant-design/icons"; 5 | 6 | import { copy } from "../helpers/EditorHelper"; 7 | 8 | export default function TokenDetailedDisplay({ 9 | tokenSettingsHelper, 10 | token, 11 | tokenCoreDisplay, 12 | network, 13 | setItemDetailed, 14 | }) { 15 | const [addressCopied, setAddressCopied] = useState(false); 16 | 17 | const tokenLink = network.blockExplorer + "token/" + token.address; 18 | 19 | return ( 20 | <> 21 |
22 | {tokenCoreDisplay && tokenCoreDisplay(token)} 23 |
24 | 25 |
30 | {token.hasOwnProperty("address") ? ( 31 |
32 |
window.open(tokenLink, "_blank")} 40 | > 41 | {token.address} 42 |
43 | 46 | copy(token.address, () => 47 | message.success(Copied Contract Address), 48 | ) 49 | } 50 | /> 51 |
52 | ) : ( 53 |
Native Token
54 | )} 55 |
56 | 57 | {setItemDetailed && ( 58 | <> 59 | {tokenSettingsHelper.isCustomItem(token) && ( 60 | <> 61 |
62 | 74 |
75 | 76 |
77 | {tokenSettingsHelper.isItemsTheSame(token, tokenSettingsHelper.getSelectedItem()) && ( 78 |
Cannot remove the selected token!
79 | )} 80 |
81 | 82 | )} 83 | 84 | )} 85 | 86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /docker/graph-node/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | save_coredumps() { 4 | graph_dir=/var/lib/graph 5 | datestamp=$(date +"%Y-%m-%dT%H:%M:%S") 6 | ls /core.* >& /dev/null && have_cores=yes || have_cores=no 7 | if [ -d "$graph_dir" -a "$have_cores" = yes ] 8 | then 9 | core_dir=$graph_dir/cores 10 | mkdir -p $core_dir 11 | exec >> "$core_dir"/messages 2>&1 12 | echo "${HOSTNAME##*-} Saving core dump on ${HOSTNAME} at ${datestamp}" 13 | 14 | dst="$core_dir/$datestamp-${HOSTNAME}" 15 | mkdir "$dst" 16 | cp /usr/local/bin/graph-node "$dst" 17 | cp /proc/loadavg "$dst" 18 | [ -f /Dockerfile ] && cp /Dockerfile "$dst" 19 | tar czf "$dst/etc.tgz" /etc/ 20 | dmesg -e > "$dst/dmesg" 21 | # Capture environment variables, but filter out passwords 22 | env | sort | sed -r -e 's/^(postgres_pass|ELASTICSEARCH_PASSWORD)=.*$/\1=REDACTED/' > "$dst/env" 23 | 24 | for f in /core.* 25 | do 26 | echo "${HOSTNAME##*-} Found core dump $f" 27 | mv "$f" "$dst" 28 | done 29 | echo "${HOSTNAME##*-} Saving done" 30 | fi 31 | } 32 | 33 | wait_for_ipfs() { 34 | # Take the IPFS URL in $1 apart and extract host and port. If no explicit 35 | # host is given, use 443 for https, and 80 otherwise 36 | if [[ "$1" =~ ^((https?)://)?([^:/]+)(:([0-9]+))? ]] 37 | then 38 | proto=${BASH_REMATCH[2]:-http} 39 | host=${BASH_REMATCH[3]} 40 | port=${BASH_REMATCH[5]} 41 | if [ -z "$port" ] 42 | then 43 | [ "$proto" = "https" ] && port=443 || port=80 44 | fi 45 | wait_for "$host:$port" -t 120 46 | else 47 | echo "invalid IPFS URL: $1" 48 | exit 1 49 | fi 50 | } 51 | 52 | start_query_node() { 53 | export DISABLE_BLOCK_INGESTOR=true 54 | graph-node \ 55 | --postgres-url "$postgres_url" \ 56 | --ethereum-rpc $ethereum \ 57 | --ipfs "$ipfs" 58 | } 59 | 60 | start_index_node() { 61 | # Only the index node with the name set in BLOCK_INGESTOR should ingest 62 | # blocks 63 | if [[ ${node_id} != "${BLOCK_INGESTOR}" ]]; then 64 | export DISABLE_BLOCK_INGESTOR=true 65 | fi 66 | 67 | graph-node \ 68 | --node-id "${node_id//-/_}" \ 69 | --postgres-url "$postgres_url" \ 70 | --ethereum-rpc $ethereum \ 71 | --ipfs "$ipfs" 72 | } 73 | 74 | start_combined_node() { 75 | graph-node \ 76 | --postgres-url "$postgres_url" \ 77 | --ethereum-rpc $ethereum \ 78 | --ipfs "$ipfs" 79 | } 80 | 81 | postgres_url="postgresql://$postgres_user:$postgres_pass@$postgres_host/$postgres_db" 82 | 83 | wait_for_ipfs "$ipfs" 84 | wait_for "$postgres_host:5432" -t 120 85 | sleep 5 86 | 87 | trap save_coredumps EXIT 88 | 89 | export PGAPPNAME="${node_id-$HOSTNAME}" 90 | 91 | case "${node_role-combined-node}" in 92 | query-node) 93 | start_query_node 94 | ;; 95 | index-node) 96 | start_index_node 97 | ;; 98 | combined-node) 99 | start_combined_node 100 | ;; 101 | *) 102 | echo "Unknown mode for start-node: $1" 103 | echo "usage: start (combined-node|query-node|index-node)" 104 | exit 1 105 | esac 106 | -------------------------------------------------------------------------------- /packages/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/react-app", 3 | "version": "1.0.0", 4 | "homepage": ".", 5 | "browserslist": [ 6 | ">0.2%", 7 | "not dead", 8 | "not op_mini all" 9 | ], 10 | "dependencies": { 11 | "@ant-design/icons": "^4.2.2", 12 | "@apollo/react-hooks": "^4.0.0", 13 | "@monerium/sdk": "^2.10.0", 14 | "@ramp-network/ramp-instant-sdk": "^2.2.0", 15 | "@testing-library/jest-dom": "^5.11.4", 16 | "@testing-library/react": "^11.1.0", 17 | "@testing-library/user-event": "^12.1.8", 18 | "@uniswap/sdk": "^3.0.3", 19 | "@uniswap/v2-periphery": "^1.1.0-beta.0", 20 | "@walletconnect/utils": "^2.7.2", 21 | "@walletconnect/web3-provider": "^1.6.5", 22 | "@walletconnect/web3wallet": "^1.7.0", 23 | "antd": "^4.7.0", 24 | "apollo-boost": "^0.4.9", 25 | "apollo-client": "^2.6.10", 26 | "apollo-utilities": "^1.3.4", 27 | "axios": "^0.20.0", 28 | "bignumber.js": "^9.1.2", 29 | "bnc-notify": "^1.5.0", 30 | "burner-provider": "^1.0.38", 31 | "dotenv": "^8.2.0", 32 | "eth-hooks": "^1.1.2", 33 | "eth-url-parser": "^1.0.4", 34 | "ethers": "^5.7.2", 35 | "graphiql": "^1.0.5", 36 | "graphql": "^15.3.0", 37 | "iban": "^0.0.14", 38 | "ibantools": "^4.3.4", 39 | "isomorphic-fetch": "^3.0.0", 40 | "moment": "^2.29.4", 41 | "node-watch": "^0.7.1", 42 | "postcss": "^8.2.6", 43 | "qrcode.react": "^1.0.0", 44 | "react": "^16.14.0", 45 | "react-blockies": "^1.4.1", 46 | "react-css-theme-switcher": "^0.2.2", 47 | "react-dom": "^16.14.0", 48 | "react-infinite-scroll-component": "^6.1.0", 49 | "react-qr-reader-es6": "2.2.1-2", 50 | "react-router-dom": "^5.2.0", 51 | "react-scripts": "^3.4.3", 52 | "typescript": "^4.9.4", 53 | "web3modal": "^1.9.11", 54 | "zksync-web3": "^0.14.3" 55 | }, 56 | "devDependencies": { 57 | "@testing-library/dom": "^6.12.2", 58 | "@types/react": "^16.9.19", 59 | "autoprefixer": "^10.2.4", 60 | "chalk": "^4.1.0", 61 | "eslint": "^7.5.0", 62 | "eslint-config-airbnb": "^18.2.0", 63 | "eslint-config-prettier": "^6.11.0", 64 | "eslint-plugin-babel": "^5.3.1", 65 | "eslint-plugin-import": "^2.22.1", 66 | "eslint-plugin-jsx-a11y": "^6.4.1", 67 | "eslint-plugin-prettier": "^3.1.4", 68 | "eslint-plugin-react": "^7.22.0", 69 | "eslint-plugin-react-hooks": "^4.2.0", 70 | "gulp": "^4.0.2", 71 | "gulp-csso": "^4.0.1", 72 | "gulp-debug": "^4.0.0", 73 | "gulp-less": "^4.0.1", 74 | "gulp-postcss": "^9.0.0", 75 | "ipfs-http-client": "^45.0.0", 76 | "less-plugin-npm-import": "^2.1.0", 77 | "prettier": "^2.0.5", 78 | "s3-folder-upload": "^2.3.1", 79 | "surge": "^0.21.5" 80 | }, 81 | "eslintConfig": { 82 | "extends": "react-app" 83 | }, 84 | "scripts": { 85 | "build": "react-scripts --openssl-legacy-provider build", 86 | "eject": "react-scripts eject", 87 | "start": "react-scripts --openssl-legacy-provider start", 88 | "test": "react-scripts test", 89 | "lint": "eslint --config ./.eslintrc.js --ignore-path ./.eslintignore ./src/**/*", 90 | "ipfs": "node ./scripts/ipfs.js", 91 | "surge": "surge ./build", 92 | "s3": "node ./scripts/s3.js", 93 | "ship": "yarn surge", 94 | "theme": "npx gulp less", 95 | "watch": "node ./scripts/watch.js" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/publish.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const chalk = require("chalk"); 3 | const bre = require("hardhat"); 4 | 5 | const publishDir = "../react-app/src/contracts"; 6 | const graphDir = "../subgraph" 7 | 8 | function publishContract(contractName) { 9 | console.log( 10 | " 💽 Publishing", 11 | chalk.cyan(contractName), 12 | "to", 13 | chalk.gray(publishDir) 14 | ); 15 | try { 16 | let contract = fs 17 | .readFileSync(`${bre.config.paths.artifacts}/contracts/${contractName}.sol/${contractName}.json`) 18 | .toString(); 19 | const address = fs 20 | .readFileSync(`${bre.config.paths.artifacts}/${contractName}.address`) 21 | .toString(); 22 | contract = JSON.parse(contract); 23 | let graphConfigPath = `${graphDir}/config/config.json` 24 | let graphConfig 25 | try { 26 | if (fs.existsSync(graphConfigPath)) { 27 | graphConfig = fs 28 | .readFileSync(graphConfigPath) 29 | .toString(); 30 | } else { 31 | graphConfig = '{}' 32 | } 33 | } catch (e) { 34 | console.log(e) 35 | } 36 | 37 | graphConfig = JSON.parse(graphConfig) 38 | graphConfig[contractName + "Address"] = address 39 | fs.writeFileSync( 40 | `${publishDir}/${contractName}.address.js`, 41 | `module.exports = "${address}";` 42 | ); 43 | fs.writeFileSync( 44 | `${publishDir}/${contractName}.abi.js`, 45 | `module.exports = ${JSON.stringify(contract.abi, null, 2)};` 46 | ); 47 | fs.writeFileSync( 48 | `${publishDir}/${contractName}.bytecode.js`, 49 | `module.exports = "${contract.bytecode}";` 50 | ); 51 | 52 | const folderPath = graphConfigPath.replace("/config.json","") 53 | if (!fs.existsSync(folderPath)){ 54 | fs.mkdirSync(folderPath); 55 | } 56 | fs.writeFileSync( 57 | graphConfigPath, 58 | JSON.stringify(graphConfig, null, 2) 59 | ); 60 | fs.writeFileSync( 61 | `${graphDir}/abis/${contractName}.json`, 62 | JSON.stringify(contract.abi, null, 2) 63 | ); 64 | 65 | console.log(" 📠 Published "+chalk.green(contractName)+" to the frontend.") 66 | 67 | return true; 68 | } catch (e) { 69 | if(e.toString().indexOf("no such file or directory")>=0){ 70 | console.log(chalk.yellow(" ⚠️ Can't publish "+contractName+" yet (make sure it getting deployed).")) 71 | }else{ 72 | console.log(e); 73 | return false; 74 | } 75 | } 76 | } 77 | 78 | async function main() { 79 | if (!fs.existsSync(publishDir)) { 80 | fs.mkdirSync(publishDir); 81 | } 82 | const finalContractList = []; 83 | fs.readdirSync(bre.config.paths.sources).forEach((file) => { 84 | if (file.indexOf(".sol") >= 0) { 85 | const contractName = file.replace(".sol", ""); 86 | // Add contract to list if publishing is successful 87 | if (publishContract(contractName)) { 88 | finalContractList.push(contractName); 89 | } 90 | } 91 | }); 92 | fs.writeFileSync( 93 | `${publishDir}/contracts.js`, 94 | `module.exports = ${JSON.stringify(finalContractList)};` 95 | ); 96 | } 97 | main() 98 | .then(() => process.exit(0)) 99 | .catch((error) => { 100 | console.error(error); 101 | process.exit(1); 102 | }); 103 | -------------------------------------------------------------------------------- /packages/react-app/src/components/CustomRPC.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Button, Input, Spin } from "antd"; 4 | import { ApiOutlined, DeleteOutlined, DisconnectOutlined } from "@ant-design/icons"; 5 | 6 | import { PasteButton } from "."; 7 | 8 | import { getShortRPC, CUSTOM_RPC_KEY } from "../helpers/NetworkSettingsHelper"; 9 | 10 | export default function CustomRPC({ 11 | network, 12 | userValue, 13 | setUserValue, 14 | validRPC, 15 | setValidRPC, 16 | loading, 17 | storedRPC, 18 | networkSettingsHelper, 19 | setTargetNetwork, 20 | }) { 21 | return ( 22 | <> 23 |
24 |
25 | { 29 | setUserValue(e.target.value); 30 | }} 31 | disabled={storedRPC} 32 | /> 33 |
34 |
35 | 36 |
37 |
38 |
39 | 50 |
51 | 52 | ); 53 | } 54 | 55 | const RPC = ({ 56 | network, 57 | rpc, 58 | setUserValue, 59 | validRPC, 60 | setValidRPC, 61 | loading, 62 | storedRPC, 63 | networkSettingsHelper, 64 | setTargetNetwork, 65 | }) => { 66 | if (loading) { 67 | return ; 68 | } 69 | 70 | if (validRPC === undefined) { 71 | return <>; 72 | } 73 | 74 | return ( 75 |
76 |
{validRPC ? getShortRPC(rpc) : "Invalid RPC"}
77 | 78 | {validRPC ? : } 79 | 80 | {(validRPC || (!validRPC && storedRPC)) && ( 81 | 88 | )} 89 |
90 | ); 91 | }; 92 | 93 | const RemoveButton = ({ networkSettingsHelper, network, setTargetNetwork, setUserValue, setValidRPC }) => { 94 | return ( 95 |
96 | 107 |
108 | ); 109 | }; 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@scaffold-eth/monorepo", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "ethereum", 6 | "react", 7 | "uniswap", 8 | "workspaces", 9 | "yarn" 10 | ], 11 | "private": true, 12 | "scripts": { 13 | "react-app:build": "yarn workspace @scaffold-eth/react-app build --max-old-space-size=12288", 14 | "react-app:eject": "yarn workspace @scaffold-eth/react-app eject", 15 | "react-app:start": "yarn workspace @scaffold-eth/react-app start", 16 | "react-app:test": "yarn workspace @scaffold-eth/react-app test", 17 | "build": "yarn workspace @scaffold-eth/react-app build --max-old-space-size=12288", 18 | "chain": "yarn workspace @scaffold-eth/hardhat chain", 19 | "fork": "yarn workspace @scaffold-eth/hardhat fork", 20 | "node": "yarn workspace @scaffold-eth/hardhat chain", 21 | "test": "yarn workspace @scaffold-eth/hardhat test", 22 | "start": "yarn workspace @scaffold-eth/react-app start", 23 | "compile": "yarn workspace @scaffold-eth/hardhat compile", 24 | "deploy": "yarn workspace @scaffold-eth/hardhat deploy", 25 | "watch": "yarn workspace @scaffold-eth/hardhat watch", 26 | "accounts": "yarn workspace @scaffold-eth/hardhat accounts", 27 | "balance": "yarn workspace @scaffold-eth/hardhat balance", 28 | "send": "yarn workspace @scaffold-eth/hardhat send", 29 | "ipfs": "yarn workspace @scaffold-eth/react-app ipfs", 30 | "surge": "yarn workspace @scaffold-eth/react-app surge", 31 | "s3": "yarn workspace @scaffold-eth/react-app s3", 32 | "ship": "yarn workspace @scaffold-eth/react-app ship", 33 | "generate": "yarn workspace @scaffold-eth/hardhat generate", 34 | "account": "yarn workspace @scaffold-eth/hardhat account", 35 | "mineContractAddress": "cd packages/hardhat && npx hardhat mineContractAddress", 36 | "wallet": "cd packages/hardhat && npx hardhat wallet", 37 | "fundedwallet": "cd packages/hardhat && npx hardhat fundedwallet", 38 | "flatten": "cd packages/hardhat && npx hardhat flatten", 39 | "graph-run-node": "cd docker/graph-node && docker-compose up", 40 | "graph-remove-node": "cd docker/graph-node && docker-compose down", 41 | "graph-prepare": "mustache packages/subgraph/config/config.json packages/subgraph/src/subgraph.template.yaml > packages/subgraph/subgraph.yaml", 42 | "graph-codegen": "yarn workspace @scaffold-eth/subgraph graph codegen", 43 | "graph-build": "yarn workspace @scaffold-eth/subgraph graph build", 44 | "graph-create-local": "yarn workspace @scaffold-eth/subgraph graph create --node http://localhost:8020/ scaffold-eth/your-contract", 45 | "graph-remove-local": "yarn workspace @scaffold-eth/subgraph graph remove --node http://localhost:8020/ scaffold-eth/your-contract", 46 | "graph-deploy-local": "yarn workspace @scaffold-eth/subgraph graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 scaffold-eth/your-contract", 47 | "graph-ship-local": "yarn graph-prepare && yarn graph-codegen && yarn graph-deploy-local", 48 | "deploy-and-graph": "yarn deploy && yarn graph-ship-local", 49 | "theme": "yarn workspace @scaffold-eth/react-app theme", 50 | "watch-theme": "yarn workspace @scaffold-eth/react-app watch" 51 | }, 52 | "workspaces": { 53 | "packages": [ 54 | "packages/*" 55 | ], 56 | "nohoist": [ 57 | "**/@graphprotocol/graph-ts", 58 | "**/@graphprotocol/graph-ts/**", 59 | "**/hardhat", 60 | "**/hardhat/**" 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/react-app/src/components/BytesStringInput.jsx: -------------------------------------------------------------------------------- 1 | import { Input } from "antd"; 2 | import React, { useEffect, useState } from "react"; 3 | 4 | const { utils, constants } = require("ethers"); 5 | 6 | /* 7 | ~ What it does? ~ 8 | 9 | Displays input field with options to convert between STRING and BYTES32 10 | 11 | ~ How can I use? ~ 12 | 13 | { 18 | setValue(value); 19 | }} 20 | /> 21 | 22 | ~ Features ~ 23 | 24 | - Provide value={value} to specify initial string 25 | - Provide placeholder="Enter value..." value for the input 26 | - Control input change by onChange={value => { setValue(value);}} 27 | 28 | */ 29 | 30 | export default function BytesStringInput(props) { 31 | const [mode, setMode] = useState("STRING"); 32 | const [display, setDisplay] = useState(); 33 | const [value, setValue] = useState(constants.HashZero); 34 | 35 | // current value is the value in bytes32 36 | const currentValue = typeof props.value !== "undefined" ? props.value : value; 37 | 38 | const option = title => { 39 | return ( 40 |
{ 43 | if (mode === "STRING") { 44 | setMode("BYTES32"); 45 | if (!utils.isHexString(currentValue)) { 46 | /* in case user enters invalid bytes32 number, 47 | it considers it as string and converts to bytes32 */ 48 | const changedValue = utils.formatBytes32String(currentValue); 49 | setDisplay(changedValue); 50 | } else { 51 | setDisplay(currentValue); 52 | } 53 | } else { 54 | setMode("STRING"); 55 | if (currentValue && utils.isHexString(currentValue)) { 56 | setDisplay(utils.parseBytes32String(currentValue)); 57 | } else { 58 | setDisplay(currentValue); 59 | } 60 | } 61 | }} 62 | > 63 | {title} 64 |
65 | ); 66 | }; 67 | 68 | let addonAfter; 69 | if (mode === "STRING") { 70 | addonAfter = option("STRING 🔀"); 71 | } else { 72 | addonAfter = option("BYTES32 🔀"); 73 | } 74 | 75 | useEffect(() => { 76 | if (!currentValue) { 77 | setDisplay(""); 78 | } 79 | }, [currentValue]); 80 | 81 | return ( 82 | { 88 | const newValue = e.target.value; 89 | if (mode === "STRING") { 90 | // const ethValue = parseFloat(newValue) / props.price; 91 | // setValue(ethValue); 92 | if (typeof props.onChange === "function") { 93 | props.onChange(utils.formatBytes32String(newValue)); 94 | } 95 | setValue(utils.formatBytes32String(newValue)); 96 | setDisplay(newValue); 97 | } else { 98 | if (typeof props.onChange === "function") { 99 | props.onChange(newValue); 100 | } 101 | setValue(newValue); 102 | setDisplay(newValue); 103 | } 104 | }} 105 | /> 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /packages/react-app/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Account } from "./Account"; 2 | export { default as Address } from "./Address"; 3 | export { default as AddressInput } from "./AddressInput"; 4 | export { default as AmountDollarSwitch } from "./AmountDollarSwitch"; 5 | export { default as Balance } from "./Balance"; 6 | export { default as Blockie } from "./Blockie"; 7 | export { default as BytesStringInput } from "./BytesStringInput"; 8 | export { default as CustomRPC } from "./CustomRPC"; 9 | export { default as ERC20Balance } from "./ERC20Balance"; 10 | export { default as ERC20Input } from "./ERC20Input"; 11 | export { default as Contract } from "./Contract"; 12 | export { default as EtherInput } from "./EtherInput"; 13 | export { default as Faucet } from "./Faucet"; 14 | export { default as GasGauge } from "./GasGauge"; 15 | export { default as Header } from "./Header"; 16 | export { default as IFrame } from "./IFrame"; 17 | export { default as LogoOnLogo } from "./LogoOnLogo"; 18 | export { default as Monerium } from "./Monerium"; 19 | export { default as MoneriumBalances } from "./MoneriumBalances"; 20 | export { default as MoneriumCrossChainAddressSelector } from "./MoneriumCrossChainAddressSelector"; 21 | export { default as MoneriumDescription } from "./MoneriumDescription"; 22 | export { default as MoneriumHeader } from "./MoneriumHeader"; 23 | export { default as MoneriumIban } from "./MoneriumIban"; 24 | export { default as MoneriumOnChainCrossChainRadio } from "./MoneriumOnChainCrossChainRadio"; 25 | export { default as MoneriumPunkNotConnected } from "./MoneriumPunkNotConnected"; 26 | export { default as NetworkDetailedDisplay } from "./NetworkDetailedDisplay"; 27 | export { default as NetworkDisplay } from "./NetworkDisplay"; 28 | export { default as PasteButton } from "./PasteButton"; 29 | export { default as Provider } from "./Provider"; 30 | export { default as Punk } from "./Punk"; 31 | export { default as PunkBlockie } from "./PunkBlockie"; 32 | export { default as QRPunkBlockie } from "./QRPunkBlockie"; 33 | export { default as Ramp } from "./Ramp"; 34 | export { default as Reload } from "./Reload"; 35 | export { default as SelectorWithSettings } from "./SelectorWithSettings"; 36 | export { default as SettingsModal } from "./SettingsModal"; 37 | export { default as Swap } from "./Swap"; 38 | export { default as ThemeSwitch } from "./ThemeSwitch"; 39 | export { default as Timeline } from "./Timeline"; 40 | export { default as TokenDetailedDisplay } from "./TokenDetailedDisplay"; 41 | export { default as TokenBalance } from "./TokenBalance"; 42 | export { default as TokenDisplay } from "./TokenDisplay"; 43 | export { default as TokenImportDisplay } from "./TokenImportDisplay"; 44 | export { default as TransactionDisplay } from "./TransactionDisplay"; 45 | export { default as TransactionHistory } from "./TransactionHistory"; 46 | export { default as TransactionResponseDisplay } from "./TransactionResponseDisplay"; 47 | export { default as TransactionResponses } from "./TransactionResponses"; 48 | export { default as Wallet } from "./Wallet"; 49 | export { default as WalletImport } from "./WalletImport"; 50 | export { default as WalletConnectActiveSessions } from "./WalletConnectActiveSessions"; 51 | export { default as WalletConnectTransactionDisplay } from "./WalletConnectTransactionDisplay"; 52 | export { default as WalletConnectTransactionPopUp } from "./WalletConnectTransactionPopUp"; 53 | export { default as WalletConnectV2ConnectionError } from "./WalletConnectV2ConnectionError"; 54 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/NetworkSettingsHelper.js: -------------------------------------------------------------------------------- 1 | import { StaticJsonRpcProvider } from "@ethersproject/providers"; 2 | 3 | import { getBLockExplorer, getChain } from "./ChainHelper"; 4 | 5 | export const NETWORK_SETTINGS_STORAGE_KEY = "networkSettings"; 6 | 7 | export const SELECTED_BLOCK_EXPORER_NAME_KEY = "selectedBlockExplorerName"; 8 | export const SELECTED_BLOCK_EXPORER_URL_KEY = "selectedBlockExplorerURL"; 9 | export const CUSTOM_RPC_KEY = "customRPC"; 10 | 11 | export const getNetworkWithSettings = (network, networkSettings) => { 12 | const networkWithSettings = {}; 13 | 14 | const selectedBlockExplorerURL = networkSettings[SELECTED_BLOCK_EXPORER_URL_KEY]; 15 | 16 | if (selectedBlockExplorerURL) { 17 | networkWithSettings.blockExplorer = selectedBlockExplorerURL; 18 | } 19 | 20 | const customRPC = networkSettings[CUSTOM_RPC_KEY]; 21 | 22 | if (customRPC) { 23 | networkWithSettings.rpcUrl = customRPC; 24 | } 25 | 26 | return { ...network, ...networkWithSettings }; 27 | }; 28 | 29 | export const getShortRPC = rpc => { 30 | const RPC_URL_BEGINNING = "://"; 31 | 32 | try { 33 | let startIndex = rpc.indexOf(RPC_URL_BEGINNING); 34 | 35 | if (startIndex === -1) { 36 | startIndex = 0; 37 | } else { 38 | startIndex = startIndex + RPC_URL_BEGINNING.length; 39 | } 40 | 41 | let endIndex = rpc.indexOf("/", startIndex); 42 | 43 | if (endIndex === -1) { 44 | endIndex = rpc.length; 45 | } 46 | 47 | return rpc.substring(startIndex, endIndex); 48 | } catch (error) { 49 | console.error("Couldn't get short RPC", error); 50 | 51 | return rpc; 52 | } 53 | }; 54 | 55 | export const migrateSelectedNetworkStorageSetting = networkSettingsHelper => { 56 | // Old code 57 | // const cachedNetwork = window.localStorage.getItem("network"); 58 | 59 | try { 60 | const oldKey = "network"; 61 | 62 | const oldValue = localStorage.getItem(oldKey); 63 | 64 | if (!oldValue) { 65 | return; 66 | } 67 | 68 | localStorage.removeItem(oldKey); 69 | 70 | networkSettingsHelper.updateSelectedName(oldValue); 71 | } catch (error) { 72 | console.error("Coudn't migrate selected token name setting", error); 73 | } 74 | }; 75 | 76 | export const validateRPC = async ( 77 | setLoading, 78 | network, 79 | rpc, 80 | currentPunkAddress, 81 | networkSettingsHelper, 82 | setTargetNetwork, 83 | setValidRPC, 84 | ) => { 85 | setLoading(true); 86 | 87 | const validRPC = await isValidRPC(rpc, currentPunkAddress, network); 88 | 89 | if (validRPC) { 90 | networkSettingsHelper.updateItemSettings(network, { 91 | [CUSTOM_RPC_KEY]: rpc, 92 | }); 93 | 94 | setTargetNetwork(networkSettingsHelper.getSelectedItem(true)); 95 | } 96 | 97 | setValidRPC(validRPC); 98 | 99 | setLoading(false); 100 | }; 101 | 102 | const isValidRPC = async (rpc, currentPunkAddress, network) => { 103 | try { 104 | const provider = new StaticJsonRpcProvider(rpc); 105 | 106 | const nonce = await provider.getTransactionCount(currentPunkAddress); 107 | 108 | const rpcNetwork = await provider.getNetwork(); 109 | 110 | if (network?.chainId !== rpcNetwork?.chainId) { 111 | console.log("ChainId doesn't match current networ's chainId", network?.chainId, rpcNetwork?.chainId); 112 | 113 | return false; 114 | } 115 | 116 | return true; 117 | } catch (error) { 118 | console.log("Couldn't validate RPC", error); 119 | 120 | return false; 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /packages/react-app/src/components/Faucet.jsx: -------------------------------------------------------------------------------- 1 | import { SendOutlined } from "@ant-design/icons"; 2 | import { parseEther } from "@ethersproject/units"; 3 | import { Button, Input, Tooltip } from "antd"; 4 | import { useLookupAddress } from "eth-hooks"; 5 | import React, { useCallback, useState } from "react"; 6 | import Blockies from "react-blockies"; 7 | import { Transactor } from "../helpers"; 8 | 9 | // improved a bit by converting address to ens if it exists 10 | // added option to directly input ens name 11 | // added placeholder option 12 | 13 | /* 14 | ~ What it does? ~ 15 | 16 | Displays a local faucet to send ETH to given address, also wallet is provided 17 | 18 | ~ How can I use? ~ 19 | 20 | 26 | 27 | ~ Features ~ 28 | 29 | - Provide price={price} of ether and convert between USD and ETH in a wallet 30 | - Provide localProvider={localProvider} to be able to send ETH to given address 31 | - Provide ensProvider={mainnetProvider} and your address will be replaced by ENS name 32 | (ex. "0xa870" => "user.eth") or you can enter directly ENS name instead of address 33 | works both in input field & wallet 34 | - Provide placeholder="Send local faucet" value for the input 35 | */ 36 | 37 | export default function Faucet(props) { 38 | const [address, setAddress] = useState(); 39 | 40 | let blockie; 41 | if (address && typeof address.toLowerCase === "function") { 42 | blockie = ; 43 | } else { 44 | blockie =
; 45 | } 46 | 47 | const ens = useLookupAddress(props.ensProvider, address); 48 | 49 | const updateAddress = useCallback( 50 | async newValue => { 51 | if (typeof newValue !== "undefined") { 52 | let address = newValue; 53 | if (address.indexOf(".eth") > 0 || address.indexOf(".xyz") > 0) { 54 | try { 55 | const possibleAddress = await props.ensProvider.resolveName(address); 56 | if (possibleAddress) { 57 | address = possibleAddress; 58 | } 59 | // eslint-disable-next-line no-empty 60 | } catch (e) {} 61 | } 62 | setAddress(address); 63 | } 64 | }, 65 | [props.ensProvider, props.onChange], 66 | ); 67 | 68 | const tx = Transactor(props.localProvider); 69 | 70 | return ( 71 | 72 | { 79 | // setAddress(e.target.value); 80 | updateAddress(e.target.value); 81 | }} 82 | suffix={ 83 | 84 | 76 | ); 77 | 78 | return ( 79 |
80 | {useInfiniteScroll ? 81 |
90 | 95 | {transactionList} 96 | 97 | 98 | {(transactionResponsesArray.length > 2) && clearAllButton} 99 |
100 | : 101 | <> 102 | {transactionList} 103 | {(transactionResponsesArray.length > 1) && clearAllButton } 104 | 105 | 106 | } 107 |
108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/PolygonTransferWithAuthorizationHelper.js: -------------------------------------------------------------------------------- 1 | // USDC implements EIP-3009 2 | // https://github.com/centrehq/centre-tokens/blob/0d3cab14ebd133a83fc834dbd48d0468bdf0b391/contracts/v2/EIP3009.sol 3 | // On Polygon a slightly different version is deployed, where EIP_712 version is "1" and the domain uses salt instead of the chainId 4 | // https://polygonscan.com/address/0x2791bca1f2de4661ed88a30c99a7a9449aa84174#readProxyContract 5 | 6 | // Instead of calling the transfer function on the token contract and pay for the transaction fee, 7 | // we can sign the transferWithAuthorization message, and execute it 8 | // from a different "relayer account", who has MATIC to pay for the transaction. 9 | 10 | // PolygonNativeMetaTransaction should work for USDC as well, USDC contract handles nonces differently than DAI and USDT though 11 | // USDC has the nonces method instead of getNonce 12 | 13 | import { createEthersWallet } from "./EIP1559Helper"; 14 | import { getAmount } from "./ERC20Helper"; 15 | import { sendTransactionViaRelayerAccount } from "./PolygonRelayerAccountHelper"; 16 | import { NETWORKS, POLYGON_USDC_ADDRESS } from "../constants"; 17 | 18 | const { ethers, BigNumber } = require("ethers"); 19 | 20 | const ABI = [ 21 | "function transferWithAuthorization (address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)" 22 | ]; 23 | 24 | export const transferWithAuthorization = async (token, to, value) => { 25 | value = getAmount(value, token.decimals); 26 | 27 | const validAfter = "0x0000000000000000000000000000000000000000000000000000000000000001"; // signature valid from the beginning 28 | 29 | const provider = new ethers.providers.JsonRpcProvider(NETWORKS.polygon.rpcUrl); 30 | const timestamp = (await provider.getBlock()).timestamp + 60; // signature valid till a minute 31 | const validBefore = ethers.utils.hexZeroPad(BigNumber.from(timestamp).toHexString(), 32); 32 | 33 | const nonce = ethers.utils.hexlify(ethers.utils.randomBytes(32)); // eip-3009 uses Unique Random Nonces 34 | 35 | const signerWallet = createEthersWallet(); 36 | const transferWithAuthorizationSignature = await getTransferWithAuthorizationSignature(signerWallet, to, value, validAfter, validBefore, nonce); 37 | 38 | const contract = new ethers.Contract(POLYGON_USDC_ADDRESS, ABI, provider); 39 | 40 | const txParams = 41 | await contract.populateTransaction.transferWithAuthorization( 42 | signerWallet.address, to, value, validAfter, validBefore, nonce, transferWithAuthorizationSignature.v, transferWithAuthorizationSignature.r, transferWithAuthorizationSignature.s); 43 | 44 | console.log("txParams", JSON.parse(JSON.stringify(txParams))); 45 | 46 | txParams.chainId = NETWORKS.polygon.chainId; 47 | 48 | return sendTransactionViaRelayerAccount(txParams, signerWallet.address, provider); 49 | } 50 | 51 | const getTransferWithAuthorizationSignature = async (signer, to, value, validAfter, validBefore, nonce) => { 52 | const name = "USD Coin (PoS)"; 53 | const version = "1"; 54 | const verifyingContract = POLYGON_USDC_ADDRESS; 55 | const salt = ethers.utils.hexZeroPad(ethers.utils.hexlify(NETWORKS.polygon.chainId), 32); 56 | 57 | return ethers.utils.splitSignature( 58 | await signer._signTypedData( 59 | { 60 | name, 61 | version, 62 | verifyingContract, 63 | salt 64 | }, 65 | { 66 | TransferWithAuthorization: [ 67 | { 68 | name: "from", 69 | type: "address", 70 | }, 71 | { 72 | name: "to", 73 | type: "address", 74 | }, 75 | { 76 | name: "value", 77 | type: "uint256", 78 | }, 79 | { 80 | name: "validAfter", 81 | type: "uint256", 82 | }, 83 | { 84 | name: "validBefore", 85 | type: "uint256", 86 | }, 87 | { 88 | name: "nonce", 89 | type: "bytes32", 90 | } 91 | ], 92 | }, 93 | { 94 | from: signer.address, 95 | to, 96 | value, 97 | validAfter, 98 | validBefore, 99 | nonce, 100 | } 101 | ) 102 | ) 103 | } -------------------------------------------------------------------------------- /packages/react-app/src/helpers/ERC20Helper.js: -------------------------------------------------------------------------------- 1 | import { NETWORKS } from "../constants"; 2 | 3 | const { ethers, BigNumber, utils } = require("ethers"); 4 | 5 | // https://docs.ethers.org/v5/single-page/#/v5/api/contract/example/-%23-example-erc-20-contract--connecting-to-a-contract 6 | // A Human-Readable ABI; for interacting with the contract, we 7 | // must include any fragment we wish to use 8 | const abi = [ 9 | "function balanceOf(address owner) view returns (uint256)", 10 | "function decimals() view returns (uint8)", 11 | "function symbol() view returns (string)", 12 | "function transfer(address to, uint amount) returns (bool)", 13 | ]; 14 | 15 | export class ERC20Helper { 16 | constructor(tokenAddress, wallet, rpcURL) { 17 | this.tokenAddress = tokenAddress; 18 | this.wallet = wallet; 19 | 20 | let provider; 21 | 22 | if (!wallet && rpcURL) { 23 | provider = new ethers.providers.StaticJsonRpcProvider(rpcURL); 24 | } 25 | 26 | let signerOrProvider = wallet ? wallet : provider ? provider : undefined; 27 | 28 | this.contract = new ethers.Contract(tokenAddress, abi, signerOrProvider); 29 | } 30 | 31 | balanceOf = address => this.contract.balanceOf(address); 32 | 33 | symbol = () => this.contract.symbol(); 34 | 35 | decimals = () => this.contract.decimals(); 36 | 37 | transfer = (toAddress, amount) => this.contract.transfer(toAddress, amount); 38 | 39 | transferPopulateTransaction = (toAddress, amount) => this.contract.populateTransaction.transfer(toAddress, amount); 40 | } 41 | 42 | export const getAmount = (amount, decimals) => { 43 | if (utils.isHexString(amount)) { 44 | return amount; 45 | } 46 | 47 | return getDecimalCorrectedAmountBigNumber(amount, decimals).toHexString(); 48 | }; 49 | 50 | export const getDecimalCorrectedAmountBigNumber = (amountNumber, decimals) => 51 | utils.parseUnits(amountNumber.toString(), decimals); 52 | 53 | export const getInverseDecimalCorrectedAmountNumber = (amountBigNumber, decimals) => 54 | Number(utils.formatUnits(amountBigNumber.toString(), decimals)); 55 | 56 | export const getDisplayNumberWithDecimals = (number, dollarMode) => { 57 | let decimals = 2; 58 | 59 | if (!dollarMode && number < 1) { 60 | decimals = 4; 61 | } 62 | 63 | return number.toFixed(decimals); 64 | }; 65 | 66 | export const getTokenBalance = async (token, rpcURL, address) => { 67 | const erc20Helper = new ERC20Helper(token.address, null, rpcURL); 68 | 69 | const balanceBigNumber = await erc20Helper.balanceOf(address); 70 | 71 | return balanceBigNumber; 72 | }; 73 | 74 | export const getTransferTxParams = (token, to, amount) => { 75 | const erc20Helper = new ERC20Helper(token.address); 76 | 77 | return erc20Helper.transferPopulateTransaction(to, getAmount(amount, token.decimals)); 78 | }; 79 | 80 | const checkBalance = async (token, rpcURL, address, balance, setBalance, intervalId) => { 81 | try { 82 | const balanceBigNumber = BigNumber.from(balance); 83 | 84 | const currentBalanceBigNumber = await getTokenBalance(token, rpcURL, address); 85 | 86 | if (!balanceBigNumber.eq(currentBalanceBigNumber)) { 87 | setBalance(currentBalanceBigNumber.toHexString()); 88 | 89 | clearInterval(intervalId); 90 | } 91 | } catch (error) { 92 | console.error("Couldn't check balance", error); 93 | } 94 | }; 95 | 96 | export const monitorBalance = (token, rpcURL, address, balance, setBalance) => { 97 | if (!token || !balance || !address) { 98 | return; 99 | } 100 | 101 | let intervalId; 102 | 103 | const checkBalanceAndUpdate = () => { 104 | checkBalance(token, rpcURL, address, balance, setBalance, intervalId); 105 | }; 106 | 107 | intervalId = setInterval(checkBalanceAndUpdate, 1000); 108 | 109 | setTimeout(() => { 110 | clearInterval(intervalId); 111 | }, 30 * 1000); 112 | }; 113 | 114 | const isTokenAddressMainnetWETH = tokenAddress => { 115 | const tokens = NETWORKS?.ethereum?.erc20Tokens; 116 | 117 | if (!tokens) { 118 | return false; 119 | } 120 | 121 | let tokenAddressWETH = undefined; 122 | 123 | for (const token of tokens) { 124 | if (token.name === "WETH") { 125 | tokenAddressWETH = token.address; 126 | break; 127 | } 128 | } 129 | 130 | if (tokenAddress == tokenAddressWETH) { 131 | return true; 132 | } 133 | 134 | return false; 135 | }; 136 | -------------------------------------------------------------------------------- /packages/react-app/src/components/QRPunkBlockie.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import QR from "qrcode.react"; 3 | import { message } from "antd"; 4 | import { build } from "eth-url-parser"; 5 | 6 | import { Punk, PunkBlockie } from "."; 7 | 8 | import { copy } from "../helpers/EditorHelper"; 9 | 10 | const { ethers } = require("ethers"); 11 | 12 | export const BLOCKIES_DEFAULT_SIZE = 8; 13 | 14 | export default function QRPunkBlockie({ 15 | address, 16 | showAddress, 17 | withQr, 18 | receiveMode, 19 | scale, 20 | chainId, 21 | amount, 22 | selectedErc20Token, 23 | }) { 24 | let displayValue = address; 25 | let paymentLink; 26 | 27 | if (receiveMode) { 28 | try { 29 | const eip681Data = { 30 | scheme: "ethereum", 31 | prefix: null, 32 | target_address: selectedErc20Token ? selectedErc20Token.address : address, 33 | chain_id: chainId, 34 | function_name: selectedErc20Token ? "transfer" : null, 35 | parameters: {}, 36 | }; 37 | 38 | if (selectedErc20Token) { 39 | eip681Data.parameters.address = address; 40 | } 41 | 42 | if (amount) { 43 | let amountString; 44 | 45 | try { 46 | const decimals = selectedErc20Token ? selectedErc20Token.decimals : 18; 47 | 48 | amountString = ethers.utils 49 | .parseUnits(typeof amount === "string" ? amount : amount.toFixed(decimals).toString(), decimals) 50 | .toString(); 51 | } catch (error) { 52 | console.error("Couldn't parse amount", amount, error); 53 | } 54 | 55 | if (amountString) { 56 | if (selectedErc20Token) { 57 | eip681Data.parameters.uint256 = amountString; 58 | } else { 59 | eip681Data.parameters.value = amountString; 60 | } 61 | } 62 | } 63 | 64 | const eip681 = build(eip681Data); 65 | 66 | displayValue = eip681; 67 | 68 | paymentLink = window.location.href + displayValue; 69 | } catch (error) { 70 | console.error("Couldn't create EIP-681 QR value", error); 71 | } 72 | } 73 | 74 | const hardcodedSizeForNow = 380; 75 | let blockieScale = 11.5; 76 | 77 | if (scale) { 78 | blockieScale *= scale; 79 | } 80 | 81 | const punkSize = blockieScale * BLOCKIES_DEFAULT_SIZE; // Make punk image the same size as the blockie, from https://github.com/ethereum/blockies: width/height of the icon in blocks, default: 8 82 | 83 | return ( 84 | 86 | copy(paymentLink || displayValue, () => 87 | message.success( 88 | 89 | {!receiveMode ? "Copied Address" : "Copied Payment Link"} 90 |
91 | 92 |
93 |
, 94 | ), 95 | ) 96 | } 97 | > 98 |
108 | {withQr && ( 109 |
110 | 117 |
118 | )} 119 | 120 |
121 | 122 |
123 |
124 | 125 | {showAddress && ( 126 |
137 | {displayValue} 138 |
139 | )} 140 |
141 | ); 142 | } 143 | --------------------------------------------------------------------------------