├── .eslintrc.json ├── .env.sample ├── public ├── logo.png ├── favicon.ico ├── login-bg.jpg ├── logo192.png ├── logo512.png ├── robots.txt ├── chains │ ├── core-dao.png │ ├── gnosis-chain.png │ ├── neonevm-logo.png │ ├── polygon-zk-evm.png │ ├── ethereum-eth-logo.svg │ └── polygon-matic-logo.svg ├── .well-known │ ├── walletconnect.txt │ └── did.json ├── arrow-down.svg ├── paid-icon.svg ├── received-icon.svg ├── menu-icon.svg ├── close-modal.svg ├── close.svg ├── success-icon.svg ├── add.svg ├── onboarding │ ├── big-success-icon.svg │ ├── next-step-icon.svg │ ├── checked-icon.svg │ ├── professional-call-icon.svg │ ├── current-icon.svg │ ├── terms-icon.svg │ ├── contractor-call-icon.svg │ └── community-call-icon.svg ├── allowedTokens.json ├── manifest.json ├── social │ ├── x-icon.svg │ ├── telegram-icon.svg │ ├── linkedin-icon.svg │ └── discord-icon.svg ├── info-icon.svg ├── pending-icon.svg ├── web3dev.svg ├── agreement-wallet-icon.svg ├── usd-icon.svg ├── error-icon.svg ├── alert-icon.svg ├── coins │ ├── usdt-icon.svg │ ├── usdc-icon.svg │ └── dai-icon.svg ├── dashboard │ ├── community │ │ ├── rewards-icon-green.svg │ │ ├── benefits-icon-green.svg │ │ ├── agreements-icon-green.svg │ │ ├── social-icon-green.svg │ │ └── members-icon-green.svg │ ├── contractor │ │ └── social-icon-blue.svg │ ├── professional │ │ └── social-icon-red.svg │ └── discord-icon-purple.svg ├── index.html ├── agreement-title-icon.svg ├── no-agreement-icon.svg ├── solana-icon.svg └── locales │ ├── en-US │ └── translation.json │ └── pt-BR │ └── translation.json ├── contracts ├── solana │ ├── programs │ │ └── agreement │ │ │ ├── Xargo.toml │ │ │ └── Cargo.toml │ ├── Cargo.toml │ ├── tsconfig.json │ ├── Anchor.toml │ ├── migrations │ │ └── deploy.ts │ ├── package.json │ ├── README.md │ └── scripts │ │ ├── retrieveAgreements.ts │ │ ├── deploy.sh │ │ ├── withdrawFromVault.ts │ │ └── createAgreements.ts └── ethereum │ ├── contracts │ ├── interfaces │ │ ├── IAaveIncentivesController.sol │ │ ├── IDataProvider.sol │ │ ├── ILendingPool.sol │ │ ├── IKyodoRegistry.sol │ │ └── IStableVault.sol │ ├── Admin.sol │ ├── testToken.sol │ └── KyodoRegistry.sol │ ├── scripts │ ├── deployMultichain.js │ ├── utils │ │ └── getRegistry.js │ ├── createAgreements.js │ ├── payAgreement.js │ └── retrieveAgreements.js │ ├── deploy │ └── 001_deploy_kyodo_registry.js │ ├── package.json │ ├── test │ ├── AgreementContract │ │ └── AgreementContract.test.js │ └── StableVault │ │ └── Withdraw.test.js │ ├── README.md │ └── hardhat.config.js ├── pages ├── index.js ├── payments │ └── index.js ├── agreements │ ├── index.js │ └── new.js ├── onboarding │ └── index.js ├── notifications.js ├── dashboard │ └── index.js └── api │ └── notify.js ├── next.config.js ├── jest.config.js ├── chains ├── ethereum │ ├── transactions │ │ ├── fetchUserInfo.js │ │ ├── saveUserInfo.js │ │ ├── addAgreement.js │ │ ├── fetchUserBalances.js │ │ ├── fetchPaidAgreements.js │ │ ├── fetchAgreements.js │ │ ├── withdrawFromVault.js │ │ └── payAgreement.js │ ├── utils │ │ └── ERC20Token.js │ ├── transactions.js │ └── contracts.js ├── solana │ ├── contracts.js │ ├── contracts │ │ ├── vaultContract.js │ │ └── agreementContract.js │ ├── transactions │ │ ├── fetchUserBalances.js │ │ ├── addAgreement.js │ │ ├── fetchAgreements.js │ │ ├── fetchPaidAgreements.js │ │ ├── payAgreement.js │ │ └── withdrawFromVault.js │ └── transactions.js ├── transactionManager.js ├── ContractManager.js └── chainConfig.json ├── styles ├── animations.scss ├── grid.scss ├── globals.scss ├── forms.scss ├── footer.scss ├── variables.scss └── components.scss ├── components ├── utils │ ├── Loader │ │ └── index.js │ ├── Toast │ │ └── index.js │ └── web3inbox.js ├── AddAgreement │ └── AddAgreement_function.md ├── Dashboard │ └── Cards.js └── ConnectWalletButton │ ├── ConnectWalletButton.js │ └── ConnectWalletButton.module.scss ├── .env.development.local.sample ├── i18n.js ├── .gitignore ├── LICENSE ├── __tests__ └── connectButton.spec.js ├── package.json └── hooks ├── useTransactionHandler_hook.md └── useTransactionHandler.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY= 2 | POLYGONSCAN_API_KEY= 3 | TESTNET_ALCHEMY_URL= -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/login-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/login-bg.jpg -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/chains/core-dao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/chains/core-dao.png -------------------------------------------------------------------------------- /contracts/solana/programs/agreement/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /public/chains/gnosis-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/chains/gnosis-chain.png -------------------------------------------------------------------------------- /public/chains/neonevm-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/chains/neonevm-logo.png -------------------------------------------------------------------------------- /public/chains/polygon-zk-evm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3b3d3v/kyodo-protocol/HEAD/public/chains/polygon-zk-evm.png -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Router from "next/router.js" 2 | export default function Home() { 3 | Router.push("/dashboard") 4 | } 5 | -------------------------------------------------------------------------------- /public/.well-known/walletconnect.txt: -------------------------------------------------------------------------------- 1 | 34abaa21-b474-49f2-91bf-dbb58d37a50d=a6d1117cf5836c71d704ef2c3761351a4daf570707c5c80c2cdc0270385ac25f -------------------------------------------------------------------------------- /pages/payments/index.js: -------------------------------------------------------------------------------- 1 | import Payments from "../../components/Dashboard/Payments.js" 2 | 3 | export default function Agreements() { 4 | return ( 5 | 6 | ) 7 | } -------------------------------------------------------------------------------- /pages/agreements/index.js: -------------------------------------------------------------------------------- 1 | import AgreementList from '../../components/AgreementList/AgreementList'; 2 | 3 | export default function Agreements() { 4 | return ( 5 | 6 | ) 7 | } -------------------------------------------------------------------------------- /pages/onboarding/index.js: -------------------------------------------------------------------------------- 1 | import Onboarding from "../../components/Onboarding" 2 | 3 | export default function Home() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | i18n: { 5 | locales: ["en-US", "pt-BR"], 6 | defaultLocale: "en-US", 7 | }, 8 | } 9 | 10 | module.exports = nextConfig 11 | -------------------------------------------------------------------------------- /public/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /contracts/solana/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | 6 | [profile.release] 7 | overflow-checks = true 8 | lto = "fat" 9 | codegen-units = 1 10 | [profile.release.build-override] 11 | opt-level = 3 12 | incremental = false 13 | codegen-units = 1 14 | -------------------------------------------------------------------------------- /contracts/solana/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/ethereum/contracts/interfaces/IAaveIncentivesController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.23; 4 | 5 | interface IAaveIncentivesController { 6 | function claimRewards( 7 | address[] calldata assets, 8 | uint amount, 9 | address to 10 | ) external returns (uint); 11 | } -------------------------------------------------------------------------------- /pages/agreements/new.js: -------------------------------------------------------------------------------- 1 | import AddAgreement from "../../components/AddAgreement/AddAgreement" 2 | import { AccountProvider } from "../../contexts/AccountContext"; 3 | 4 | export default function NewAgreement() { 5 | return ( 6 | 7 | 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const jestConfig = { 2 | verbose: true, 3 | silent: true, 4 | testEnvironment: 'jsdom', 5 | testURL: "http://localhost/", 6 | transform: { 7 | '^.+\\.jsx?$': 'babel-jest', 8 | "^.+\\.css$": "jest-css-modules-transform" 9 | }, 10 | testMatch: ['**/__tests__/*.js?(x)'], 11 | } 12 | 13 | module.exports = jestConfig -------------------------------------------------------------------------------- /contracts/solana/Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | [programs.devnet] 5 | agreement_program = "CBSQAPewE2kKVypu3536Xczdf6MYab547G3mFchcN8X1" 6 | 7 | [registry] 8 | url = "http://127.0.0.1:8899" 9 | 10 | [provider] 11 | cluster = "devnet" 12 | wallet = "~/.config/solana/id.json" 13 | 14 | [scripts] 15 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 16 | -------------------------------------------------------------------------------- /public/paid-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /chains/ethereum/transactions/fetchUserInfo.js: -------------------------------------------------------------------------------- 1 | export const fetchUserInfo = async (details) => { 2 | try { 3 | const userInfo = await details.contract.getUserInfo(details.account); 4 | 5 | return { 6 | name: userInfo[0], 7 | taxDocument: userInfo[1] 8 | }; 9 | } catch (error) { 10 | console.log("Error in fetchUserInfo:", error); 11 | throw error; 12 | } 13 | }; 14 | 15 | export default fetchUserInfo; 16 | -------------------------------------------------------------------------------- /public/received-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /chains/ethereum/transactions/saveUserInfo.js: -------------------------------------------------------------------------------- 1 | export const saveUserInfo = async (details) => { 2 | console.log("details", details) 3 | try { 4 | const tx = await details.contract.storeUserInfo( 5 | details.name, 6 | details.taxDocument 7 | ); 8 | 9 | return tx; 10 | } catch (error) { 11 | console.log("Error in saveUserInfo:", error); 12 | throw error; 13 | } 14 | }; 15 | 16 | export default saveUserInfo; 17 | -------------------------------------------------------------------------------- /pages/notifications.js: -------------------------------------------------------------------------------- 1 | import Web3Inbox from "../components/utils/web3inbox.js" 2 | import { useAccount } from "../contexts/AccountContext" 3 | 4 | function Notifications() { 5 | const { account, selectedChain, projectId } = useAccount() 6 | 7 | return ( 8 |
9 | {" "} 10 |
11 | ) 12 | } 13 | 14 | export default Notifications 15 | -------------------------------------------------------------------------------- /public/menu-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/close-modal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /contracts/solana/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@coral-xyz/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /pages/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import Balances from "../../components/Dashboard/Balances.js" 2 | import Cards from "../../components/Dashboard/Cards.js" 3 | import Payments from "../../components/Dashboard/Payments.js" 4 | 5 | function Dashboard() { 6 | return ( 7 |
8 |
9 | {/* */} 10 | 11 | 12 |
13 |
14 | ) 15 | } 16 | 17 | export default Dashboard 18 | -------------------------------------------------------------------------------- /styles/animations.scss: -------------------------------------------------------------------------------- 1 | /** 2 | ANIMATIONS 3 | =========================== 4 | */ 5 | 6 | .tracking-in-expand { 7 | -webkit-animation: tracking-in-expand 2s cubic-bezier(0.215, 0.610, 0.355, 1.000) both; 8 | animation: tracking-in-expand 2s cubic-bezier(0.215, 0.610, 0.355, 1.000) both; 9 | } 10 | 11 | @keyframes tracking-in-expand { 12 | 0% { 13 | opacity: 0; 14 | } 15 | 16 | 100% { 17 | opacity: 0.2; 18 | } 19 | 20 | 100% { 21 | opacity: 1; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chains/ethereum/utils/ERC20Token.js: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import ERC20_ABI from '../abis/ERC20.json'; 3 | 4 | class ERC20Token { 5 | constructor(tokenAddress) { 6 | if (!tokenAddress) throw new Error("Token address is required"); 7 | 8 | const provider = new ethers.providers.Web3Provider(window.ethereum); 9 | const contract = new ethers.Contract(tokenAddress, ERC20_ABI.abi, provider.getSigner()); 10 | return contract; 11 | } 12 | } 13 | 14 | export default ERC20Token; -------------------------------------------------------------------------------- /public/success-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /contracts/solana/programs/agreement/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "agreement_program" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "agreement_program" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = {version = "0.28.0", features = ["init-if-needed"]} 20 | anchor-spl = "0.28.0" 21 | -------------------------------------------------------------------------------- /contracts/ethereum/scripts/deployMultichain.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | 3 | const networks = ["sepolia", "avalancheFuji", "arbitrumGoerli", "polygonMumbai", "bnbTesnet", "baseGoerli"]; 4 | 5 | networks.forEach(network => { 6 | exec(`npx hardhat deploy --network ${network}`, (err, stdout, stderr) => { 7 | if (err) { 8 | console.error(`Erro ao implantar na rede ${network}: ${err}`); 9 | return; 10 | } 11 | console.log(`Deployment to ${network} completed`); 12 | console.log(stdout); 13 | }); 14 | }); -------------------------------------------------------------------------------- /components/utils/Loader/index.js: -------------------------------------------------------------------------------- 1 | // Loader.js 2 | import React from 'react'; 3 | import { PuffLoader } from "react-spinners"; 4 | 5 | function Loader({ isLoading }) { 6 | if (!isLoading) return null; 7 | 8 | return ( 9 |
10 | {isLoading && ( 11 |
12 |
13 | 14 |
15 |
16 | )} 17 |
18 | ); 19 | } 20 | 21 | export default Loader; 22 | -------------------------------------------------------------------------------- /public/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/onboarding/big-success-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /chains/solana/contracts.js: -------------------------------------------------------------------------------- 1 | import { agreementContract } from "./contracts/agreementContract" 2 | import { vaultContract } from "./contracts/vaultContract" 3 | 4 | async function verify() { 5 | // Unfortunatelly phantom wallet or solana object does not offer a clear way to get the network id 6 | // so we have to use just ask the user to manually change to the localnetwork while testing 7 | alert("Please change to Devnet. Phantom > Settings > Developer Settings") 8 | } 9 | 10 | const contracts = { agreementContract, vaultContract, verify } 11 | export default contracts -------------------------------------------------------------------------------- /public/allowedTokens.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "address": "0x2058A9D7613eEE744279e3856Ef0eAda5FCbaA7e", 4 | "logo": "src/components/assets/usd-coin-usdc-logo.svg", 5 | "name": "USDC", 6 | "decimals": 6 7 | }, 8 | { 9 | "address": "0x001B3B4d0F3714Ca98ba10F6042DaEbF0B1B7b6F", 10 | "logo": "src/components/assets/multi-collateral-dai-dai-logo.svg", 11 | "name": "DAI", 12 | "decimals": 18 13 | }, 14 | { 15 | "address": "0x9A676e781A523b5d0C0e43731313A708CB607508", 16 | "logo": "src/components/assets/your-token-logo.svg", 17 | "name": "USDT", 18 | "decimals": 18 19 | } 20 | ] -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "kyodo-protocol", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /chains/solana/contracts/vaultContract.js: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import idl from "./agreement_program.json"; 3 | import * as anchor from "@coral-xyz/anchor"; 4 | 5 | export function vaultContract(details) { 6 | const provider = new anchor.AnchorProvider( 7 | details.connection, 8 | details.wallet.adapter, 9 | anchor.AnchorProvider.defaultOptions() 10 | ) 11 | anchor.setProvider(provider); 12 | 13 | const programAddress = new PublicKey(process.env.NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS); 14 | const contract = new anchor.Program(idl, programAddress, provider); 15 | return contract; 16 | } -------------------------------------------------------------------------------- /chains/ethereum/transactions/addAgreement.js: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers" 2 | 3 | export const addAgreement = async (details) => { 4 | try { 5 | const paymentAmountInWei = ethers.utils.parseUnits(details.paymentAmount.toString(), 18); 6 | 7 | const tx = await details.contract.createAgreement( 8 | details.title, 9 | details.description, 10 | details.professional, 11 | details.skillsList, 12 | paymentAmountInWei 13 | ); 14 | 15 | return tx; 16 | } catch (error) { 17 | console.log("Error in addAgreement:", error); 18 | throw error; 19 | } 20 | }; 21 | 22 | export default addAgreement; -------------------------------------------------------------------------------- /public/social/x-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/info-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /contracts/ethereum/scripts/utils/getRegistry.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | 3 | async function getKyodoRegistry() { 4 | try { 5 | const KyodoRegistry = await ethers.getContractFactory("KyodoRegistry"); 6 | const kyodoRegistry = await KyodoRegistry.attach(process.env.NEXT_PUBLIC_KYODO_REGISTRY); 7 | const address = await kyodoRegistry.getRegistry("AGREEMENT_CONTRACT_ADDRESS"); 8 | console.log("address", address) 9 | 10 | 11 | } catch (error) { 12 | console.error("Failed to initialize KyodoRegistry contract: ", error); 13 | throw error; 14 | } 15 | } 16 | 17 | getKyodoRegistry() 18 | -------------------------------------------------------------------------------- /.env.development.local.sample: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_AGREEMENT_CONTRACT_ADDRESS=0x0000000000000000000000000000000000000000 2 | NEXT_PUBLIC_FAKE_STABLE_ADDRESS=0x0000000000000000000000000000000000000000 3 | 4 | NEXT_PUBLIC_KYODO_TREASURY_CONTRACT_ADDRESS=0x516E98eb5C1D826FCca399b8D8B13BD8e4E12bC8 5 | NEXT_PUBLIC_COMMUNITY_TREASURY_CONTRACT_ADDRESS=0x19E776E2ff69d8E6600c776d3f1Ef4586606805F 6 | NEXT_PUBLIC_STABLE_VAULT_ADDRESS=0x0000000000000000000000000000000000000000 7 | 8 | DAI_GOERLY=0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844 9 | 10 | SOL_KYODO_TREASURY_ADDRESS= 11 | SOL_COMMUNITY_TREASURY_ADDRESS= 12 | 13 | NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS= 14 | ANCHOR_WALLET= -------------------------------------------------------------------------------- /contracts/solana/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@coral-xyz/anchor": "^0.28.0", 8 | "@solana/spl-token": "^0.3.8", 9 | "dotenv": "^16.3.1" 10 | }, 11 | "devDependencies": { 12 | "@types/bn.js": "^5.1.0", 13 | "@types/chai": "^4.3.6", 14 | "@types/mocha": "^9.0.0", 15 | "chai": "^4.3.4", 16 | "mocha": "^9.0.3", 17 | "prettier": "^2.6.2", 18 | "ts-mocha": "^10.0.0", 19 | "typescript": "^4.3.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /public/pending-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/social/telegram-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /chains/solana/contracts/agreementContract.js: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import idl from "./agreement_program.json"; 3 | import * as anchor from "@coral-xyz/anchor"; 4 | 5 | const opts ={ 6 | preflightCommitment: "processed" 7 | } 8 | 9 | export function agreementContract(details) { 10 | const provider = new anchor.AnchorProvider( 11 | details.connection, 12 | details.wallet.adapter, 13 | anchor.AnchorProvider.defaultOptions() 14 | ) 15 | anchor.setProvider(provider); 16 | 17 | const programAddress = new PublicKey(process.env.NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS); 18 | const contract = new anchor.Program(idl, programAddress, provider); 19 | return contract; 20 | } -------------------------------------------------------------------------------- /components/AddAgreement/AddAgreement_function.md: -------------------------------------------------------------------------------- 1 | ### 1. `addAgreement` function: 2 | 3 | This is an asynchronous function that performs the following steps: 4 | 5 | - It creates a `details` object which holds the agreement details. 6 | - It defines a callback function `onConfirmation` which will be called once the transaction is confirmed. This function logs the transaction receipt, resets various state variables, and then redirects the user to the `/agreements` route after a delay of 3 seconds. 7 | - It then invokes the `sendTransaction` function to initiate the blockchain transaction. The arguments provided are the function name ("addAgreement"), the agreement details, the event name ("AgreementCreated"), and the `onConfirmation` callback. -------------------------------------------------------------------------------- /public/onboarding/next-step-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/.well-known/did.json: -------------------------------------------------------------------------------- 1 | {"@context":["https://www.w3.org/ns/did/v1","https://w3id.org/security/suites/jws-2020/v1"],"id":"did:web:betapp.kyodoprotocol.xyz","verificationMethod":[{"id":"did:web:betapp.kyodoprotocol.xyz#wc-notify-subscribe-key","type":"JsonWebKey2020","controller":"did:web:betapp.kyodoprotocol.xyz","publicKeyJwk":{"kty":"OKP","crv":"X25519","x":"JDMv-SwfMTU9D_q0hcpWA7xg4TTJpzhA04OEQH8gpy4"}},{"id":"did:web:betapp.kyodoprotocol.xyz#wc-notify-authentication-key","type":"JsonWebKey2020","controller":"did:web:betapp.kyodoprotocol.xyz","publicKeyJwk":{"kty":"OKP","crv":"Ed25519","x":"EaqEDAPDxirCOSbavFubOzPXuGx0K3-ABixgyZPxc2U"}}],"keyAgreement":["did:web:betapp.kyodoprotocol.xyz#wc-notify-subscribe-key"],"authentication":["did:web:betapp.kyodoprotocol.xyz#wc-notify-authentication-key"]} -------------------------------------------------------------------------------- /public/web3dev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/social/linkedin-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from "i18next" 2 | import { initReactI18next } from "react-i18next" 3 | import Backend from "i18next-http-backend" 4 | import LanguageDetector from "i18next-browser-languagedetector" 5 | import * as Yup from "yup" 6 | 7 | i18n 8 | .use(Backend) 9 | .use(LanguageDetector) 10 | .use(initReactI18next) 11 | .init({ 12 | fallbackLng: "en-US", 13 | interpolation: { 14 | escapeValue: false, 15 | }, 16 | }) 17 | 18 | function setupYup() { 19 | Yup.setLocale({ 20 | mixed: { 21 | required: ({ path }) => i18n.t("validation.required", { field: i18n.t(path) }), 22 | }, 23 | }) 24 | } 25 | 26 | i18n.on("initialized", setupYup) 27 | i18n.on("languageChanged", setupYup) 28 | 29 | // // ... other Yup configurations 30 | // }) 31 | 32 | export default i18n 33 | -------------------------------------------------------------------------------- /public/agreement-wallet-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /styles/grid.scss: -------------------------------------------------------------------------------- 1 | /** 2 | GRID 3 | =========================== 4 | */ 5 | 6 | .holder { 7 | width: 94%; 8 | margin: auto; 9 | 10 | @media #{$smartphones-1} { 11 | width: 90%; 12 | } 13 | } 14 | 15 | .columns, 16 | .columns-3 { 17 | display: flex; 18 | justify-content: space-between; 19 | flex-direction: row; 20 | 21 | @media #{$smartphones-1} { 22 | display: block; 23 | } 24 | } 25 | 26 | 27 | /* Two columns */ 28 | 29 | .columns { 30 | 31 | @media #{$smartphones-1} { 32 | display: block; 33 | } 34 | 35 | .col-01, 36 | .col-02 { 37 | width: 45%; 38 | 39 | @media #{$smartphones-1} { 40 | width: 100%; 41 | } 42 | } 43 | } 44 | 45 | 46 | /* Three columns */ 47 | 48 | .columns-3 { 49 | 50 | > div { 51 | width: 30%; 52 | 53 | @media #{$smartphones-1} { 54 | width: 100%; 55 | margin-top: 60px; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /public/usd-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /contracts/ethereum/contracts/interfaces/IDataProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.23; 4 | 5 | interface IDataProvider { 6 | function getReserveTokensAddresses(address asset) external view returns ( 7 | address aTokenAddress, 8 | address stableDebtTokenAddress, 9 | address variableDebtTokenAddress 10 | ); 11 | 12 | function getUserReserveData(address asset, address user) external view returns ( 13 | uint256 currentATokenBalance, 14 | uint256 currentStableDebt, 15 | uint256 currentVariableDebt, 16 | uint256 principalStableDebt, 17 | uint256 scaledVariableDebt, 18 | uint256 stableBorrowRate, 19 | uint256 liquidityRate, 20 | uint40 stableRateLastUpdated, 21 | bool usageAsCollateralEnabled 22 | ); 23 | 24 | function getRewardsBalance(address[] calldata assets, address user) external view returns ( 25 | uint256 26 | ); 27 | } -------------------------------------------------------------------------------- /public/onboarding/checked-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/onboarding/professional-call-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/onboarding/current-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | yarn.lock 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | .pnpm-debug.log* 28 | 29 | # local env files 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | .env 35 | 36 | # vercel 37 | .vercel 38 | 39 | artifacts/ 40 | cache/ 41 | package-lock.json 42 | 43 | contracts/solana/Cargo.lock 44 | contracts/solana/.anchor/ 45 | contracts/solana/node_modules/ 46 | contracts/solana/.prettierignore 47 | contracts/solana/yarn.lock 48 | contracts/solana/target/ 49 | contracts/solana/mykey.json 50 | contracts/solana/test-ledger/ 51 | 52 | contracts/ethereum/node_modules 53 | contracts/ethereum/yarn.lock 54 | contracts/ethereum/deployments 55 | 56 | TODO 57 | .vscode/ -------------------------------------------------------------------------------- /chains/ethereum/transactions/fetchUserBalances.js: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import ERC20Token from '../utils/ERC20Token'; 3 | 4 | export const fetchUserBalances = async (details) => { 5 | // TODO: Make tokens come from accpeted tokens list 6 | const tokenAddresses = [details.contract.address] //Vault Token Address 7 | const balances = [] 8 | 9 | for (let address of tokenAddresses) { 10 | try { 11 | const tokenContract = new ERC20Token(address) 12 | const rawBalance = await tokenContract.balanceOf(details.account) 13 | const decimals = await tokenContract.decimals() 14 | 15 | const formattedBalance = ethers.utils.formatUnits(rawBalance, decimals) 16 | 17 | balances.push({ 18 | tokenAddress: address, 19 | tokenDecimals: decimals, 20 | amount: formattedBalance, 21 | }) 22 | } catch (error) { 23 | console.error(`Error when retrieving balance for ${address}:`, error) 24 | } 25 | } 26 | 27 | return balances 28 | }; 29 | 30 | 31 | export default fetchUserBalances; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) web3dev 2022 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /public/error-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /styles/globals.scss: -------------------------------------------------------------------------------- 1 | /** 2 | GLOBAL 3 | =========================== 4 | */ 5 | 6 | 7 | // Core 8 | 9 | @import "normalize"; 10 | @import "variables"; 11 | @import "grid"; 12 | @import "header"; 13 | @import "forms"; 14 | @import "footer"; 15 | @import "components"; 16 | @import "animations"; 17 | 18 | 19 | // Generic elements 20 | 21 | html { 22 | min-height: 100%; 23 | } 24 | 25 | body { 26 | -webkit-font-smoothing: antialiased; 27 | -moz-osx-font-smoothing: grayscale; 28 | font-family: $sans-serif; 29 | background: rgb(6,6,6); 30 | background: linear-gradient(180deg, rgba(6,6,6,1) 69%, rgba(18,18,18,1) 100%); 31 | color: $white; 32 | margin: 0; 33 | } 34 | 35 | h1, 36 | h2, 37 | h3, 38 | h4 { 39 | font-weight: 600; 40 | } 41 | 42 | h1, 43 | h2, 44 | h3, 45 | h4, 46 | p, 47 | ul, 48 | li { 49 | margin: 0; 50 | padding: 0; 51 | } 52 | 53 | h1 { 54 | text-align: center; 55 | margin: 55px 0 50px 0; 56 | font-size: $font-big; 57 | letter-spacing: -1px; 58 | font-weight: 500; 59 | } 60 | 61 | .centered-content { 62 | display: flex; 63 | justify-content: center; 64 | align-items: center; 65 | height: calc(100vh - 60px); 66 | } 67 | 68 | -------------------------------------------------------------------------------- /contracts/ethereum/contracts/interfaces/ILendingPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.23; 4 | 5 | interface ILendingPool { 6 | event Deposit( 7 | address indexed reserve, 8 | address user, 9 | address indexed onBehalfOf, 10 | uint amount, 11 | uint16 indexed referral 12 | ); 13 | 14 | event Withdraw(address indexed reserve, address indexed user, address indexed to, uint amount); 15 | 16 | function deposit(address asset, uint amount, address onBehalfOf, uint16 referralCode) external; 17 | function withdraw(address asset, uint amount, address to) external returns (uint); 18 | function borrow(address asset, uint amount, uint interestRateMode, uint16 referralCode, address onBehalfOf) external; 19 | function repay(address asset, uint amount, uint rateMode, address onBehalfOf) external returns (uint); 20 | 21 | function getUserAccountData(address user) external view returns ( 22 | uint totalCollateralETH, 23 | uint totalDebtETH, 24 | uint availableBorrowsETH, 25 | uint currentLiquidationThreshold, 26 | uint ltv, 27 | uint healthFactor 28 | ); 29 | } -------------------------------------------------------------------------------- /contracts/ethereum/deploy/001_deploy_kyodo_registry.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = async ({getNamedAccounts, deployments}) => { 5 | const {deploy} = deployments; 6 | const {deployer} = await getNamedAccounts(); 7 | console.log(`Deployer: ${deployer}`); 8 | 9 | const salt = '0x'; 10 | 11 | const deployedContract = await deploy('KyodoRegistry', { 12 | from: deployer, 13 | args: [deployer], 14 | log: true, 15 | deterministicDeployment: salt 16 | }); 17 | 18 | const envPath = path.join(__dirname, '../../../.env.development.local'); 19 | let envContent = fs.readFileSync(envPath, { encoding: 'utf8' }); 20 | 21 | if (!envContent.includes('NEXT_PUBLIC_KYODO_REGISTRY=')) { 22 | envContent += `\nNEXT_PUBLIC_KYODO_REGISTRY=${deployedContract.address}\n`; 23 | } else { 24 | envContent = envContent.replace(/NEXT_PUBLIC_KYODO_REGISTRY=.*/, `NEXT_PUBLIC_KYODO_REGISTRY=${deployedContract.address}`); 25 | } 26 | 27 | fs.writeFileSync(envPath, envContent.trim() + '\n'); 28 | console.log(`Updated or added NEXT_PUBLIC_KYODO_REGISTRY in .env.development.local to ${deployedContract.address}`); 29 | }; 30 | 31 | module.exports.tags = ['KyodoRegistry']; 32 | -------------------------------------------------------------------------------- /public/onboarding/terms-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/alert-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /chains/solana/transactions/fetchUserBalances.js: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | 4 | export const fetchUserBalances = async (details) => { 5 | //TODO: Make these tokens come from accepted payment tokens list 6 | const tokenAddresses = [process.env.NEXT_PUBLIC_SOLANA_FAKE_STABLE_ADDRESS] 7 | const balances = []; 8 | 9 | const accountPublicKey = new PublicKey(details.account) 10 | 11 | for (let address of tokenAddresses) { 12 | try { 13 | const [professionalVaultPublicKey, ___] = 14 | anchor.web3.PublicKey.findProgramAddressSync( 15 | [accountPublicKey.toBytes(), new PublicKey(address).toBytes()], 16 | details.contract.programId 17 | ); 18 | 19 | const accountBalance = await details.contract.provider.connection.getTokenAccountBalance(professionalVaultPublicKey); 20 | 21 | balances.push({ 22 | amount: accountBalance.value.uiAmountString, 23 | }); 24 | 25 | return balances 26 | } catch (error) { 27 | console.error(`Error when retrieving balance for ${address}:`, error); 28 | } 29 | } 30 | return balances; 31 | }; 32 | 33 | export default fetchUserBalances; 34 | -------------------------------------------------------------------------------- /public/onboarding/contractor-call-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /components/Dashboard/Cards.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import styles from "./Dashboard.module.scss" 3 | import { useTranslation } from "react-i18next" 4 | 5 | function Cards() { 6 | const { t } = useTranslation() 7 | 8 | return ( 9 |
10 |
    11 |
  • 12 |

    Complete your profile to be visible

    13 |
    14 |
    15 |
    16 |

    17 | You profile is 35% complete 18 |

    19 | Complete profile 20 |
  • 21 |
  • 22 |

    {t("call-02")}

    23 |

    {t("phrase-02")}

    24 | {t("btn-02")} 25 |
  • 26 |
  • 27 |

    28 | Refer and

    earn 29 |

    30 |

    31 | Professionals or contractors that refer the usage of Kyodo, can earn a % of paid value 32 | to the protocol. 33 |

    34 | Get referral link 35 |
  • 36 |
37 |
38 | ) 39 | } 40 | 41 | export default Cards 42 | -------------------------------------------------------------------------------- /styles/forms.scss: -------------------------------------------------------------------------------- 1 | /** 2 | FORMS 3 | =========================== 4 | */ 5 | 6 | form { 7 | 8 | label { 9 | color: $light-gray; 10 | 11 | span { 12 | color: $red; 13 | } 14 | } 15 | } 16 | 17 | input, 18 | textarea, 19 | .custom-select { 20 | width: 100%; 21 | border: 1px solid $dark-blue-2; 22 | background: rgba($medium-blue, .07); 23 | border-radius: 6px; 24 | font-size: 14px; 25 | color: $white; 26 | box-sizing: border-box; 27 | display: block; 28 | margin: 8px 0 22px 0; 29 | height: 40px; 30 | } 31 | 32 | .custom-select { 33 | padding: 0 10px; 34 | line-height: 38px; 35 | 36 | select { 37 | width: 100%; 38 | background: none; 39 | border: none; 40 | color: $white; 41 | } 42 | 43 | option { 44 | 45 | } 46 | 47 | option:nth-child(even) { 48 | background-color: $dark-gray; 49 | } 50 | 51 | option:nth-child(odd) { 52 | background-color: #020202; 53 | } 54 | } 55 | 56 | input { 57 | line-height: 40px; 58 | padding: 0 15px; 59 | } 60 | 61 | textarea { 62 | padding: 12px 15px; 63 | line-height: 22px; 64 | max-width: 100%; 65 | min-width: 100%; 66 | min-height: 83px; 67 | max-height: 140px; 68 | } 69 | 70 | #community-description-input, 71 | #contractor-about-input { 72 | min-height: 72px; 73 | } 74 | 75 | 76 | .validation-msg { 77 | font-size: $font-small; 78 | color: $red; 79 | margin-top: 5px; 80 | } 81 | -------------------------------------------------------------------------------- /public/chains/ethereum-eth-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/chains/polygon-matic-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /chains/ethereum/transactions/fetchPaidAgreements.js: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | export const fetchPaidAgreements = async (details) => { 4 | try { 5 | const startBlock = parseInt(details.KyodoRegistry.getRegistry("AGREEMENT_CONTRACT")); 6 | const latestBlock = await details.contract.provider.getBlockNumber(); // get the latest block number 7 | 8 | const companyFilter = details.contract.filters.PaymentMade(details.account, null); 9 | const professionalFilter = details.contract.filters.PaymentMade(null, details.account); 10 | 11 | const companyAgreements = await details.contract.queryFilter(companyFilter, startBlock, latestBlock); 12 | const professionalAgreements = await details.contract.queryFilter(professionalFilter, startBlock, latestBlock); 13 | 14 | const allAgreements = [...companyAgreements, ...professionalAgreements]; 15 | 16 | const agreements = allAgreements.map(event => { 17 | let formattedAmount = ethers.utils.formatUnits(event.args.amount, 18); //TODO: get the correct amount of decimals based on the token 18 | return { 19 | ...event.args, 20 | amount: formattedAmount, 21 | transactionHash: event.transactionHash 22 | }; 23 | }); 24 | 25 | return agreements; 26 | } catch (error) { 27 | console.log("error: ", error); 28 | throw new Error("Error in fetching agreements: ", error); 29 | } 30 | } 31 | 32 | export default fetchPaidAgreements; -------------------------------------------------------------------------------- /public/coins/usdt-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/dashboard/community/rewards-icon-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/dashboard/community/benefits-icon-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /contracts/ethereum/contracts/Admin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.23; 4 | 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | import "@openzeppelin/contracts/security/Pausable.sol"; 7 | 8 | contract Admin is AccessControl, Pausable { 9 | event NewAdminAdded(address indexed new_admin); 10 | event RemovedAdmin(address indexed removed_admin); 11 | event NewProfileAdded(address indexed profile); 12 | 13 | bytes32 public constant CHANGE_PARAMETERS = keccak256("CHANGE_PARAMETERS"); 14 | 15 | constructor(address admin) { 16 | _grantRole(DEFAULT_ADMIN_ROLE, admin); 17 | _grantRole(CHANGE_PARAMETERS, admin); 18 | } 19 | 20 | function addAdmin(address account) external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused() { 21 | _grantRole(DEFAULT_ADMIN_ROLE, account); 22 | emit NewAdminAdded(account); 23 | } 24 | 25 | function removeAdmin(address account) external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused() { 26 | _revokeRole(DEFAULT_ADMIN_ROLE, account); 27 | emit RemovedAdmin(account); 28 | } 29 | 30 | function pause() external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused() { 31 | _pause(); 32 | } 33 | 34 | function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) whenPaused() { 35 | _unpause(); 36 | } 37 | 38 | function addProfile(address account) external whenNotPaused() { 39 | _grantRole(CHANGE_PARAMETERS, account); 40 | emit NewProfileAdded(account); 41 | } 42 | } -------------------------------------------------------------------------------- /public/social/discord-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /__tests__/connectButton.spec.js: -------------------------------------------------------------------------------- 1 | import { act, render, screen, fireEvent, waitFor} from "@testing-library/react"; 2 | import "@testing-library/jest-dom"; 3 | import React from "react"; 4 | import App from "../src/App"; 5 | 6 | const mockEthereum = { 7 | isMetaMask: true, 8 | selectedAddress: '0x0BB56447B1e484C3486AC033a2A1DDE4f13efEF5', 9 | networkVersion: '1', 10 | request: jest.fn(() => Promise.resolve(["0x0BB56447B1e484C3486AC033a2A1DDE4f13efEF5"])), 11 | enable: jest.fn(), 12 | on: jest.fn(), 13 | removeListener: jest.fn(), 14 | }; 15 | 16 | describe("ConnectWalletButton", () => { 17 | it("displays the connect wallet button if the user has not connected their wallet", () => { 18 | window.ethereum = mockEthereum; 19 | 20 | act(() => { 21 | render(); 22 | }); 23 | 24 | const connectButton = screen.getByRole("button", { 25 | name: "Conectar carteira" 26 | }); 27 | 28 | expect(connectButton).toBeInTheDocument(); 29 | }); 30 | 31 | it("does not display the connect wallet button if the user has connected their wallet", async () => { 32 | window.ethereum = mockEthereum; 33 | 34 | act(() => { 35 | render(); 36 | }); 37 | 38 | const connectButton = screen.getByRole("button", { 39 | name: "Conectar carteira" 40 | }); 41 | fireEvent.click(connectButton); 42 | 43 | await waitFor(() => { 44 | const connectButtonPresent = screen.queryByRole("button", { 45 | name: "Conectar carteira" 46 | }); 47 | expect(connectButtonPresent).not.toBeInTheDocument(); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /pages/api/notify.js: -------------------------------------------------------------------------------- 1 | const notificationTypes = { 2 | payment: "91c35de2-ddfd-4756-a54b-3a1dc52993d4", 3 | job: "68b107bf-c219-4b86-8f64-f5b67da4ee34", 4 | agreement: "c13da13c-443c-4467-b6ce-67c470edc391", 5 | } 6 | 7 | function description(type, params) { 8 | switch (type) { 9 | case "payment": 10 | return `Payment of ${params.value} received` 11 | case "job": 12 | return "New Job Listed" 13 | case "agreement": 14 | return "New Agreement created for you" 15 | default: 16 | return "New Notification" 17 | } 18 | } 19 | export default async function handler(req, res) { 20 | const { account, type } = req.query 21 | 22 | const response = await fetch( 23 | `https://notify.walletconnect.com/${process.env.NEXT_PUBLIC_WC_PROJECT_ID}/notify`, 24 | { 25 | method: "POST", 26 | headers: { 27 | Authorization: `Bearer ${process.env.NOTIFY_API_SECRET}`, 28 | "Content-Type": "application/json", 29 | }, 30 | body: JSON.stringify({ 31 | notification: { 32 | type: notificationTypes[type], // Notification type ID copied from Cloud 33 | title: "New " + type, 34 | body: description(type, req.query), 35 | icon: "https://betapp.kyodoprotocol.xyz/logo-square.svg", // optional 36 | url: "https://betapp.kyodoprotocol.xyz", // optional 37 | }, 38 | accounts: [ 39 | `eip155:1:${account}`, // CAIP-10 account ID 40 | ], 41 | }), 42 | } 43 | ) 44 | 45 | const body = await response.text() 46 | console.log(body) 47 | res.status(200).json({}) 48 | } 49 | -------------------------------------------------------------------------------- /contracts/ethereum/scripts/createAgreements.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const { ethers } = require("hardhat"); 4 | require('dotenv').config({ path: '../../.env.development.local' }); 5 | 6 | async function kyodoRegistry(contractName) { 7 | const KyodoRegistryContract = await ethers.getContractFactory("KyodoRegistry") 8 | const kyodoRegistryContract = await KyodoRegistryContract.attach(process.env.NEXT_PUBLIC_KYODO_REGISTRY); 9 | 10 | const address = await kyodoRegistryContract.getRegistry(contractName) 11 | return address 12 | } 13 | 14 | async function main() { 15 | const AgreementContract = await ethers.getContractFactory("AgreementContract") 16 | const agreementContract = await AgreementContract.attach(await kyodoRegistry("AGREEMENT_CONTRACT")); 17 | 18 | const [deployer, developer] = await ethers.getSigners(); 19 | const paymentAmount = ethers.utils.parseUnits("100", 18) 20 | 21 | skills = [ 22 | { name: "Programming", level: 50 }, 23 | { name: "Design", level: 50 } 24 | ]; 25 | 26 | const tx = await agreementContract.connect(deployer).createAgreement( 27 | "Agreement 1 by cli test", 28 | "Description 1", 29 | developer.address, 30 | skills, 31 | paymentAmount 32 | ); 33 | 34 | await tx.wait(); 35 | 36 | console.log(`Agreement created. User: ${deployer.address} Transaction hash: ${tx.hash}`); 37 | 38 | const agreements = await agreementContract.getAllAgreements(); 39 | console.log("agreements", agreements) 40 | } 41 | 42 | main() 43 | .then(() => process.exit(0)) 44 | .catch((error) => { 45 | console.error(error); 46 | process.exit(1); 47 | }); -------------------------------------------------------------------------------- /contracts/ethereum/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kyodo-protocol-smart-contracts", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "compile": "hardhat compile", 7 | "deploy:multichain":"npx hardhat run scripts/deployMultichain.js", 8 | "deploy:agreement:local":"npx hardhat run scripts/deployAgreement.js --network testing" 9 | }, 10 | "dependencies": { 11 | "@ethersproject/hdnode": "^5.7.0", 12 | "bip39": "^3.1.0", 13 | "dotenv": "^16.0.3", 14 | "ethersjs": "^0.0.1-security", 15 | "hardhat": "^2.19.0", 16 | "hardhat-jest-plugin": "^0.0.6", 17 | "sol2uml": "^2.4.3", 18 | "solc": "^0.8.1", 19 | "solidity-docgen": "^0.5.16" 20 | }, 21 | "devDependencies": { 22 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", 23 | "@nomicfoundation/hardhat-network-helpers": "^1.0.9", 24 | "@nomicfoundation/hardhat-toolbox": "^2.0.1", 25 | "@nomiclabs/hardhat-ethers": "^2.2.3", 26 | "@nomiclabs/hardhat-etherscan": "^3.1.7", 27 | "@openzeppelin/contracts": "^4.9.3", 28 | "@typechain/ethers-v5": "^10.2.1", 29 | "@typechain/hardhat": "^6.1.6", 30 | "@types/chai": "^4.3.6", 31 | "@types/mocha": "^10.0.2", 32 | "chai": "^4.3.9", 33 | "ethers": "^5.7.0", 34 | "hardhat-contract-sizer": "^2.10.0", 35 | "hardhat-deploy": "^0.11.43", 36 | "hardhat-deploy-ethers": "^0.4.1", 37 | "hardhat-gas-reporter": "^1.0.9", 38 | "slc-0.8": "npm:solc@^0.8.1", 39 | "solc-0.8": "npm:solc@^0.8.23", 40 | "typechain": "^8.3.1", 41 | "typescript": "^5.2.2" 42 | } 43 | } -------------------------------------------------------------------------------- /chains/transactionManager.js: -------------------------------------------------------------------------------- 1 | import solTransactions from "./solana/transactions" 2 | import ethTransactions from "./ethereum/transactions" 3 | import contractManager from './ContractManager'; 4 | 5 | class TransactionManager { 6 | constructor() { 7 | this.chains = {} 8 | this.supportedNetworks = contractManager.supportedNetworks 9 | 10 | // Initialize transactions based on chain type 11 | for (const chain of this.supportedNetworks) { 12 | this.chains[chain] = ethTransactions; 13 | } 14 | this.chains["solana"] = solTransactions; 15 | } 16 | 17 | async handleTransactionPromise(chain, ...args) { 18 | return this.chains[chain].handleTransactionPromise(...args); 19 | } 20 | 21 | async addAgreement(chain, ...args) { 22 | return this.chains[chain].addAgreement(...args); 23 | } 24 | 25 | async fetchAgreements(chain, ...args) { 26 | return this.chains[chain].fetchAgreements(...args); 27 | } 28 | 29 | async payAgreement(chain, ...args) { 30 | return this.chains[chain].payAgreement(...args); 31 | } 32 | 33 | async fetchUserBalances(chain, ...args) { 34 | return this.chains[chain].fetchUserBalances(...args); 35 | } 36 | 37 | async withdrawFromVault(chain, ...args) { 38 | return this.chains[chain].withdrawFromVault(...args); 39 | } 40 | 41 | async saveUserInfo(chain, ...args) { 42 | return this.chains[chain].saveUserInfo(...args); 43 | } 44 | 45 | async fetchPaidAgreements(chain, ...args) { 46 | return this.chains[chain].fetchPaidAgreements(...args); 47 | } 48 | 49 | async fetchUserInfo(chain, ...args) { 50 | return this.chains[chain].fetchUserInfo(...args); 51 | } 52 | } 53 | 54 | const manager = new TransactionManager(); 55 | export default manager; 56 | -------------------------------------------------------------------------------- /styles/footer.scss: -------------------------------------------------------------------------------- 1 | /** 2 | GLOBAL 3 | =========================== 4 | */ 5 | 6 | footer { 7 | width: 100%; 8 | height: 50px; 9 | position: fixed; 10 | bottom: 0; 11 | left: 0; 12 | background-color: $dark-gray; 13 | font-size: $font-medium; 14 | z-index: 600; 15 | @extend %clearfix; 16 | 17 | @media #{$smartphones-1} { 18 | height: auto; 19 | left: 0; 20 | bottom: 0; 21 | top: 210px; 22 | width: 200px; 23 | z-index: 9999; 24 | box-sizing: border-box; 25 | background: linear-gradient(110deg, rgba(6, 6, 6, .99) 0%, rgba(47, 47, 47, .98) 100%); 26 | animation: tracking-in-expand 1s ease-in-out; 27 | padding-left: 10px; 28 | padding-top: 10px; 29 | } 30 | 31 | p { 32 | margin: 18px 0 0 0; 33 | float: left; 34 | 35 | @media #{$smartphones-1} { 36 | float: none; 37 | } 38 | 39 | img { 40 | float: left; 41 | margin: -6px 12px 0 0; 42 | } 43 | } 44 | 45 | ul { 46 | float: right; 47 | 48 | @media #{$smartphones-1} { 49 | border-top: 1px solid rgba($white, .1); 50 | float: none; 51 | margin-top: 27px; 52 | padding-top: 15px; 53 | } 54 | } 55 | 56 | li { 57 | list-style-type: none; 58 | margin-left: 26px; 59 | display: inline-block; 60 | line-height: 50px; 61 | 62 | @media #{$smartphones-1} { 63 | display: block; 64 | margin-left: 0; 65 | line-height: 25px; 66 | } 67 | } 68 | 69 | a { 70 | color: $normal-gray; 71 | text-decoration: none; 72 | 73 | &:hover { 74 | opacity: .9; 75 | } 76 | } 77 | 78 | .web3dev-link { 79 | color: $light-gray; 80 | 81 | &:hover { 82 | color: $white; 83 | opacity: 1; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /styles/variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | VARS AND MIXINS 3 | =========================== 4 | */ 5 | 6 | @import url('https://fonts.googleapis.com/css2?family=Geologica:wght@400;600;700&display=swap'); 7 | 8 | 9 | $sans-serif: "Geologica", Arial, Helvetica, sans-serif; 10 | $headings: "Geologica", Arial, Helvetica, sans-serif; 11 | 12 | 13 | /* COLORS */ 14 | 15 | // Basics 16 | $white: #ffffff; 17 | $black: #000000; 18 | $red: #BE5757; 19 | $green: #87BB5F; 20 | $orange: #CB924E; 21 | 22 | // Branding 23 | $brand-green: #A3C987; 24 | $brand-red: #F69783; 25 | $brand-blue: #78BCEE; 26 | 27 | // Blues 28 | $dark-blue: #060A0F; 29 | $dark-blue-2: #1C2124; 30 | $medium-blue: #2B526B; 31 | 32 | // Grays 33 | $light-gray: #CCDDE2; 34 | $medium-gray: #85A8BE; 35 | $normal-gray: #777777; 36 | 37 | // Dark mode 38 | $dark-gray: #060606; 39 | $dark-gray-2: #111111; 40 | 41 | 42 | /* FONT SIZES */ 43 | 44 | $font-verysmall: 10px; 45 | $font-small: 11px; 46 | $font-small-2: 12px; 47 | 48 | $font-medium: 13px; 49 | $font-medium-2: 15px; 50 | $font-medium-3: 17px; 51 | 52 | $font-large: 19px; 53 | $font-large-2: 21px; 54 | $font-large-3: 23px; 55 | 56 | $font-big: 25px; 57 | $font-big-2: 27px; 58 | $font-big-3: 30px; 59 | $font-big-4: 33px; 60 | 61 | $font-bigger: 37px; 62 | $font-bigger-2: 45px; 63 | 64 | 65 | /* BREAKPOINTS */ 66 | 67 | $tablets: "only screen and (max-width: 1000px)"; 68 | $smartphones-1: "only screen and (max-width: 800px)"; 69 | 70 | 71 | /* MIXINS */ 72 | 73 | %clearfix { 74 | &:after { 75 | content: ""; 76 | display: table; 77 | clear: both; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kyodo-protocol", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@coral-xyz/anchor": "^0.28.1-beta.2", 13 | "@coral-xyz/spl-token": "^0.28.1-beta.2", 14 | "@project-serum/anchor": "^0.26.0", 15 | "@solana/spl-token": "^0.3.8", 16 | "@solana/wallet-adapter-base": "^0.9.23", 17 | "@solana/wallet-adapter-react": "^0.15.35", 18 | "@solana/wallet-adapter-react-ui": "^0.9.34", 19 | "@solana/wallet-adapter-sollet": "^0.11.17", 20 | "@solana/wallet-adapter-wallets": "^0.19.22", 21 | "@solana/web3.js": "^1.78.5", 22 | "@web3inbox/core": "^0.0.22", 23 | "@web3inbox/widget-react": "^0.6.25", 24 | "@web3modal/wagmi": "^3.2.0", 25 | "dotenv": "^16.0.3", 26 | "ethers": "^5.7.2", 27 | "i18next": "^23.5.1", 28 | "i18next-browser-languagedetector": "^7.1.0", 29 | "i18next-http-backend": "^2.2.2", 30 | "next": "^13.4.19", 31 | "react": "^18.2.0", 32 | "react-dom": "18.2.0", 33 | "react-i18next": "^13.2.2", 34 | "react-icons": "^4.10.1", 35 | "react-spinners": "^0.13.8", 36 | "viem": "^1.18.1", 37 | "wagmi": "^1.4.5", 38 | "yup": "^1.3.0" 39 | }, 40 | "devDependencies": { 41 | "@testing-library/jest-dom": "^5.16.5", 42 | "@testing-library/react": "^14.0.0", 43 | "@testing-library/user-event": "^13.5.0", 44 | "eslint": "8.48.0", 45 | "eslint-config-next": "13.4.19", 46 | "identity-obj-proxy": "^3.0.0", 47 | "jest": "^29.4.1", 48 | "jest-html-reporter": "^3.7.0", 49 | "react-test-renderer": "^18.2.0", 50 | "sass": "^1.66.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /chains/ethereum/transactions.js: -------------------------------------------------------------------------------- 1 | import addAgreement from "./transactions/addAgreement"; 2 | import fetchAgreements from "./transactions/fetchAgreements"; 3 | import payAgreement from "./transactions/payAgreement"; 4 | import fetchUserBalances from "./transactions/fetchUserBalances"; 5 | import withdrawFromVault from "./transactions/withdrawFromVault"; 6 | import fetchPaidAgreements from "./transactions/fetchPaidAgreements"; 7 | import saveUserInfo from "./transactions/saveUserInfo"; 8 | import fetchUserInfo from "./transactions/fetchUserInfo"; 9 | 10 | const EVENT_TIMEOUT = 60000; 11 | 12 | async function handleTransactionPromise(contract, txResponse, eventName, account) { 13 | // TODO: Make event filters more robust based on parameters 14 | const eventReceived = new Promise((resolve, reject) => { 15 | const timeout = setTimeout(async () => { 16 | const tx = await contract.provider.getTransaction(txResponse.hash); 17 | if (tx && tx.blockNumber) { 18 | resolve({ event: tx }); 19 | } else { 20 | reject(new Error(`Timeout waiting for ${eventName} event`)); 21 | } 22 | }, EVENT_TIMEOUT); 23 | 24 | const filter = contract.filters[eventName](); 25 | 26 | contract.on(filter, (...args) => { 27 | clearTimeout(timeout); 28 | resolve(args); 29 | }); 30 | }); 31 | 32 | const eventArgs = await eventReceived; 33 | return eventArgs; 34 | } 35 | 36 | const transactions = { 37 | handleTransactionPromise, 38 | addAgreement, 39 | fetchAgreements, 40 | payAgreement, 41 | fetchUserBalances, 42 | withdrawFromVault, 43 | fetchPaidAgreements, 44 | saveUserInfo, 45 | fetchUserInfo 46 | } 47 | export default transactions -------------------------------------------------------------------------------- /chains/solana/transactions.js: -------------------------------------------------------------------------------- 1 | import addAgreement from "./transactions/addAgreement"; 2 | import fetchAgreements from "./transactions/fetchAgreements"; 3 | import payAgreement from "./transactions/payAgreement"; 4 | import fetchUserBalances from "./transactions/fetchUserBalances"; 5 | import fetchPaidAgreements from "./transactions/fetchPaidAgreements"; 6 | import withdrawFromVault from "./transactions/withdrawFromVault"; 7 | 8 | async function handleTransactionPromise(contract, txResponse) { 9 | const EVENT_TIMEOUT = 30000; // 30 seconds timeout 10 | const RETRY_INTERVAL = 1000; // 1 second retry interval 11 | 12 | const checkTransaction = async () => { 13 | const result = await contract.provider.connection.getTransaction(txResponse, { 14 | commitment: 'confirmed', 15 | }); 16 | 17 | if (result && !result.meta?.err) { 18 | return true; 19 | } 20 | 21 | return false; // Return false instead of throwing an error 22 | }; 23 | 24 | const startTime = Date.now(); 25 | 26 | while (Date.now() - startTime < EVENT_TIMEOUT) { 27 | const success = await checkTransaction(); 28 | if (success) { 29 | return true; 30 | } 31 | 32 | // Wait for the retry interval before trying again 33 | await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL)); 34 | } 35 | 36 | throw new Error('Transaction failed on Solana'); // Throw an error if the timeout is reached without success 37 | } 38 | 39 | 40 | const transactions = { handleTransactionPromise, 41 | addAgreement, 42 | fetchAgreements, 43 | payAgreement, 44 | fetchUserBalances, 45 | fetchPaidAgreements, 46 | withdrawFromVault 47 | } 48 | export default transactions -------------------------------------------------------------------------------- /contracts/ethereum/contracts/testToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.23; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/access/AccessControl.sol"; 6 | import "@openzeppelin/contracts/security/Pausable.sol"; 7 | 8 | contract fakeStable is ERC20, AccessControl, Pausable { 9 | uint8 private _decimals; 10 | 11 | event NewAdminAdded(address indexed new_admin); 12 | event RemovedAdmin(address indexed removed_admin); 13 | 14 | constructor(uint256 initialSupply, uint8 decimals) ERC20("fakeStable", "TSTBL") { 15 | _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); 16 | _mint(msg.sender, initialSupply); 17 | _decimals = decimals; 18 | } 19 | 20 | function mint(address account, uint256 amount) external onlyRole(DEFAULT_ADMIN_ROLE) { 21 | _mint(account, amount); 22 | } 23 | 24 | function burn(address account, uint256 amount) external onlyRole(DEFAULT_ADMIN_ROLE) { 25 | _burn(account, amount); 26 | } 27 | 28 | function addAdmin(address account) external onlyRole(DEFAULT_ADMIN_ROLE) { 29 | _grantRole(DEFAULT_ADMIN_ROLE, account); 30 | emit NewAdminAdded(account); 31 | } 32 | 33 | function removeAdmin(address account) external onlyRole(DEFAULT_ADMIN_ROLE) { 34 | _revokeRole(DEFAULT_ADMIN_ROLE, account); 35 | emit RemovedAdmin(account); 36 | } 37 | 38 | function pause() external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused() { 39 | _pause(); 40 | } 41 | 42 | function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) whenPaused() { 43 | _unpause(); 44 | } 45 | 46 | function decimals() public view override returns(uint8){ 47 | return _decimals; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/ethereum/contracts/interfaces/IKyodoRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.1; 3 | 4 | interface IKyodoRegistry { 5 | 6 | // Events 7 | event RegistryCreated(bytes32 indexed key, address value, uint blockNumber); 8 | event RegistryUpdated(bytes32 indexed key, address value, uint blockNumber); 9 | 10 | /** 11 | * @notice Creates a new registry entry. 12 | * @param registry The name of the registry to create. 13 | * @param value The address to be associated with the registry. 14 | * @param blockNumber The block number at which the registry is created. 15 | */ 16 | function createRegistry(string memory registry, address value, uint blockNumber) external; 17 | 18 | /** 19 | * @notice Updates an existing registry entry. 20 | * @param registry The name of the registry to update. 21 | * @param value The new address to be associated with the registry. 22 | * @param blockNumber The block number at which the registry is updated. 23 | */ 24 | function updateRegistry(string memory registry, address value, uint blockNumber) external; 25 | 26 | /** 27 | * @notice Retrieves the address associated with a registry. 28 | * @param registry The name of the registry to query. 29 | * @return address The address associated with the given registry. 30 | */ 31 | function getRegistry(string memory registry) external view returns (address); 32 | 33 | /** 34 | * @notice Retrieves the block number at which a registry was created or updated. 35 | * @param registry The name of the registry to query. 36 | * @return uint The block number of the registry creation or update. 37 | */ 38 | function getBlockDeployment(string memory registry) external view returns (uint); 39 | } 40 | -------------------------------------------------------------------------------- /chains/ethereum/transactions/fetchAgreements.js: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | function transformAgreementData(agreement, skills) { 4 | const skillNames = skills.map(skill => skill.name); 5 | 6 | return { 7 | id: agreement.id, 8 | title: agreement.title, 9 | description: agreement.description, 10 | professional: agreement.professional?.toString(), 11 | company: agreement.company?.toString(), 12 | skills: skillNames, 13 | amount: ethers.utils.formatUnits(agreement.paymentAmount, 18), 14 | totalPaid: ethers.utils.formatUnits(agreement.totalPaid, 18), 15 | fee: agreement.fee 16 | }; 17 | } 18 | 19 | export const fetchAgreements = async (details) => { 20 | try { 21 | const contractorAgreementIds = await details.contract.getContractorAgreementIds(details.account); 22 | const professionalAgreementIds = await details.contract.getProfessionalAgreementIds(details.account); 23 | 24 | const userAgreementIds = Array.from(new Set([...contractorAgreementIds, ...professionalAgreementIds])); 25 | 26 | if (userAgreementIds.length === 0) return null; 27 | 28 | const stringIds = userAgreementIds.map((id) => id.toString()); 29 | 30 | const fetchedAgreements = await Promise.all( 31 | stringIds.map(async (agreementId) => { 32 | const agreement = await details.contract.getAgreementById(agreementId); 33 | const agreementSkills = await details.contract.getSkillsByAgreementId(agreementId); 34 | const transformedAgreement = transformAgreementData(agreement, agreementSkills); 35 | return { 36 | ...transformedAgreement 37 | }; 38 | }) 39 | ); 40 | 41 | return fetchedAgreements; 42 | } catch (error) { 43 | console.error("Error when fetching agreements:", error); 44 | } 45 | }; 46 | 47 | 48 | export default fetchAgreements; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | App - Kyōdō Protocol 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /chains/ethereum/transactions/withdrawFromVault.js: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import ERC20Token from '../utils/ERC20Token'; 3 | 4 | const handleWithdrawal = async (user, amount, asset, account) => { 5 | if (user.toLowerCase() == account.toLowerCase()) { 6 | if (!localStorage.getItem(asset)) { 7 | const tokenContract = new ERC20Token(asset) 8 | const symbol = await tokenContract.symbol() 9 | const decimals = await tokenContract.decimals() 10 | 11 | await window.ethereum.request({ 12 | method: "wallet_watchAsset", 13 | params: { 14 | type: "ERC20", 15 | options: { 16 | address: asset, 17 | symbol: symbol, 18 | decimals: decimals, 19 | }, 20 | }, 21 | }) 22 | localStorage.setItem(asset, "saved") 23 | return () => { 24 | details.contract.off("Withdrawal") 25 | } 26 | } 27 | } 28 | } 29 | 30 | export const withdrawFromVault = async (details) => { 31 | const redeemAmountInWei = ethers.utils.parseUnits(details.amount.toString(), details.balance.tokenDecimals) 32 | const balanceInWei = ethers.utils.parseUnits(details.balance.amount, details.balance.tokenDecimals) 33 | if (redeemAmountInWei.gt(balanceInWei)) { 34 | alert("You cannot redeem more than your balance!") 35 | setRedeemValue("") 36 | } 37 | 38 | try { 39 | const tx = await details.contract.withdraw( 40 | redeemAmountInWei, 41 | details.KyodoRegistry.getRegistry("FAKE_STABLE") // TODO: Make user select the token desired to withdraw 42 | ) 43 | 44 | details.contract.on("Withdrawal", (user, amount, asset) => handleWithdrawal(user, amount, asset, details.account)); 45 | 46 | return tx 47 | } catch (error) { 48 | throw new Error("Error in withdrawFromVault: ", error); 49 | } 50 | }; 51 | 52 | export default withdrawFromVault; -------------------------------------------------------------------------------- /chains/solana/transactions/addAgreement.js: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | 4 | export const addAgreement = async (details) => { 5 | const companyAccount = details.publicKey 6 | const professionalPubkey = new PublicKey(details.professional) 7 | 8 | const agreementData = { 9 | title: details.title, 10 | description: details.description, 11 | skills: details.skills, 12 | paymentAmount: new anchor.BN(details.paymentAmount), 13 | professional: professionalPubkey, 14 | company: companyAccount, 15 | } 16 | 17 | const stringBuffer = Buffer.from("company_agreements", "utf-8"); 18 | const [companyAgreementsPublicKey, _] = anchor.web3.PublicKey.findProgramAddressSync( 19 | [stringBuffer, companyAccount.toBuffer()], 20 | details.contract.programId 21 | ); 22 | 23 | const stringProfessionalBuffer = Buffer.from("professional_agreements", "utf-8"); 24 | const [professionalAgreementsPublicKey, __] = anchor.web3.PublicKey.findProgramAddressSync( 25 | [stringProfessionalBuffer, professionalPubkey.toBuffer()], 26 | details.contract.programId 27 | ); 28 | 29 | try { 30 | const agreementAddress = anchor.web3.Keypair.generate(); 31 | 32 | const tx = await details.contract.methods 33 | .initializeAgreement(agreementData) 34 | .accounts({ 35 | agreement: agreementAddress.publicKey, 36 | company: companyAccount, 37 | professional:professionalPubkey, 38 | professionalAgreements: professionalAgreementsPublicKey, 39 | companyAgreements: companyAgreementsPublicKey, 40 | systemProgram: anchor.web3.SystemProgram.programId, 41 | }).signers([agreementAddress]).rpc(); 42 | 43 | console.log("tx: ", tx); 44 | return tx; 45 | 46 | } catch (error) { 47 | console.error("Error initializing agreement:", error); 48 | } 49 | }; 50 | 51 | export default addAgreement; -------------------------------------------------------------------------------- /contracts/ethereum/contracts/KyodoRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.23; 3 | 4 | import "./Admin.sol"; 5 | import "./interfaces/IKyodoRegistry.sol"; 6 | 7 | contract KyodoRegistry is Admin, IKyodoRegistry { 8 | mapping(bytes32 => address) private addressRegistry; 9 | mapping(bytes32 => uint) private blockDeployment; 10 | 11 | constructor(address admin) Admin(admin) {} 12 | 13 | function createRegistry(string memory registry, address value, uint blockNumber) external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused() { 14 | bytes32 key = keccak256(abi.encodePacked(registry)); 15 | require(addressRegistry[key] == address(0), 'The registry already exists'); 16 | addressRegistry[key] = value; 17 | blockDeployment[key] = blockNumber; 18 | emit RegistryCreated(key, value, blockNumber); 19 | } 20 | 21 | function updateRegistry(string memory registry, address value, uint blockNumber) external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused() { 22 | bytes32 key = keccak256(abi.encodePacked(registry)); 23 | require(addressRegistry[key] != address(0), 'Registry does not exists'); 24 | addressRegistry[key] = value; 25 | blockDeployment[key] = blockNumber; 26 | emit RegistryUpdated(key, value, blockNumber); 27 | } 28 | 29 | function getRegistry(string memory registry) external override view returns (address) { 30 | bytes32 key = keccak256(abi.encodePacked(registry)); 31 | address registeredAddress = addressRegistry[key]; 32 | require(registeredAddress != address(0), 'Registry does not exists'); 33 | return registeredAddress; 34 | } 35 | 36 | function getBlockDeployment(string memory registry) external override view returns (uint) { 37 | bytes32 key = keccak256(abi.encodePacked(registry)); 38 | uint blockNumber = blockDeployment[key]; 39 | return blockNumber; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /hooks/useTransactionHandler_hook.md: -------------------------------------------------------------------------------- 1 | ### 1. `useTransactionHandler` function: 2 | 3 | This is a custom React hook that encapsulates the logic for handling blockchain transactions. Here's a step-by-step breakdown: 4 | 5 | - It initializes state variables using the `useState` hook to manage the transaction's lifecycle (e.g., loading state, success, pending, failure, etc.). 6 | - It retrieves the user's account and selected blockchain chain from the `useAccount` hook. 7 | - It also retrieves the contract instance from the `useAgreementContract` hook. 8 | - The `sendTransaction` function is defined using the `useCallback` hook to ensure that it doesn't get recreated on every render. This function handles the process of sending a transaction and waiting for its confirmation: 9 | 10 | 1. It sets the initial states (loading, no transaction failures, etc.). 11 | 2. It defines timeouts for the transaction and for waiting for an event. 12 | 3. Using `Promise.race`, it either sends the transaction (via `transactionManager`) or rejects if the transaction takes too long. 13 | 4. If the transaction is sent successfully and its hash is available, it sets up an event listener to wait for the transaction's confirmation. 14 | 5. If the transaction confirmation event is received within the timeout, it sets the success state and calls the provided `onConfirmation` callback. 15 | 6. If there's an error (e.g., timeout or other issues), it handles the error and sets the appropriate state variables. 16 | 7. Finally, it resets the loading state after a 3-second delay. 17 | 18 | - The hook returns various state variables and the `sendTransaction` function so that they can be used in the component that utilizes this hook. 19 | 20 | In essence, this code provides a way to add an agreement to the blockchain and manage the transaction's lifecycle, including loading, success, failure, and pending states. The custom hook (`useTransactionHandler`) abstracts away the details of sending a transaction, listening for its confirmation, and updating the relevant states, allowing for clean and reusable code in React components. -------------------------------------------------------------------------------- /chains/solana/transactions/fetchAgreements.js: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | 4 | function lamportsToSol(lamports) { 5 | return Math.round(lamports.toString() / Math.pow(10, 8)).toString(); 6 | } 7 | 8 | function transformAgreementData(agreement) { 9 | return { 10 | title: agreement.title, 11 | description: agreement.description, 12 | status: agreement.status, 13 | professional: agreement.professional?.toString(), 14 | company: agreement.company?.toString(), 15 | skills: agreement.skills, 16 | amount: agreement.paymentAmount.toString(), 17 | totalPaid: lamportsToSol(agreement.totalPaid) 18 | }; 19 | } 20 | 21 | 22 | export const fetchAgreements = async (details) => { 23 | const company = new PublicKey(details.account) 24 | const program = details.contract 25 | 26 | try { 27 | const stringBuffer = Buffer.from("company_agreements", "utf-8"); 28 | 29 | const [companyAgreementsPublicKey, _] = anchor.web3.PublicKey.findProgramAddressSync( 30 | [stringBuffer, company.toBuffer()], 31 | program.programId 32 | ); 33 | 34 | const fetchedCompanyAgreements = await program.account.companyAgreements.fetch( 35 | companyAgreementsPublicKey 36 | ); 37 | 38 | if (!fetchedCompanyAgreements.agreements || fetchedCompanyAgreements.agreements.length === 0) return null; 39 | 40 | const fetchedAgreements = await Promise.all( 41 | fetchedCompanyAgreements.agreements.map(async (agreementAddress) => { 42 | const agreement = await program.account.agreementAccount.fetch(agreementAddress); 43 | 44 | const agreementWithPubKey = { 45 | ...transformAgreementData(agreement), 46 | publicKey: agreementAddress 47 | }; 48 | 49 | return agreementWithPubKey; 50 | }) 51 | ); 52 | 53 | return fetchedAgreements; 54 | 55 | } catch (error) { 56 | console.error("Error when fetching agreements:", error); 57 | } 58 | } 59 | 60 | export default fetchAgreements; -------------------------------------------------------------------------------- /public/agreement-title-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /contracts/ethereum/scripts/payAgreement.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | require('dotenv').config({ path: '../../.env.development.local' }); 3 | 4 | async function kyodoRegistry(contractName) { 5 | const KyodoRegistryContract = await ethers.getContractFactory("KyodoRegistry") 6 | const kyodoRegistryContract = await KyodoRegistryContract.attach(process.env.NEXT_PUBLIC_KYODO_REGISTRY); 7 | 8 | const address = await kyodoRegistryContract.getRegistry(contractName) 9 | return address 10 | } 11 | 12 | async function payUserAgreement() { 13 | const [signer] = await ethers.getSigners(); 14 | const AgreementContract = await ethers.getContractFactory("AgreementContract"); 15 | const agreementContract = await AgreementContract.attach(kyodoRegistry("AGREEMENT_CONTRACT")); 16 | 17 | let userAgreements = await agreementContract.connect(signer).getContractorAgreementIds(signer.address); 18 | userAgreements = userAgreements.map(id => id.toString()); 19 | 20 | if (userAgreements.length === 0) { 21 | console.log("Nenhum acordo encontrado para o usuário."); 22 | return; 23 | } 24 | 25 | const firstAgreementId = userAgreements[0]; 26 | const agreementDetails = await agreementContract.getAgreementById(firstAgreementId); 27 | const paymentAmount = ethers.BigNumber.from(agreementDetails.paymentAmount); 28 | 29 | const protocolFee = await agreementContract.getFee(); 30 | const totalFeeAmount = paymentAmount.mul(protocolFee).div(1000); 31 | 32 | const totalAmountIncludingFee = paymentAmount.add(totalFeeAmount); 33 | 34 | const TokenContract = await ethers.getContractFactory("fakeStable"); 35 | const tokenContract = await TokenContract.attach(kyodoRegistry("FAKE_STABLE")); 36 | 37 | await tokenContract.connect(signer).approve(agreementContract.address, totalAmountIncludingFee); 38 | 39 | await agreementContract.connect(signer).makePayment([firstAgreementId], [paymentAmount], tokenContract.address); 40 | 41 | console.log(`Pagamento de ${totalAmountIncludingFee} tokens feito para o acordo com ID ${firstAgreementId}.`); 42 | } 43 | 44 | 45 | payUserAgreement() 46 | .then(() => process.exit(0)) 47 | .catch(error => { 48 | console.error(error); 49 | process.exit(1); 50 | }); 51 | -------------------------------------------------------------------------------- /chains/ethereum/contracts.js: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import KyodoRegistry from './abis/KyodoRegistry.json'; 3 | import AgreementContract from './abis/AgreementContract.json'; 4 | import VaultContract from './abis/StableVault.json'; 5 | 6 | const REGISTRY_CONTRACT_ADDRESS = process.env.NEXT_PUBLIC_KYODO_REGISTRY 7 | 8 | function getRegistryContract() { 9 | try { 10 | const provider = new ethers.providers.Web3Provider(window.ethereum); 11 | return new ethers.Contract(REGISTRY_CONTRACT_ADDRESS, KyodoRegistry.abi, provider.getSigner()); 12 | } catch (e) { 13 | console.error("Failed to instantiate the registry contract", e); 14 | } 15 | } 16 | 17 | async function getContractInstance(abi, contractName) { 18 | try { 19 | const provider = new ethers.providers.Web3Provider(window.ethereum); 20 | const registryContract = getRegistryContract(); 21 | 22 | const contractAddress = await registryContract.getRegistry(contractName); 23 | 24 | return new ethers.Contract(contractAddress, abi, provider.getSigner()); 25 | } catch (e) { 26 | console.log("failed to instantiate contract") 27 | console.log(e) 28 | } 29 | } 30 | 31 | const contracts = { 32 | kyodoRegistry: getRegistryContract(), 33 | agreementContract: () => getContractInstance(AgreementContract.abi, "AGREEMENT_CONTRACT"), 34 | vaultContract: () => getContractInstance(VaultContract.abi, "VAULT_CONTRACT"), 35 | 36 | verify: async () => { 37 | const TEST_NETWORKS = ["0x7A69", "31337", "534351"] 38 | const testEnv = process.env.NODE_ENV === "development" 39 | const chainId = window.ethereum.networkVersion 40 | 41 | if (testEnv) { 42 | if (!TEST_NETWORKS.includes(chainId)) { 43 | await window.ethereum.request({ 44 | method: "wallet_addEthereumChain", 45 | params: [ 46 | { 47 | chainId: TEST_NETWORKS[0], 48 | rpcUrls: ["http://localhost:8545"], 49 | chainName: "Hardhat", 50 | nativeCurrency: { 51 | name: "ETH", 52 | symbol: "HETH", 53 | decimals: 18, 54 | }, 55 | }, 56 | ], 57 | }) 58 | } 59 | } 60 | }, 61 | }; 62 | 63 | export default contracts; 64 | -------------------------------------------------------------------------------- /public/no-agreement-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /contracts/ethereum/test/AgreementContract/AgreementContract.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers } = require("hardhat"); 3 | require('dotenv').config({ path: '../../.env.development.local' }); 4 | 5 | const FAKE_STABLE_ADDRESS = process.env.NEXT_PUBLIC_FAKE_STABLE_ADDRESS 6 | const PROTOCOL_FEE = 10; // 1% in 1000 basis points 7 | 8 | describe("AgreementContract", function () { 9 | let agreementContract; 10 | let developer; 11 | let skills; 12 | 13 | beforeEach(async () => { 14 | const AgreementContract = await ethers.getContractFactory("AgreementContract"); 15 | const {deployer, kyodoTreasury} = await getNamedAccounts(); 16 | agreementContract = await AgreementContract.deploy(kyodoTreasury, PROTOCOL_FEE, deployer); 17 | await agreementContract.deployed(); 18 | 19 | await agreementContract.addAcceptedPaymentToken(FAKE_STABLE_ADDRESS); 20 | 21 | [developer, addr1] = await ethers.getSigners(); 22 | 23 | skills = [ 24 | { name: "Programming", level: 50 }, 25 | { name: "Design", level: 50 } 26 | ]; 27 | }); 28 | 29 | it("Should create a new agreement with authorized tokens", async function () { 30 | const paymentAmount = ethers.utils.parseEther("5"); 31 | 32 | await agreementContract.connect(developer).createAgreement( 33 | "Test Agreement", 34 | "This is a test agreement", 35 | addr1.address, 36 | skills, 37 | paymentAmount, 38 | ); 39 | 40 | const agreementCount = await agreementContract.getAgreementCount(); 41 | expect(agreementCount).to.equal(1); 42 | }); 43 | 44 | it("Should fail if the professional is the same as company", async function () { 45 | const paymentAmount = ethers.utils.parseEther("5"); 46 | 47 | await expect(agreementContract.connect(developer).createAgreement( 48 | "Test Agreement", 49 | "This is a test agreement", 50 | developer.address, 51 | skills, 52 | paymentAmount, 53 | )).to.be.revertedWith("Professional address cannot be the same as company") 54 | }); 55 | 56 | it("Should correctly return the protocol fee", async function () { 57 | // Chamando a função getFee e verificando o resultado 58 | const fee = await agreementContract.getFee(); 59 | expect(fee).to.equal(PROTOCOL_FEE); 60 | }); 61 | }); -------------------------------------------------------------------------------- /public/dashboard/community/agreements-icon-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /chains/ethereum/transactions/payAgreement.js: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import ERC20Token from '../utils/ERC20Token'; 3 | 4 | const checkAllowance = async (userAddress, contractAddress, selectedToken) => { 5 | try { 6 | const tokenContract = new ERC20Token(selectedToken) 7 | const allowance = await tokenContract.allowance(userAddress, contractAddress) 8 | return ethers.BigNumber.from(allowance.toString()) 9 | } catch (error) { 10 | console.error("Error to check allowance: ", error) 11 | } 12 | } 13 | 14 | const handleApprove = async (totalAmountInWei, spender, selectedPaymentToken) => { 15 | try { 16 | const tokenContract = new ERC20Token(selectedPaymentToken.address); 17 | const tx = await tokenContract.approve(spender, totalAmountInWei); 18 | await tx.wait(); 19 | 20 | console.log(`Approval successful for total amount: ${ethers.utils.formatUnits(totalAmountInWei, selectedPaymentToken.decimals)}`); 21 | } catch (error) { 22 | console.error("Error approving token:", error); 23 | } 24 | } 25 | 26 | 27 | export const payAgreement = async (details) => { 28 | try { 29 | const paymentAmountInWei = ethers.utils.parseUnits( 30 | details.paymentValue.toString(), 31 | details.selectedPaymentToken.decimals 32 | ); 33 | const feePercentage = details.agreement.fee; 34 | const feeAmountInWei = paymentAmountInWei.mul(feePercentage).div(1000); 35 | const totalAmountIncludingFeeInWei = paymentAmountInWei.add(feeAmountInWei); 36 | 37 | const userAllowance = await checkAllowance(details.account, details.contract.address, details.selectedPaymentToken.address); 38 | if (!userAllowance.gte(totalAmountIncludingFeeInWei)) { 39 | await handleApprove(totalAmountIncludingFeeInWei, details.contract.address, details.selectedPaymentToken); 40 | } 41 | 42 | const tx = await details.contract.makePayment( 43 | [details.agreement.id], 44 | [paymentAmountInWei], 45 | details.selectedPaymentToken.address 46 | ); 47 | 48 | return tx; 49 | } catch (error) { 50 | console.error("Error in payAgreement:", error); 51 | throw error; 52 | } 53 | }; 54 | 55 | 56 | export default payAgreement; -------------------------------------------------------------------------------- /public/onboarding/community-call-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/coins/usdc-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/dashboard/community/social-icon-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/dashboard/contractor/social-icon-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/dashboard/professional/social-icon-red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/dashboard/community/members-icon-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /contracts/solana/README.md: -------------------------------------------------------------------------------- 1 | # Adding Solana Support to the Project 2 | 3 | This guide provides step-by-step instructions for adding Solana support to your project, including installing Anchor, setting up a local Solana node, and updating various configurations. 4 | 5 | ## Prerequisites 6 | 7 | - Ensure that you have a suitable development environment set up for Solana projects, including Rust, Node.js, and npm. 8 | 9 | ## Installing Anchor 10 | 11 | Anchor is a framework for developing smart contracts on Solana. Follow the Anchor installation tutorial to install Anchor and the main dependencies for Solana projects: 12 | 13 | [Anchor Installation](https://www.anchor-lang.com/docs/installation#install-using-pre-build-binary-on-x86-64-linux) 14 | 15 | ### Installing TypeScript and ts-node 16 | 17 | Before running the scripts, ensure that TypeScript and ts-node are installed globally on your system. These packages are essential for executing TypeScript files directly. Run the following command to install them: 18 | 19 | ```sh 20 | npm install -g typescript ts-node 21 | ``` 22 | 23 | This command installs TypeScript and ts-node globally, allowing you to use the `tsc` and `ts-node` commands from the terminal. 24 | 25 | --- 26 | 27 | ## Configuring Key Pair 28 | 29 | Before interacting with Solana, make sure you have a key pair set up. You can create a new key pair using the following command: 30 | 31 | ```sh 32 | solana-keygen new 33 | ``` 34 | 35 | ## Wallet and Environment Configuration 36 | 37 | 1. Place the path of the wallet generated by `solana-keygen` into `ANCHOR_WALLET` in your `.env` file. 38 | 2. Copy the wallet address and airdrop 6 SOL to the wallet. 39 | 3. Configure the wallet addresses that will receive tokens after transactions in your `.env`: 40 | 41 | ``` 42 | SOL_KYODO_TREASURY_ADDRESS= 43 | SOL_COMMUNITY_TREASURY_ADDRESS= 44 | SOL_PROFESSIONAL_ADDRESS= 45 | ``` 46 | 47 | 4. Update the wallet path in `anchor.toml`. 48 | 49 | ## Deployment Steps 50 | 51 | 1. Deploy to the testnet to generate a `programID`: 52 | 53 | ```sh 54 | chmod +x scripts/deploy.sh 55 | bash scripts/deploy.sh testnet 56 | ``` 57 | 58 | 2. Replace the `programID` address in `src/lib.rs` with your new address. 59 | 3. Update the `programID` in `anchor.toml`. 60 | 4. Deploy again to the devnet: 61 | 62 | ```sh 63 | bash scripts/deploy.sh devnet 64 | ``` 65 | 66 | 5. Execute the initialization script to set up the required data for the program: 67 | 68 | ```sh 69 | ts-node scripts/initializePaymentInfrastructure.ts 70 | ``` -------------------------------------------------------------------------------- /contracts/solana/scripts/retrieveAgreements.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import * as dotenv from "dotenv"; 4 | import path from "path"; 5 | dotenv.config({ path: path.resolve(__dirname, '../../../.env.development.local') }); 6 | 7 | async function readAgreements() { 8 | try { 9 | const provider = anchor.AnchorProvider.local("https://api.devnet.solana.com"); 10 | anchor.setProvider(provider); 11 | 12 | // Carrega o programa de acordos do espaço de trabalho. 13 | const program = anchor.workspace.AgreementProgram; 14 | 15 | // Obtenha a chave pública da empresa a partir da carteira do provedor. 16 | const companyAddress = provider.wallet.publicKey; 17 | 18 | // Converte a string "company_agreements" em um buffer para ser usado em cálculos de PDA. 19 | const stringBuffer = Buffer.from("company_agreements", "utf-8"); 20 | 21 | // Encontre o Program Derived Address (PDA) para os acordos da empresa usando o buffer e o endereço da empresa. 22 | const [companyAgreementsPublicKey, _] = anchor.web3.PublicKey.findProgramAddressSync( 23 | [stringBuffer, companyAddress.toBuffer()], 24 | program.programId 25 | ); 26 | 27 | // Consulta os detalhes dos acordos associados à empresa a partir da blockchain. 28 | const fetchedCompanyAgreements = await program.account.companyAgreements.fetch( 29 | companyAgreementsPublicKey 30 | ); 31 | 32 | console.log("fetchedCompanyAgreements", fetchedCompanyAgreements) 33 | 34 | // Processa e exibe os acordos associados à empresa. 35 | for (const agreement of fetchedCompanyAgreements.agreements) { 36 | const fetchedAgreement = await program.account.agreementAccount.fetch( 37 | agreement 38 | ); 39 | // console.log("fetchedAgreement", fetchedAgreement) 40 | console.log("Title:", fetchedAgreement.title); 41 | console.log("Description:", fetchedAgreement.description); 42 | console.log("Professional:", fetchedAgreement.professional); 43 | console.log("Company:", fetchedAgreement.company); 44 | console.log("Status:", fetchedAgreement.status); 45 | console.log("Payment Amount:", fetchedAgreement.paymentAmount.toString()); 46 | console.log("Total Paid:", fetchedAgreement.totalPaid.toString()); 47 | console.log("------"); 48 | } 49 | 50 | } catch (error) { 51 | console.error("Erro ao ler os acordos:", error); 52 | } 53 | } 54 | 55 | // Chame a função para ler os acordos. 56 | readAgreements(); 57 | -------------------------------------------------------------------------------- /public/solana-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /contracts/ethereum/scripts/retrieveAgreements.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | require('dotenv').config({ path: '../../.env.development.local' }); 3 | 4 | async function kyodoRegistry(contractName) { 5 | const KyodoRegistryContract = await ethers.getContractFactory("KyodoRegistry") 6 | const kyodoRegistryContract = await KyodoRegistryContract.attach(process.env.NEXT_PUBLIC_KYODO_REGISTRY); 7 | 8 | const address = await kyodoRegistryContract.getRegistry(contractName) 9 | return address 10 | } 11 | 12 | async function getAllAgreements() { 13 | 14 | const AgreementContract = await ethers.getContractFactory("AgreementContract"); 15 | const agreementContract = await AgreementContract.attach(kyodoRegistry("AGREEMENT_CONTRACT")); 16 | 17 | const agreements = await agreementContract.getAllAgreements(); 18 | agreements.forEach((agreement) => { 19 | console.log(` 20 | Title: ${agreement.title} 21 | Description: ${agreement.description} 22 | Status: ${agreement.status} 23 | Developer: ${agreement.professional} 24 | Company: ${agreement.company} 25 | Payment Amount: ${ethers.utils.formatEther(agreement.paymentAmount)} tokens 26 | Total Paid: ${agreement.totalPaid} 27 | Fee: ${agreement.fee} 28 | `); 29 | }); 30 | } 31 | 32 | async function getContractorAgreementIds() { 33 | const AgreementContract = await ethers.getContractFactory("AgreementContract"); 34 | const agreementContract = await AgreementContract.attach(kyodoRegistry("AGREEMENT_CONTRACT_ADDRESS")); 35 | 36 | const accounts = await ethers.getSigners(); 37 | const userAddress = accounts[0].address 38 | console.log(`Agreements for user at address ${userAddress}:`); 39 | const userAgreementIds = await agreementContract.getContractorAgreementIds(userAddress); 40 | 41 | 42 | for (const agreementId of userAgreementIds) { 43 | const agreement = await agreementContract.getAgreementById(agreementId); 44 | console.log(` 45 | Agreement ID: ${agreement.id} 46 | Title: ${agreement.title} 47 | Description: ${agreement.description} 48 | Status: ${agreement.status} 49 | Developer: ${agreement.developer} 50 | Payment Amount: ${ethers.utils.formatEther(agreement.paymentAmount)} tokens 51 | Total Paid: ${agreement.totalPaid} 52 | `); 53 | } 54 | } 55 | 56 | // getAllAgreements() 57 | // .then(() => process.exit(0)) 58 | // .catch((error) => { 59 | // console.error(error); 60 | // process.exit(1); 61 | // }); 62 | 63 | getAllAgreements() 64 | .then(() => process.exit(0)) 65 | .catch((error) => { 66 | console.error(error); 67 | process.exit(1); 68 | }); -------------------------------------------------------------------------------- /chains/solana/transactions/fetchPaidAgreements.js: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | 3 | function lamportsToSol(lamports) { 4 | return Math.round(lamports.toString() / Math.pow(10, 8)).toString(); 5 | } 6 | 7 | export const fetchPaidAgreements = async (details) => { 8 | console.log("fetch paid agreements for solana.") 9 | const eventsFound = [] 10 | 11 | try { 12 | 13 | let sigOptions = anchor.web3.SignaturesForAddressOptions = {before: null, until: null, limit: 20}; 14 | 15 | const signatures = await details.contract.provider.connection.getSignaturesForAddress(details.contract.programId, sigOptions, "confirmed"); 16 | const filteredSignatures = []; 17 | 18 | for (const { signature } of signatures) { 19 | const tx = await details.contract.provider.connection.getParsedTransaction(signature, "confirmed"); 20 | 21 | if (tx) { 22 | for (const account of tx.transaction.message.accountKeys) { 23 | // change companyPubkey to provider wallet pubkey 24 | if (account.pubkey.toBase58() === details.account) { 25 | if(signature) { 26 | filteredSignatures.push(signature); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | 33 | const txs = await details.contract.provider.connection.getParsedTransactions(filteredSignatures, {commitment: "confirmed"}); 34 | const eventsParser = new anchor.EventParser(details.contract.programId, new anchor.BorshCoder(details.contract.idl)); 35 | for (const { meta, transaction } of txs) { 36 | // console.log("event with meta found", meta) 37 | const events = eventsParser.parseLogs(meta.logMessages); 38 | // console.log("events: ", events) 39 | for (const event of events) { 40 | if(event.name === "PaymentEvent"){ 41 | console.log("Event", txs[0]) 42 | const formattedEvent = { 43 | agreementId: event.data.agreementId.toString(), 44 | amount: lamportsToSol(event.data.amount), 45 | company: event.data.company.toString(), 46 | professional: event.data.professional.toString(), 47 | transactionHash: transaction.signatures[0] 48 | }; 49 | eventsFound.push(formattedEvent) 50 | console.log("Formatted Event: ", formattedEvent) 51 | console.log(event.data); 52 | } 53 | } 54 | 55 | } 56 | 57 | return eventsFound 58 | 59 | } catch (error) { 60 | console.log("error: ", error) 61 | throw new Error("Error in fetching agreements: ", error); 62 | } 63 | }; 64 | 65 | export default fetchPaidAgreements; -------------------------------------------------------------------------------- /chains/ContractManager.js: -------------------------------------------------------------------------------- 1 | import ethContracts from "./ethereum/contracts" 2 | import solContracts from "./solana/contracts" 3 | import chainConfig from "./chainConfig.json" 4 | 5 | class ContractManager { 6 | constructor() { 7 | this.chains = {} 8 | this.supportedNetworks = [1115, 10200, 245022926, 31337, 80001, 1442] 9 | this.addressValidators = { 10 | ethereum: /^0x[a-fA-F0-9]{40}$/, 11 | solana: /^[1-9A-HJ-NP-Za-km-z]{43,44}$/, 12 | } 13 | 14 | // Initialize contracts based on chain type 15 | for (const chain of this.supportedNetworks) { 16 | this.chains[chain] = ethContracts 17 | } 18 | this.chains["solana"] = solContracts 19 | } 20 | 21 | verify(chain) { 22 | this.chains[chain].verify() 23 | } 24 | 25 | getAddressValidator(chain) { 26 | if (chain === "ethereum") { 27 | return this.addressValidators["ethereum"] 28 | } else if (chain === "solana") { 29 | return this.addressValidators["solana"] 30 | } 31 | console.error(`Address Validator for ${chain} not found.`); 32 | return null; 33 | } 34 | 35 | getSupportedChains(){ 36 | return this.supportedNetworks 37 | } 38 | 39 | async tokens(chain, networkId) { 40 | const config = chain === 'ethereum' ? chainConfig[networkId] : chainConfig[chain]; 41 | 42 | if (!config) { 43 | console.error(`Token List for ${chain === 'ethereum' ? `Ethereum network ID ${networkId}` : chain} not found.`); 44 | return []; 45 | } 46 | 47 | let tokenList = [...(config.tokens || [])]; 48 | 49 | if (process.env.NODE_ENV !== 'production') { 50 | const developmentToken = { 51 | name: 'fakeStable', 52 | address: chain === 'solana' 53 | ? process.env.NEXT_PUBLIC_SOLANA_FAKE_STABLE_ADDRESS 54 | : await ethContracts.kyodoRegistry.getRegistry("FAKE_STABLE"), 55 | decimals: chain === 'solana' ? 8 : 18, 56 | }; 57 | tokenList.push(developmentToken); 58 | } 59 | return tokenList; 60 | } 61 | 62 | blockExplorer(chain, networkId) { 63 | const config = chain === 'ethereum' ? chainConfig[networkId] : chainConfig[chain]; 64 | 65 | if (!config) { 66 | console.error(`Configuration for ${chain === 'ethereum' ? `Ethereum network ID ${networkId}` : chain} not found.`); 67 | return null; 68 | } 69 | 70 | return config.blockExplorer.url; 71 | } 72 | 73 | 74 | chainMetadata(chain) { 75 | if (!chainConfig[chain]) { 76 | console.error(`Metadata for ${chain} not found.`); 77 | return null; 78 | } 79 | return { 80 | "name": chainConfig[chain].name, 81 | "logo": chainConfig[chain].logo, 82 | }; 83 | } 84 | } 85 | 86 | const manager = new ContractManager() 87 | export default manager 88 | -------------------------------------------------------------------------------- /hooks/useTransactionHandler.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react' 2 | import { useAccount } from "../contexts/AccountContext" 3 | import transactionManager from '../chains/transactionManager' 4 | 5 | function useTransactionHandler() { 6 | const [isLoading, setIsLoading] = useState(false) 7 | const [transactionSuccess, setTransactionSuccess] = useState(false) 8 | const [transactionPending, setTransactionPending] = useState(false) 9 | const [transactionFail, setTransactionFail] = useState(false); 10 | const [transactionHash, setTransactionHash] = useState(null); 11 | const [warning, setWarning] = useState(null); 12 | const [errorMessage, setErrorMessage] = useState(''); 13 | const [warningMessage, setWarningMessage] = useState(''); 14 | const { account, selectedNetworkId } = useAccount() 15 | 16 | const sendTransaction = useCallback(async (functionName, details, eventName, onConfirmation) => { 17 | setIsLoading(true); 18 | setTransactionFail(false); 19 | setTransactionPending(false); 20 | setTransactionSuccess(false); 21 | setWarning(false); 22 | 23 | try { 24 | const TRANSACTION_TIMEOUT = 30000; 25 | 26 | const timeoutPromise = new Promise((_, reject) => { 27 | setTimeout(() => reject(new Error('Transaction timed out')), TRANSACTION_TIMEOUT); 28 | }); 29 | 30 | const txResponse = await Promise.race([ 31 | transactionManager[functionName](selectedNetworkId, details), 32 | timeoutPromise 33 | ]); 34 | 35 | setTransactionHash(txResponse) 36 | 37 | const response = await transactionManager.handleTransactionPromise 38 | ( 39 | selectedNetworkId, 40 | details.contract, 41 | txResponse, 42 | eventName, 43 | account 44 | ) 45 | 46 | if (response){ 47 | setTransactionSuccess(true); 48 | setTransactionPending(false); 49 | onConfirmation(); 50 | } 51 | } catch (error) { 52 | setErrorMessage(error.message) 53 | console.error("Error:", error.message) 54 | if (transactionHash) { 55 | if (error.message.includes(`Timeout waiting for ${eventName} event`)) { 56 | setTransactionFail(false) 57 | setTransactionPending(true) 58 | } else { 59 | setTransactionFail(true) 60 | } 61 | } else { 62 | setIsLoading(false) 63 | setTransactionFail(true) 64 | } 65 | } 66 | }, [selectedNetworkId]) 67 | 68 | return { 69 | isLoading, 70 | setIsLoading, 71 | transactionSuccess, 72 | transactionPending, 73 | setTransactionFail, 74 | transactionFail, 75 | setErrorMessage, 76 | errorMessage, 77 | sendTransaction, 78 | warning, 79 | setWarning, 80 | setWarningMessage, 81 | warningMessage, 82 | transactionHash 83 | } 84 | } 85 | 86 | export default useTransactionHandler -------------------------------------------------------------------------------- /chains/chainConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": { 3 | "name":"Ethereum", 4 | "logo":"", 5 | "tokens": [ 6 | { 7 | "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 8 | "logo": "/public/coins/usdc-icon.svg", 9 | "name": "USDC", 10 | "decimals": 6 11 | }, 12 | { 13 | "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", 14 | "logo": "/public/coins/dai-icon.svg", 15 | "name": "DAI", 16 | "decimals": 18 17 | }, 18 | { 19 | "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", 20 | "logo": "/public/coins/usdt-icon.svg", 21 | "name": "USDT", 22 | "decimals": 6 23 | } 24 | ], 25 | "blockExplorer": { 26 | "name": "Etherscan", 27 | "url": "/chains/ethereum-eth-logo.svg" 28 | } 29 | }, 30 | "1115": { 31 | "name":"CoreDaoTestnet", 32 | "logo":"/chains/core-dao.png", 33 | "tokens": [ 34 | { 35 | "address": "0x3786495F5d8a83B7bacD78E2A0c61ca20722Cce3", 36 | "logo": "/public/coins/usdt-icon.svg", 37 | "name": "USDT", 38 | "decimals": 6 39 | } 40 | ], 41 | "blockExplorer": { 42 | "name": "Core Explorer", 43 | "url": "https://scan.test.btcs.network/" 44 | } 45 | }, 46 | "10200": { 47 | "name":"GnosisChiado", 48 | "logo":"/chains/gnosis-chain.png", 49 | "tokens": [ 50 | 51 | ], 52 | "blockExplorer": { 53 | "name": "Blockscout", 54 | "url": "https://blockscout.chiadochain.net/" 55 | } 56 | }, 57 | "245022926": { 58 | "name":"NeonDevnet", 59 | "logo":"/chains/neonevm-logo.png", 60 | "tokens": [ 61 | 62 | ], 63 | "blockExplorer": { 64 | "name": "Neonscan", 65 | "url": "https://devnet.neonscan.org/" 66 | } 67 | }, 68 | "80001": { 69 | "name":"Mumbai", 70 | "logo":"/chains/polygon-matic-logo.svg", 71 | "tokens": [ 72 | 73 | ], 74 | "blockExplorer": { 75 | "name": "PolygonScan", 76 | "url": "https://mumbai.polygonscan.com/" 77 | } 78 | }, 79 | "31337": { 80 | "name":"Hardhat", 81 | "logo":"/chains/hardhat.svg", 82 | "tokens": [ 83 | 84 | ], 85 | "blockExplorer": { 86 | "name": "LocalNetwork", 87 | "url": "/" 88 | } 89 | }, 90 | "1442": { 91 | "name":"ZkEvmTestnet", 92 | "logo":"/chains/polygon-zk-evm.png", 93 | "tokens": [ 94 | 95 | ], 96 | "blockExplorer": { 97 | "name": "polygonZkEvmTestnet", 98 | "url": "https://testnet-zkevm.polygonscan.com/" 99 | } 100 | }, 101 | "solana": { 102 | "tokens": [ 103 | 104 | ], 105 | "blockExplorer": { 106 | "name": "Solana Explorer", 107 | "url": "https://explorer.solana.com/" 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /public/coins/dai-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /contracts/ethereum/README.md: -------------------------------------------------------------------------------- 1 | ## Requirements 2 | 3 | Before starting, ensure you have your mnemonic phrase set in the `.env` file. It should be formatted as follows: 4 | 5 | ``` 6 | MNEMONIC=your_mnemonic_phrase_here 7 | ``` 8 | 9 | ## Running Tests 10 | 11 | To run tests, follow these steps: 12 | 13 | 1. Start a local node: 14 | 15 | ``` 16 | npx hardhat node 17 | ``` 18 | 19 | 2. Then, execute the tests: 20 | 21 | ``` 22 | npx hardhat test 23 | ``` 24 | 25 | ## Deploying to Individual Networks 26 | 27 | To deploy contracts to a specific network, follow these steps: 28 | 29 | 1. Deploy the Registry: 30 | 31 | For a local Hardhat node (default network name is "testing"): 32 | ``` 33 | npx hardhat deploy --network testing 34 | ``` 35 | 36 | For other networks, replace `testing` with the name of your target network. 37 | 38 | 2. Deploy the AgreementContract and the Vault: 39 | 40 | For the local Hardhat node: 41 | ``` 42 | npx hardhat run scripts/deployAgreementAndVault.js --network testing 43 | ``` 44 | 45 | For deployment to other networks, again replace `testing` with your chosen network name. 46 | 47 | *Note: "testing" is the default network name used when running a local Hardhat node.* 48 | 49 | ## Deploying the Registry to Multiple Networks 50 | 51 | To deploy the Registry to multiple networks: 52 | 53 | 1. Run the multichain deployment: 54 | 55 | ``` 56 | npx hardhat deploy:multichain 57 | ``` 58 | 59 | This will deploy to all networks configured in `hardhat.config.js` and listed in the `deployMultichain.js` file. 60 | 61 | 2. Next, deploy the AgreementContract and the Vault individually on each chain. Repeat the deployment command for each network as described in the "Deploying to Individual Networks" section. 62 | 63 | --- 64 | 65 | ### Automated Documentation Generation 66 | 67 | To enhance understanding and collaboration on our project, we provide tools to automatically generate documentation for our Solidity contracts. Here's how you can generate UML diagrams and detailed documentation for individual contracts: 68 | 69 | 1. **Generate UML Diagrams for Individual Contracts:** 70 | - For `StableVault.sol`: 71 | ``` 72 | npx sol2uml ./contracts/StableVault.sol -o ./docs/diagram/StableVault.svg 73 | ``` 74 | - For `AgreementContract.sol`: 75 | ``` 76 | npx sol2uml ./contracts/AgreementContract.sol -o ./docs/diagram/AgreementContract.svg 77 | ``` 78 | 79 | The `sol2uml` utility will generate UML diagrams, providing a visual representation of the contract structure, including inherited contracts and dependencies. 80 | 81 | 2. **Generate Detailed Documentation for Public and External Functions:** 82 | ``` 83 | npx solidity-docgen --solc-module solc-0.8 -t ./docs/template/external -o ./docs/output/external 84 | ``` 85 | 86 | The `solidity-docgen` tool will produce documentation focusing on public and external functions, giving you a clear view of the contract's interfaces and functionalities. 87 | 88 | Make sure you have the necessary environment set up to run these commands. The generated UML diagrams and documentation can be found in the directories specified in the commands above. 89 | -------------------------------------------------------------------------------- /public/locales/en-US/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "community": "Community", 3 | "professional": "Professional", 4 | "contractor": "Contractor", 5 | 6 | "welcome": "Welcome to Kyōdō", 7 | 8 | "connect-wallet": "Connect wallet", 9 | "profile-selection": "Profile selection", 10 | "initial-setup": "Initial setup", 11 | "terms-conditions": "Terms & conditions", 12 | 13 | "community-call": "Implement the protocol", 14 | "professional-call": "Get a job and career help", 15 | "contractor-call": "Find a talent", 16 | 17 | "view-document": "View document", 18 | "get-started": "Get started", 19 | 20 | "basic-info": "Basic info", 21 | "personal-info": "Personal info", 22 | "tax-document": "Tax Document", 23 | "create-profile": "Create Profile", 24 | 25 | "name": "Name", 26 | "company-name": "Company name", 27 | "bio": "Bio", 28 | "avatar": "Avatar URL", 29 | "logo": "Logo URL", 30 | "website": "Website", 31 | "about": "About", 32 | "company-document": "Registration number", 33 | "community-fee": "Community fee", 34 | "select-option": "Select an option", 35 | "token-tooltip": "It can be used as a governance token if the community become a DAO", 36 | 37 | "back": "Back", 38 | "next-step": "Next step", 39 | "i-agree": "I agree with ", 40 | 41 | "dashboard": "Dashboard", 42 | "agreements": "Agreements", 43 | "agreement": "Agreement", 44 | 45 | "gm": "Hello,", 46 | "redeem": "Redeem", 47 | "confirm": "Confirm", 48 | 49 | "call-02": "Add an agreement", 50 | "phrase-02": "Start adding your first agreement.", 51 | "btn-02": "Add agreement", 52 | 53 | "payments": "Payments", 54 | "paid": "Paid", 55 | "received": "Received", 56 | "view-on": "View on block explorer", 57 | "view-all": "View all", 58 | 59 | "code-conduct": "Code of conduct", 60 | "privacy-policy": "Privacy policy", 61 | "terms-use": "Terms of use", 62 | 63 | "validation": { 64 | "required": "{{field}} is a required field", 65 | "valid-chain-address": "Professional must be a valid {{chain}} address", 66 | "is-not-company": "Professional address cannot be the same as the agreement owner or company", 67 | "positive": "{{field}} must be a positive value", 68 | "level-validation": "The level must be a number between 1 and 100, and the total level sum cannot exceed 100%" 69 | }, 70 | 71 | "all-tab": "All", 72 | "inactive-tab": "Inactive", 73 | 74 | "total-paid": "Total paid", 75 | "pay-agreement": "Pay agreement", 76 | "select-token": "Select a token", 77 | 78 | "onboarding-success": "Onboarding successfully completed!", 79 | 80 | "add-agreement-heading": "Add agreement", 81 | "title": "Title", 82 | "professional-wallet": "Professional wallet", 83 | "payment-amount": "Payment amount", 84 | "description": "Description", 85 | "skills": "Skills", 86 | "skills-tooltip": "The sum must equal 100%", 87 | "add": "add", 88 | "add-btn": "Add", 89 | "transaction-success": "Transaction successful!", 90 | "agreement-pending": "Transaction is still pending, please wait.", 91 | "no-agreements": "No agreements found. Click on the add icon above.", 92 | "notifications": "Notifications", 93 | "professional-not-registered": "This professional address is not registered. You can continue, but make sure it's correct." 94 | } 95 | -------------------------------------------------------------------------------- /chains/solana/transactions/payAgreement.js: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import * as anchor from "@coral-xyz/anchor"; 3 | import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; 4 | require('dotenv').config({ path: '../../.env.development.local' }); 5 | 6 | export const payAgreement = async (details) => { 7 | try { 8 | const acceptedPaymentTokensPubkey = new PublicKey(process.env.NEXT_PUBLIC_SOL_ACCEPTED_PAYMENT_TOKENS_ADDRESS); 9 | const feesPubkey = new PublicKey(process.env.NEXT_PUBLIC_SOL_FEES_ADDRESS); 10 | const associatedTokenAddressCompany = new PublicKey(process.env.NEXT_PUBLIC_SOL_ASSOCIATED_TOKEN_ADDRESS_COMPANY); 11 | const associatedTokenAddressCommunity = new PublicKey(process.env.NEXT_PUBLIC_SOL_ASSOCIATED_TOKEN_ADDRESS_COMMUNITY); 12 | const associatedTokenAddressTreasury = new PublicKey(process.env.NEXT_PUBLIC_SOL_ASSOCIATED_TOKEN_ADDRESS_TREASURY); 13 | 14 | const selectedPaymentTokenPubkey = new PublicKey(details.selectedPaymentToken.address) 15 | 16 | const professionalPubkey = new PublicKey(details.agreement.professional) 17 | const companyPubkey = new PublicKey(details.account) 18 | 19 | const [professionalVaultPublicKey, ___] = 20 | anchor.web3.PublicKey.findProgramAddressSync( 21 | [professionalPubkey.toBytes(), selectedPaymentTokenPubkey.toBytes()], 22 | details.contract.programId 23 | ) 24 | 25 | const stringBufferCompany = Buffer.from("company_agreements", "utf-8") 26 | const [companyAgreementsPublicKey, _] = anchor.web3.PublicKey.findProgramAddressSync( 27 | [stringBufferCompany, companyPubkey.toBuffer()], 28 | details.contract.programId 29 | ) 30 | 31 | const stringBufferProfessional = Buffer.from("professional_agreements", "utf-8") 32 | const [professionalAgreementsPublicKey, __] = anchor.web3.PublicKey.findProgramAddressSync( 33 | [stringBufferProfessional, professionalPubkey.toBytes()], 34 | details.contract.programId 35 | ) 36 | 37 | const amountInLamports = new anchor.BN(details.paymentValue * Math.pow(10, details.selectedPaymentToken.decimals)) 38 | 39 | const tx = await details.contract.methods.processPayment(amountInLamports) 40 | .accounts({ 41 | company: companyPubkey, 42 | agreement: details.agreement.publicKey, 43 | fromAta: associatedTokenAddressCompany, 44 | communityDaoAta: associatedTokenAddressCommunity, 45 | treasuryAta: associatedTokenAddressTreasury, 46 | paymentToken: selectedPaymentTokenPubkey, 47 | professional: professionalPubkey, 48 | professionalVault: professionalVaultPublicKey, 49 | companyAgreements: companyAgreementsPublicKey, 50 | professionalAgreements: professionalAgreementsPublicKey, 51 | acceptedPaymentTokens: acceptedPaymentTokensPubkey, 52 | fees: feesPubkey, 53 | tokenProgram: TOKEN_PROGRAM_ID, 54 | }) 55 | .rpc() 56 | console.log("tx", tx) 57 | return tx 58 | } catch (error) { 59 | console.error("Error in payAgreement:", error); 60 | throw error; 61 | } 62 | }; 63 | 64 | export default payAgreement; -------------------------------------------------------------------------------- /contracts/ethereum/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | require("@nomiclabs/hardhat-etherscan"); 3 | require('dotenv').config(); 4 | require("hardhat-jest-plugin"); 5 | require("hardhat-gas-reporter"); 6 | require('hardhat-contract-sizer'); 7 | require('dotenv').config({ path: '../../.env' }); 8 | require('hardhat-deploy'); 9 | 10 | const MNEMONIC = process.env["MNEMONIC"]; 11 | 12 | /** @type import('hardhat/config').HardhatUserConfig */ 13 | 14 | let config = { 15 | solidity: "0.8.23", 16 | namedAccounts: { 17 | deployer: 0, 18 | kyodoTreasury: 4 19 | }, 20 | settings: { 21 | optimizer: { 22 | enabled: false, 23 | runs: 200 24 | } 25 | }, 26 | networks: { 27 | testing: { 28 | url: "http://127.0.0.1:8545/" 29 | }, 30 | sepolia: { 31 | url: process.env.SEPOLIA_RPC_URL || 'https://rpc.sepolia.org', 32 | accounts: { mnemonic: MNEMONIC }, 33 | }, 34 | optimismGoerli: { 35 | url: process.env.OPTIMISM_GOERLI_RPC_URL || 'https://goerli.optimism.io', 36 | accounts: { mnemonic: MNEMONIC }, 37 | }, 38 | avalancheFuji: { 39 | url: process.env.AVALANCHE_FUJI_RPC_URL || 'https://api.avax-test.network/ext/bc/C/rpc', 40 | accounts: { mnemonic: MNEMONIC }, 41 | }, 42 | arbitrumGoerli: { 43 | url: process.env.ARBITRUM_GOERLI_RPC_URL || 'https://goerli-rollup.arbitrum.io/rpc', 44 | accounts: { mnemonic: MNEMONIC }, 45 | }, 46 | polygonMumbai: { 47 | url: process.env.MUMBAI_RPC_URL || 'https://rpc.ankr.com/polygon_mumbai', 48 | accounts: { mnemonic: MNEMONIC }, 49 | }, 50 | bnbTesnet: { 51 | url: process.env.BNB_TESTNET_RPC_URL || 'https://data-seed-prebsc-1-s1.binance.org:8545', 52 | accounts: { mnemonic: MNEMONIC }, 53 | }, 54 | baseGoerli: { 55 | url: process.env.BASE_GOERLI_RPC_URL || 'https://goerli.base.org', 56 | accounts: { mnemonic: MNEMONIC }, 57 | }, 58 | polygonZkEvmTestnet: { 59 | url: process.env.POLYGON_ZKEVM_TESTNET_RPC_URL || "https://rpc.public.zkevm-test.net", 60 | accounts: { mnemonic: MNEMONIC }, 61 | }, 62 | goerli: { 63 | url: process.env.GOERLI_RPC_URL || "https://rpc.ankr.com/eth_goerli", 64 | accounts: { mnemonic: MNEMONIC }, 65 | }, 66 | scrollTestnet: { 67 | url: process.env.SCROLL_TESTNET_RPC_URL || "https://sepolia-rpc.scroll.io", 68 | accounts: { mnemonic: MNEMONIC }, 69 | }, 70 | fantomTestnet: { 71 | url: process.env.FANTOM_TESTNET_RPC_URL || "https://rpc.testnet.fantom.network", 72 | accounts: { mnemonic: MNEMONIC }, 73 | }, 74 | gnosisChiado: { 75 | url: process.env.GNOSIS_CHIADO_RPC_URL || "https://rpc.chiado.gnosis.gateway.fm", 76 | accounts: { mnemonic: MNEMONIC }, 77 | }, 78 | neonDevnet: { 79 | url: process.env.NEON_DEVNET_RPC_URL || "https://devnet.neonevm.org", 80 | accounts: { mnemonic: MNEMONIC }, 81 | }, 82 | coreDaoTestnet: { 83 | url: process.env.COREDAO_TESTNET_RPC_URL || "https://rpc.test.btcs.network", 84 | accounts: { mnemonic: MNEMONIC }, 85 | }, 86 | }, 87 | contractSizer: { 88 | alphaSort: false, 89 | disambiguatePaths: false, 90 | runOnCompile: false, 91 | strict: true, 92 | }, 93 | gasReporter: { 94 | enabled: false, 95 | } 96 | }; 97 | 98 | module.exports = config; -------------------------------------------------------------------------------- /public/locales/pt-BR/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "community": "Comunidade", 3 | "professional": "Profissional", 4 | "contractor": "Contratante", 5 | 6 | "welcome": "Bem-vindo ao Kyōdō", 7 | 8 | "connect-wallet": "Conectar carteira", 9 | "profile-selection": "Seleção de perfil", 10 | "initial-setup": "Informações Básicas", 11 | "terms-conditions": "Termos & Condições", 12 | 13 | "community-call": "Implemente o protocolo", 14 | "professional-call": "Consiga um trabalho", 15 | "contractor-call": "Encontre talentos", 16 | 17 | "view-document": "Ver documento", 18 | "get-started": "Iniciar", 19 | 20 | "basic-info": "Informações básicas", 21 | "personal-info": "Informações pessoais", 22 | "tax-document": "Documento Fiscal", 23 | "create-profile": "Criar Perfil", 24 | 25 | "name": "Nome", 26 | "company-name": "Nome da empresa", 27 | "bio": "Biografia", 28 | "avatar": "URL do avatar", 29 | "logo": "URL da Logo", 30 | "website": "Website", 31 | "about": "Sobre", 32 | "company-document": "Número do documento", 33 | "community-fee": "Community fee", 34 | "select-option": "Selecione uma opção", 35 | "token-tooltip": "Pode ser usado como um token de governança caso a comunidade se tornar um DAO", 36 | 37 | "back": "Voltar", 38 | "next-step": "Próximo passo", 39 | "i-agree": "Eu concordo com ", 40 | 41 | "dashboard": "Painel", 42 | "agreements": "Acordos", 43 | "agreement": "Acordo", 44 | 45 | "gm": "Olá,", 46 | "redeem": "Resgatar", 47 | "confirm": "Confirmar", 48 | 49 | "call-02": "Adicionar um acordo", 50 | "phrase-02": "Comece criando um acordo com alguém", 51 | "btn-02": "Adicionar acordo", 52 | 53 | "payments": "Pagamentos", 54 | "paid": "Pago", 55 | "received": "Recebido", 56 | "view-on": "Ver no explorador de blocos", 57 | "view-all": "Ver tudo", 58 | 59 | "code-conduct": "Código de conduta", 60 | "privacy-policy": "Privacidade", 61 | "terms-use": "Termos de uso", 62 | 63 | "validation": { 64 | "required": "{{field}} é um campo obrigatório", 65 | "valid-chain-address": "O endereço profissional deve ser um endereço {{chain}} válido", 66 | "is-not-company": "O endereço profissional não pode ser o mesmo que o proprietário do contrato ou da empresa", 67 | "positive": "{{field}} deve ser um valor positivo", 68 | "level-validation": "A lista de habilidades não pode estar vazia e a soma total dos níveis deve ser 100%" 69 | }, 70 | 71 | "all-tab": "Todos", 72 | "inactive-tab": "Inativos", 73 | 74 | "total-paid": "Total pago", 75 | "pay-agreement": "Pagar acordo", 76 | "select-token": "Selecionar token", 77 | 78 | "onboarding-success": "Onboarding finalizado com sucesso!", 79 | 80 | "add-agreement-heading": "Adicionar acordo", 81 | "title": "Título", 82 | "professional-wallet": "Carteira", 83 | "payment-amount": "Valor", 84 | "description": "Descrição", 85 | "skills": "Habilidades", 86 | "skills-tooltip": "A soma deve ser igual a 100%", 87 | "add": "adicionar", 88 | "add-btn": "Adicionar", 89 | "agreement-created": "Acordo criado com sucesso!", 90 | "agreement-pending": "Transação pendente. Aguarde um instante...", 91 | "no-agreements": "Nenhum acordo encontrado. Click no link de adicionar acima", 92 | "notifications": "Notificações", 93 | "professional-not-registered": "O endereço desse profissional não está registrado. Você pode continuar, mas verifique se está correto." 94 | } 95 | -------------------------------------------------------------------------------- /components/ConnectWalletButton/ConnectWalletButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect} from "react"; 2 | import Image from 'next/image' 3 | import styles from "./ConnectWalletButton.module.scss" 4 | import { useWallet } from '@solana/wallet-adapter-react'; 5 | import { useWalletModal } from '@solana/wallet-adapter-react-ui'; 6 | import contractManager from "../../chains/ContractManager" 7 | import { useWeb3Modal } from '@web3modal/wagmi/react' 8 | import { useAccount as useWagmiAccount } from 'wagmi'; 9 | 10 | require("@solana/wallet-adapter-react-ui/styles.css") 11 | 12 | async function verifyChain(chain) { 13 | contractManager.verify(chain) 14 | } 15 | 16 | function ConnectWalletButton(props) { 17 | const [showModal, setShowModal] = useState(false); 18 | const { setVisible } = useWalletModal(); 19 | const { open } = useWeb3Modal() 20 | const { publicKey } = useWallet(); 21 | const { address } = useWagmiAccount(); 22 | 23 | useEffect(() => { 24 | async function handleWalletConnection() { 25 | if (address) { 26 | props.value.setAccount(address); 27 | props.value.setSelectedChain("ethereum") 28 | localStorage.setItem("selectedChain", "ethereum") 29 | } else if (publicKey) { 30 | props.value.setAccount(publicKey); 31 | await verifyChain("solana"); 32 | } 33 | } 34 | 35 | handleWalletConnection(); 36 | }, [address, publicKey]); 37 | 38 | async function connectEthereumWallet() { 39 | setShowModal(false); 40 | open({ view: 'All wallets' }); 41 | } 42 | 43 | async function connectSolanaWallet() { 44 | alert("Coming soon!") 45 | // setShowModal(false); 46 | // setVisible(true) 47 | // props.value.setSelectedChain("solana") 48 | // localStorage.setItem('selectedChain', "solana"); 49 | } 50 | 51 | return ( 52 |
53 |
54 |
55 | WEB3DEV 56 |

Connect your wallet to start

57 | 60 |
61 |
62 | {showModal && 63 | ( 64 | 88 | ) 89 | } 90 |
91 | ) 92 | } 93 | 94 | export {ConnectWalletButton, verifyChain}; 95 | -------------------------------------------------------------------------------- /components/utils/Toast/index.js: -------------------------------------------------------------------------------- 1 | // Toast.js 2 | import React, { useEffect, useState } from "react" // <-- Import useEffect and useState 3 | import Image from "next/image" 4 | import Link from "next/link" 5 | import { useTranslation } from "react-i18next" 6 | import contractManager from '../../../chains/ContractManager'; 7 | import { useAccount } from "../../../contexts/AccountContext"; 8 | 9 | function Toast({ 10 | transactionSuccess, 11 | transactionPending, 12 | transactionFail, 13 | errorMessage, 14 | transactionHash, 15 | warning, 16 | warningMessage 17 | }) { 18 | const { t } = useTranslation() 19 | const { account, selectedChain, selectedNetworkId} = useAccount(); 20 | const [visible, setVisible] = useState(true) // <-- Add a state to manage visibility 21 | 22 | useEffect(() => { 23 | if (visible) { 24 | const timer = setTimeout(() => { 25 | setVisible(false) // <-- Hide the toast after 3 seconds 26 | }, 3000) 27 | return () => clearTimeout(timer) // <-- Clear the timer when component unmounts or if visibility changes 28 | } 29 | }, [visible]) 30 | 31 | useEffect(() => { 32 | if (transactionSuccess || transactionFail || transactionPending || warning ) { 33 | setVisible(true) // <-- Show the toast when transaction succeeds or fails 34 | } 35 | }, [transactionSuccess, transactionPending, transactionFail, warning]) 36 | 37 | if (!visible) return null // <-- Don't render the component if it's not visible 38 | 39 | if (transactionSuccess) { 40 | const explorerLink = contractManager.blockExplorer(selectedChain, selectedNetworkId) 41 | return ( 42 |
43 |

44 | Success icon 45 | {t("transaction-success")} 46 |

47 | 52 | {t("view-on")} 53 | 54 |
55 | ) 56 | } 57 | 58 | if (transactionPending) { 59 | return ( 60 |
61 |

62 | Pending icon 63 | {t("agreement-pending")} 64 |

65 | 70 | Check the status here 71 | 72 |
73 | ) 74 | } 75 | 76 | if (warning) { 77 | let displayMessage = warningMessage 78 | return ( 79 |
80 |

81 | Pending icon 82 | {displayMessage} 83 |

84 |
85 | ) 86 | } 87 | 88 | if (transactionFail) { 89 | let displayMessage = errorMessage 90 | return ( 91 |
92 |

100 | Error icon 101 | {displayMessage} 102 |

103 |
104 | ) 105 | } 106 | 107 | return null 108 | } 109 | 110 | export default Toast; 111 | -------------------------------------------------------------------------------- /contracts/solana/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if an argument was provided 4 | if [ -z "$1" ]; then 5 | echo "No environment specified. Usage: ./deploy.sh [env]" 6 | exit 1 7 | fi 8 | 9 | # Store the argument 10 | env="$1" 11 | 12 | # Perform actions based on the argument 13 | if [ "$env" == "devnet" ]; then 14 | echo "Deploying to devnet..." 15 | # Add your deployment logic for devnet here 16 | # Get the directory of the script 17 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 18 | 19 | # Construct the path to the .env.development.local file 20 | ENV_FILE="$DIR/../../../.env.development.local" 21 | 22 | # Build the program 23 | echo "Building the program..." 24 | anchor build 25 | 26 | # Deploy the program and capture the Program ID 27 | echo "Deploying the program..." 28 | DEPLOY_OUTPUT=$(anchor deploy --provider.cluster devnet) 29 | echo "$DEPLOY_OUTPUT" 30 | 31 | # Extract the Program ID from the deploy output using awk 32 | PROGRAM_ID=$(echo "$DEPLOY_OUTPUT" | awk '/Program Id:/ {print $3}') 33 | 34 | # Check if the Program ID is already in the .env.development.local file and replace it if necessary 35 | if grep -q "NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS" "$ENV_FILE"; then 36 | sed -i.bak "s/NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS=.*/NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS=$PROGRAM_ID/" "$ENV_FILE" 37 | rm -f "$ENV_FILE.bak" 38 | else 39 | echo "NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS=$PROGRAM_ID" >> "$ENV_FILE" 40 | fi 41 | 42 | # Deploy the fake Stablecoin 43 | echo "Creating fake token..." 44 | DEPLOY_FAKE_STABLE_OUTPUT=$(npx ts-node $DIR/createFakeToken.ts) 45 | echo "$DEPLOY_FAKE_STABLE_OUTPUT" 46 | 47 | # Initialize Payment Infrastructure 48 | echo "Initializing payment infrastructure..." 49 | DEPLOY_FAKE_STABLE_OUTPUT=$(npx ts-node $DIR/initializePaymentInfrastructure.ts) 50 | echo "$DEPLOY_FAKE_STABLE_OUTPUT" 51 | 52 | elif [ "$env" == "testnet" ]; then 53 | echo "Deploying to testnet..." 54 | # Get the directory of the script 55 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 56 | 57 | # Construct the path to the .env.development.local file 58 | ENV_FILE="$DIR/../../../.env.development.local" 59 | 60 | # Build the program 61 | echo "Building the program..." 62 | anchor build 63 | 64 | # Check if the solana-test-validator is running 65 | if ! pgrep -x "solana-test-validator" > /dev/null 66 | then 67 | echo "Error: solana-test-validator is not running." 68 | echo "Please start solana-test-validator and try again." 69 | exit 1 70 | fi 71 | 72 | # Deploy the program and capture the Program ID 73 | echo "Deploying the program..." 74 | DEPLOY_OUTPUT=$(anchor deploy) 75 | echo "$DEPLOY_OUTPUT" 76 | 77 | # Extract the Program ID from the deploy output using awk 78 | PROGRAM_ID=$(echo "$DEPLOY_OUTPUT" | awk '/Program Id:/ {print $3}') 79 | 80 | # Check if the Program ID is already in the .env.development.local file and replace it if necessary 81 | if grep -q "NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS" "$ENV_FILE"; then 82 | sed -i.bak "s/NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS=.*/NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS=$PROGRAM_ID/" "$ENV_FILE" 83 | rm -f "$ENV_FILE.bak" 84 | else 85 | echo "NEXT_PUBLIC_SOLANA_AGREEMENT_CONTRACT_ADDRESS=$PROGRAM_ID" >> "$ENV_FILE" 86 | fi 87 | 88 | elif [ "$env" == "mainnet" ]; then 89 | echo "Deploying to mainnet..." 90 | # Add your deployment logic for mainnet here 91 | else 92 | echo "Unknown environment: $env" 93 | exit 1 94 | fi -------------------------------------------------------------------------------- /contracts/solana/scripts/withdrawFromVault.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { PublicKey, Keypair } from "@solana/web3.js"; 3 | import * as dotenv from "dotenv"; 4 | import path from "path"; 5 | import fs from 'fs'; 6 | import bs58 from 'bs58'; 7 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 8 | import { 9 | TOKEN_PROGRAM_ID, 10 | ASSOCIATED_TOKEN_PROGRAM_ID, 11 | getOrCreateAssociatedTokenAccount, 12 | } from "@solana/spl-token"; 13 | dotenv.config({ path: path.resolve(__dirname, '../../../.env.development.local') }); 14 | 15 | const provider = anchor.AnchorProvider.local("https://api.devnet.solana.com"); 16 | anchor.setProvider(provider); 17 | const program = anchor.workspace.AgreementProgram; 18 | 19 | async function withdrawFromVault(professionalKeypair, fakeStableAddress) { 20 | const payer = (provider.wallet as NodeWallet).payer; 21 | const amountToWithdraw = new anchor.BN(20 * Math.pow(10, 8)) 22 | 23 | const stringBufferProfessional = Buffer.from("professional_agreements", "utf-8"); 24 | 25 | const [professionalAgreementsPublicKey, __] = anchor.web3.PublicKey.findProgramAddressSync( 26 | [stringBufferProfessional, professionalKeypair.publicKey.toBuffer()], 27 | program.programId 28 | ); 29 | 30 | const [professionalVaultPublicKey, ___] = 31 | anchor.web3.PublicKey.findProgramAddressSync( 32 | [professionalKeypair.publicKey.toBytes(), fakeStableAddress.toBytes()], 33 | program.programId 34 | ); 35 | 36 | const associatedTokenAddressProfessional = await getOrCreateNeededAssociatedTokenAccount( 37 | payer, 38 | fakeStableAddress, 39 | professionalKeypair.publicKey 40 | ); 41 | 42 | const tx = await program.methods.withdraw(amountToWithdraw) 43 | .accounts({ 44 | professional: professionalKeypair.publicKey, 45 | professionalAta: associatedTokenAddressProfessional.address, 46 | vaultAta: professionalVaultPublicKey, 47 | professionalAgreements: professionalAgreementsPublicKey, 48 | tokenProgram: TOKEN_PROGRAM_ID, 49 | }).signers([professionalKeypair]).rpc(); 50 | 51 | console.log("Your transaction signature:", tx); 52 | return tx; 53 | } 54 | 55 | async function main(){ 56 | const fakeStableAddress = new PublicKey(process.env.NEXT_PUBLIC_SOLANA_FAKE_STABLE_ADDRESS); 57 | const professionalPrivateKey = process.env.SOL_PROFESSIONAL_PRIVATE_KEY; 58 | 59 | const professionalKeypair = anchor.web3.Keypair.fromSecretKey(bs58.decode(professionalPrivateKey)); 60 | 61 | const [professionalVaultPublicKey, ___] = 62 | anchor.web3.PublicKey.findProgramAddressSync( 63 | [professionalKeypair.publicKey.toBytes(), fakeStableAddress.toBytes()], 64 | program.programId 65 | ); 66 | 67 | const professionalBalance = await provider.connection.getTokenAccountBalance(professionalVaultPublicKey); 68 | console.log("Initial professionalBalance: ", professionalBalance.value); 69 | 70 | // const withdraw = await withdrawFromVault(professionalKeypair, fakeStableAddress); 71 | // console.log(withdraw); 72 | const finalProfessionalBalance = await provider.connection.getTokenAccountBalance(professionalVaultPublicKey); 73 | console.log("Initial finalProfessionalBalance: ", finalProfessionalBalance.value); 74 | 75 | } 76 | 77 | async function getOrCreateNeededAssociatedTokenAccount(payer, tokenAddress, owner) { 78 | return await getOrCreateAssociatedTokenAccount( 79 | provider.connection, 80 | payer, 81 | tokenAddress, 82 | owner, 83 | false, 84 | null, null, 85 | TOKEN_PROGRAM_ID, 86 | ASSOCIATED_TOKEN_PROGRAM_ID 87 | ); 88 | }; 89 | 90 | main() -------------------------------------------------------------------------------- /contracts/ethereum/test/StableVault/Withdraw.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers } = require("hardhat"); 3 | 4 | const FAKE_STABLE_DECIMALS = 18; 5 | 6 | xdescribe("vault", function () { 7 | let StableVault, vault, Token, token, admin, user1, user2; 8 | 9 | beforeEach(async function () { 10 | // Contract deployment 11 | StableVault = await ethers.getContractFactory("StableVault"); 12 | [admin, user1, user2] = await ethers.getSigners(); 13 | vault = await StableVault.deploy(admin.address, "StableVaultToken", "STBLV"); 14 | await vault.deployed(); 15 | 16 | // Deploy mock token 17 | Token = await ethers.getContractFactory("fakeStable"); 18 | token = await Token.deploy(ethers.utils.parseEther("1000000"), FAKE_STABLE_DECIMALS); // 1 million tokens 19 | await token.deployed(); 20 | 21 | // Transfer some tokens from admin to user1 and user2 22 | await token.connect(admin).transfer(user1.address, ethers.utils.parseEther("100")); // Transfer 100 tokens to user1 23 | await token.connect(admin).transfer(user2.address, ethers.utils.parseEther("100")); // Transfer 100 tokens to user2 24 | 25 | 26 | // Approve the StableVault contract to spend tokens on behalf of user1 27 | await token.connect(user1).approve(vault.address, ethers.utils.parseEther("1000")); // Approve 1000 tokens 28 | }); 29 | 30 | describe("Withdraw", function () { 31 | it("Should allow a user to make a withdrawal", async function () { 32 | const depositAmount = ethers.utils.parseUnits("1", FAKE_STABLE_DECIMALS); // 1 token with 8 decimals 33 | const expectedVaultAmount = ethers.utils.parseUnits("1", 18); // Expected to be 1 token but with 18 decimals 34 | 35 | // First, the user deposits 36 | await vault.connect(user1).deposit(depositAmount, token.address, user1.address); 37 | const userBalanceBeforeWithdraw = await vault.balanceOf(user1.address); 38 | expect(userBalanceBeforeWithdraw).to.equal(expectedVaultAmount); 39 | 40 | // Now, the user withdraws 41 | await vault.connect(user1).withdraw(depositAmount, token.address); 42 | const userBalanceAfterWithdraw = await vault.balanceOf(user1.address); 43 | expect(userBalanceAfterWithdraw).to.equal(0); // Assuming the user withdraws all their funds 44 | 45 | await vault.connect(user1).deposit(depositAmount, token.address, user1.address); 46 | await expect(vault.connect(user1).withdraw(depositAmount, token.address)) 47 | .to.emit(vault, "BalanceUpdated") 48 | .withArgs(depositAmount); 49 | }); 50 | 51 | it("Should fail if the user tries to withdraw more than their balance", async function () { 52 | const depositAmount = ethers.utils.parseUnits("1", FAKE_STABLE_DECIMALS); // 1 token with 8 decimals 53 | const overdrawAmount = ethers.utils.parseUnits("2", FAKE_STABLE_DECIMALS); // 2 tokens with 8 decimals 54 | 55 | // First, the user deposits 56 | await vault.connect(user1).deposit(depositAmount, token.address, user1.address); 57 | 58 | // Now, the user tries to overdraw 59 | await expect(vault.connect(user1).withdraw(overdrawAmount, token.address)).to.be.revertedWith("Insufficient balance"); 60 | }); 61 | 62 | it("Should fail if the contract is paused", async function () { 63 | await vault.connect(admin).pause(); // Assuming you have a pause function 64 | 65 | const amount = ethers.utils.parseEther("1"); 66 | 67 | await expect(vault.connect(user1).withdraw(amount, token.address)).to.be.revertedWith("Pausable: paused"); 68 | }); 69 | }); 70 | }); -------------------------------------------------------------------------------- /chains/solana/transactions/withdrawFromVault.js: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import * as anchor from "@coral-xyz/anchor"; 4 | import { 5 | TOKEN_PROGRAM_ID, 6 | ASSOCIATED_TOKEN_PROGRAM_ID, 7 | getAssociatedTokenAddress, 8 | getAccount, 9 | createAssociatedTokenAccountInstruction, 10 | } from "@solana/spl-token"; 11 | import bs58 from 'bs58'; 12 | 13 | function lamportsToSol(lamports) { 14 | return new BN(Math.round(lamports * Math.pow(10, 8))); 15 | } 16 | 17 | export const withdrawFromVault = async (details) => { 18 | const amountInLamports = lamportsToSol(details.amount) 19 | const balanceInLamports = lamportsToSol(details.balance.amount) 20 | 21 | if (amountInLamports.gt(balanceInLamports)) { 22 | // alert("You cannot redeem more than your balance!") 23 | throw new Error("You cannot redeem more than your balance!"); 24 | return 25 | } 26 | 27 | const professionalPublicKey = new PublicKey(details.account) 28 | 29 | //TODO: Make this token come from withdraw tokens list 30 | const fakeStablePubkey = new PublicKey(process.env.NEXT_PUBLIC_SOLANA_FAKE_STABLE_ADDRESS) 31 | const connection = details.contract.provider.connection 32 | 33 | let associatedTokenAddressProfessional = await getAssociatedTokenAddress( 34 | fakeStablePubkey, 35 | professionalPublicKey, 36 | false, 37 | TOKEN_PROGRAM_ID, 38 | ASSOCIATED_TOKEN_PROGRAM_ID 39 | ); 40 | 41 | let account; 42 | let transaction = new anchor.web3.Transaction() 43 | try { 44 | account = await getAccount(connection, associatedTokenAddressProfessional, "confirmed", TOKEN_PROGRAM_ID); 45 | } 46 | catch (error) { 47 | try { 48 | transaction.add( 49 | createAssociatedTokenAccountInstruction( 50 | professionalPublicKey, 51 | associatedTokenAddressProfessional, 52 | professionalPublicKey, 53 | fakeStablePubkey, 54 | TOKEN_PROGRAM_ID, 55 | ASSOCIATED_TOKEN_PROGRAM_ID 56 | ) 57 | ); 58 | 59 | } catch (error) { 60 | console.log(error); 61 | } 62 | } 63 | 64 | const [professionalVaultPublicKey, ___] = 65 | anchor.web3.PublicKey.findProgramAddressSync( 66 | [professionalPublicKey.toBytes(), fakeStablePubkey.toBytes()], 67 | details.contract.programId 68 | ); 69 | 70 | const stringBufferProfessional = Buffer.from("professional_agreements", "utf-8"); 71 | const [professionalAgreementsPublicKey, __] = anchor.web3.PublicKey.findProgramAddressSync( 72 | [stringBufferProfessional, professionalPublicKey.toBuffer()], 73 | details.contract.programId 74 | ); 75 | 76 | try { 77 | const instructionWithdraw = await details.contract.methods 78 | .withdraw(amountInLamports) 79 | .accounts({ 80 | professional: professionalPublicKey, 81 | professionalAta: associatedTokenAddressProfessional, 82 | vaultAta: professionalVaultPublicKey, 83 | professionalAgreements: professionalAgreementsPublicKey, 84 | tokenProgram: TOKEN_PROGRAM_ID, 85 | }).instruction(); 86 | 87 | transaction.add(instructionWithdraw); 88 | 89 | const latestBlockhash = await connection.getLatestBlockhash(); 90 | transaction.feePayer = professionalPublicKey; 91 | transaction.recentBlockhash = latestBlockhash.blockhash; 92 | 93 | const tx = await details.contract.provider.wallet.signTransaction(transaction); 94 | await connection.sendRawTransaction(tx.serialize()); 95 | 96 | const decodedSignature = bs58.encode(tx.signature); 97 | console.log("Your transaction signature:", decodedSignature); 98 | 99 | return decodedSignature; 100 | } catch (error) { 101 | console.log(error) 102 | throw new Error("Error in withdrawFromVault: ", error); 103 | } 104 | }; 105 | 106 | export default withdrawFromVault; -------------------------------------------------------------------------------- /contracts/ethereum/contracts/interfaces/IStableVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.1; 3 | 4 | interface IStableVault { 5 | 6 | /// @notice Emitted when the vault's balance is updated. 7 | /// @param _vaultBalance The new balance of the vault. 8 | event BalanceUpdated(uint256 _vaultBalance); 9 | 10 | /// @notice Emitted when a user withdraws assets from the vault. 11 | /// @param user The address of the user who made the withdrawal. 12 | /// @param amount The amount of the asset withdrawn. 13 | /// @param asset The address of the asset that was withdrawn. 14 | event Withdrawal(address indexed user, uint256 amount, address indexed asset); 15 | 16 | /// @notice Emitted when a deposit is made to the Aave Lending Pool. 17 | /// @param user The user who made the deposit. 18 | /// @param asset The address of the asset deposited. 19 | /// @param amount The amount of the asset deposited. 20 | event DepositAave(address indexed user, address asset, uint256 amount); 21 | 22 | /// @notice Deposits an asset into the Vault and mints vault tokens for the beneficiary. 23 | /// @param amount The amount of the asset to deposit. 24 | /// @param _asset The address of the asset token being deposited. 25 | /// @param _beneficiary The address that will receive the minted vault tokens. 26 | /// @return bool Indicates if the deposit was successful. 27 | function deposit(uint256 amount, address _asset, address _beneficiary) external returns(bool); 28 | 29 | /// @notice Withdraws an asset from the Vault and burns the corresponding vault tokens. 30 | /// @param amount The amount of the asset to withdraw. 31 | /// @param _asset The address of the asset token being withdrawn. 32 | /// @return bool Indicates if the withdrawal was successful. 33 | function withdraw(uint256 amount, address _asset) external returns(bool); 34 | 35 | /// @notice Sets the addresses for the Aave ecosystem components. 36 | /// @param _AAVE_DATA_PROVIDER Address of the Aave Data Provider. 37 | /// @param _AAVE_INCENTIVES_CONTROLLER Address of the Aave Incentives Controller. 38 | /// @param _AAVE_LENDING_POOL Address of the Aave Lending Pool. 39 | function setAaveSettings(address _AAVE_DATA_PROVIDER, address _AAVE_INCENTIVES_CONTROLLER, address _AAVE_LENDING_POOL) external; 40 | 41 | /// @notice Sets the user's preference for automatic compounding in the Aave Lending Pool. 42 | /// @param useCompound Boolean indicating whether to use compounding or not. 43 | /// @param wallet The address of the user's wallet. 44 | function setUserCompoundPreference(bool useCompound, address wallet) external; 45 | 46 | /// @notice Retrieves the current chain ID. 47 | /// @return uint256 The current chain ID. 48 | function getChainID() external view returns (uint256); 49 | 50 | /// @notice Checks if a given function is valid for the current network. 51 | /// @param functionName The name of the function to check. 52 | /// @return bool Indicates if the network is valid for the given function. 53 | function isValidNetworkForFunction(string memory functionName) external view returns (bool); 54 | 55 | /// @notice Updates the valid networks for a specific function. 56 | /// @param functionName The function name to update. 57 | /// @param chainIDs An array of chain IDs that are valid for the function. 58 | function updateValidNetworks(string memory functionName, uint256[] memory chainIDs) external; 59 | 60 | /// @notice Returns the current balance of the vault. 61 | /// @return uint256 The current vault balance. 62 | function vaultBalance() external view returns(uint256); 63 | 64 | /// @notice Retrieves the balance of Aave tokens for a given asset. 65 | /// @param _asset The address of the asset token. 66 | /// @return uint The balance of Aave tokens for the specified asset. 67 | function getAaveBalance(address _asset) external view returns(uint); 68 | } 69 | -------------------------------------------------------------------------------- /components/utils/web3inbox.js: -------------------------------------------------------------------------------- 1 | import { 2 | useManageSubscription, 3 | useSubscription, 4 | useW3iAccount, 5 | useInitWeb3InboxClient, 6 | useMessages, 7 | } from "@web3inbox/widget-react" 8 | import { useCallback, useEffect } from "react" 9 | import { useSignMessage, useAccount } from "wagmi" 10 | 11 | const projectId = process.env.NEXT_PUBLIC_WC_PROJECT_ID 12 | 13 | export default function Web3Inbox({ address }) { 14 | const { signMessageAsync } = useSignMessage() 15 | const isReady = useInitWeb3InboxClient({ 16 | // The project ID and domain you setup in the Domain Setup section 17 | projectId, 18 | domain: "betapp.kyodoprotocol.xyz", 19 | // Allow localhost development with "unlimited" mode. 20 | // This authorizes this dapp to control notification subscriptions for all domains (including `app.example.com`), not just `window.location.host` 21 | isLimited: false, 22 | }) 23 | 24 | const { account, setAccount, isRegistered, isRegistering, register } = useW3iAccount() 25 | 26 | useEffect(() => { 27 | if (!address) return 28 | // Convert the address into a CAIP-10 blockchain-agnostic account ID and update the Web3Inbox SDK with it 29 | setAccount(`eip155:1:${address}`) 30 | }, [address, setAccount]) 31 | 32 | const performRegistration = useCallback(async () => { 33 | if (!address) return 34 | try { 35 | await register((message) => signMessageAsync({ message })) 36 | } catch (registerIdentityError) { 37 | alert(registerIdentityError) 38 | } 39 | }, [signMessageAsync, register, address]) 40 | 41 | useEffect(() => { 42 | // Register even if an identity key exists, to account for stale keys 43 | performRegistration() 44 | }, [performRegistration]) 45 | 46 | const { isSubscribed, isSubscribing, subscribe } = useManageSubscription() 47 | 48 | const performSubscribe = useCallback(async () => { 49 | // Register again just in case 50 | await performRegistration() 51 | await subscribe() 52 | }, [subscribe, performRegistration]) 53 | 54 | const { subscription } = useSubscription() 55 | const { messages } = useMessages() 56 | 57 | return ( 58 | <> 59 | {!isReady ? ( 60 |
Loading client...
61 | ) : ( 62 | <> 63 | {!address ? ( 64 |
Connect your wallet
65 | ) : ( 66 | <> 67 |

Address: {address}

68 |

Account ID: {account}

69 | {!isRegistered ? ( 70 |

71 | To manage notifications, sign and register an identity key:  72 | 79 |

80 | ) : ( 81 | <> 82 | {!isSubscribed ? ( 83 | <> 84 | 91 | 92 | ) : ( 93 | <> 94 |
You are subscribed
95 |
Subscription: {JSON.stringify(subscription)}
96 |
Messages: {JSON.stringify(messages)}
97 | 98 | )} 99 | 100 | )} 101 | 102 | )} 103 | 104 | )} 105 | 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /contracts/solana/scripts/createAgreements.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import * as dotenv from "dotenv"; 4 | import path from "path"; 5 | dotenv.config({ path: path.resolve(__dirname, '../../../.env.development.local') }); 6 | 7 | async function createAgreement() { 8 | try { 9 | // Configure the client to use the local Solana cluster. 10 | const provider = anchor.AnchorProvider.local("https://api.devnet.solana.com"); 11 | 12 | anchor.setProvider(provider); 13 | 14 | // Load the agreement program from the workspace. 15 | const program = anchor.workspace.AgreementProgram; 16 | console.log("program", program.programId.toBase58()); 17 | // Fetch the public key of the company from the provider's wallet. 18 | const companyAddress = provider.wallet.publicKey; 19 | 20 | // Generate a new keypair for the professional's address. 21 | const professionalAddress = new PublicKey(process.env.SOL_PROFESSIONAL_ADDRESS); 22 | 23 | // Converting the string "company_agreements" to a buffer to be used for PDA calculations. 24 | const stringBuffer = Buffer.from("company_agreements", "utf-8"); 25 | const stringProfessionalBuffer = Buffer.from("professional_agreements", "utf-8"); 26 | 27 | // Generating a new keypair for the agreement's address. 28 | const agreementAddress = anchor.web3.Keypair.generate(); 29 | 30 | const communityDaoPubkey = new PublicKey(process.env.NEXT_PUBLIC_SOL_ASSOCIATED_TOKEN_ADDRESS_COMMUNITY); 31 | 32 | // Finding the Program Derived Address (PDA) for company agreements using the buffer and company address. 33 | const [companyAgreementsPublicKey, _] = anchor.web3.PublicKey.findProgramAddressSync( 34 | [stringBuffer, companyAddress.toBuffer()], 35 | program.programId 36 | ); 37 | 38 | // Finding the Program Derived Address (PDA) for company agreements using the buffer and company address. 39 | const [professionalAgreementsPublicKey, __] = anchor.web3.PublicKey.findProgramAddressSync( 40 | [stringProfessionalBuffer, professionalAddress.toBuffer()], 41 | program.programId 42 | ); 43 | // const amount = new anchor.BN(1000); 44 | const amount = new anchor.BN(1000) 45 | 46 | const agreement = { 47 | title: "new test from backend", 48 | description: "backend description", 49 | skills: ["JavaScript", "Rust", "Solana"], // You can replace these with actual skills 50 | paymentAmount: amount, 51 | communityDao: communityDaoPubkey, 52 | professional: professionalAddress, // Replace with the professional's public key 53 | } as any; 54 | 55 | // Initialize the agreement on-chain. 56 | const tx = await program.methods 57 | .initializeAgreement(agreement) 58 | .accounts({ 59 | agreement: agreementAddress.publicKey, 60 | company: companyAddress, 61 | professional: professionalAddress, 62 | professionalAgreements: professionalAgreementsPublicKey, 63 | companyAgreements: companyAgreementsPublicKey, // The PDA address, you'll have to compute this based on your program logic 64 | systemProgram: anchor.web3.SystemProgram.programId, 65 | }) 66 | .signers([agreementAddress]) 67 | .rpc(); 68 | 69 | 70 | console.log("tx", tx); 71 | 72 | const fetchedAgreement = await program.account.agreementAccount.fetch( 73 | agreementAddress.publicKey 74 | ); 75 | console.log("Your agreement account:", fetchedAgreement); 76 | 77 | const fetchedCompanyAgreements = await program.account.companyAgreements.fetch( 78 | companyAgreementsPublicKey 79 | ); 80 | 81 | console.log("fetchedCompanyAgreements", fetchedCompanyAgreements) 82 | 83 | console.log("Agreement created successfully"); 84 | console.log("Transaction signature:", tx); 85 | } catch (error) { 86 | console.error("Error creating agreement:", error); 87 | } 88 | } 89 | // Call the createAgreement function to create a new agreement. 90 | createAgreement(); -------------------------------------------------------------------------------- /components/ConnectWalletButton/ConnectWalletButton.module.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/variables"; 2 | 3 | /** 4 | CONNECT WALLET 5 | =========================== 6 | */ 7 | 8 | .connect-wallet-bg { 9 | background-image: url("../../public/login-bg.jpg"); 10 | background-size: cover; 11 | background-repeat: no-repeat; 12 | background-position: top; 13 | height: 100vh; 14 | 15 | @media #{$smartphones-1} { 16 | background: $white; 17 | height: auto; 18 | } 19 | } 20 | 21 | .home-entry { 22 | background: rgba($white, .93); 23 | text-align: center; 24 | position: absolute; 25 | box-sizing: border-box; 26 | right: 0; 27 | width: 420px; 28 | height: 100vh; 29 | display: flex; 30 | justify-content: center; 31 | align-items: center; 32 | 33 | @media #{$smartphones-1} { 34 | width: 100%; 35 | position: inherit; 36 | padding: 0 20px; 37 | } 38 | 39 | h2 { 40 | margin: 80px 0 15px 0; 41 | font-weight: 600; 42 | font-size: $font-large-3; 43 | color: $light-gray; 44 | line-height: 47px; 45 | color: $dark-gray; 46 | letter-spacing: -1px; 47 | 48 | @media #{$smartphones-1} { 49 | font-size: $font-large-2; 50 | line-height: 34px; 51 | margin-top: 50px; 52 | } 53 | } 54 | 55 | img { 56 | width: 130px; 57 | 58 | @media #{$smartphones-1} { 59 | width: 100px; 60 | } 61 | } 62 | } 63 | 64 | 65 | // Connect wallet btn 66 | 67 | .connect-wallet { 68 | background: linear-gradient(to right, $brand-green, $brand-red, $brand-blue); 69 | display: inline-block; 70 | border-radius: 30px; 71 | border: none; 72 | padding: 0; 73 | cursor: pointer; 74 | 75 | div { 76 | border-radius: 30px; 77 | text-decoration: none; 78 | font-weight: 600; 79 | background: $white; 80 | height: 38px; 81 | line-height: 38px; 82 | display: inline-block; 83 | font-size: 14px; 84 | text-align: center; 85 | box-sizing: border-box; 86 | padding: 0 25px; 87 | color: $medium-blue; 88 | margin: 2px; 89 | cursor: pointer; 90 | 91 | &:hover { 92 | opacity: .8; 93 | } 94 | } 95 | } 96 | 97 | 98 | // Modal 99 | 100 | .modal { 101 | background: linear-gradient(-110deg, rgba(6, 6, 6, .98) 0%, rgba(47, 47, 47, .98) 100%); 102 | position: absolute; 103 | left: 0; 104 | right: 0; 105 | bottom: 0; 106 | top: 0; 107 | z-index: 9999; 108 | display: flex; 109 | align-items: center; 110 | text-align: center; 111 | 112 | .modal-content { 113 | width: 450px; 114 | margin: auto; 115 | 116 | @media #{$smartphones-1} { 117 | width: 90%; 118 | } 119 | } 120 | 121 | h3 { 122 | font-weight: bold; 123 | margin: 0 0 50px 0; 124 | } 125 | 126 | .close-modal { 127 | right: 20px; 128 | top: 18px; 129 | position: absolute; 130 | cursor: pointer; 131 | 132 | &:hover { 133 | opacity: .8; 134 | } 135 | } 136 | } 137 | 138 | .chains-list { 139 | 140 | li { 141 | list-style-type: none; 142 | margin-bottom: 20px; 143 | text-align: left; 144 | 145 | @media #{$smartphones-1} { 146 | text-align: center; 147 | } 148 | } 149 | 150 | a { 151 | background: rgba($white, .1); 152 | width: 100%; 153 | display: inline-block; 154 | cursor: pointer; 155 | padding: 27px 17px 20px 23px; 156 | border-radius: 60px; 157 | box-sizing: border-box; 158 | 159 | @media #{$smartphones-1} { 160 | padding: 27px; 161 | border-radius: 6px; 162 | } 163 | 164 | &:hover { 165 | background: rgba($white, .2); 166 | } 167 | } 168 | 169 | h4 { 170 | margin: 0 0 7px 0; 171 | 172 | span { 173 | font-weight: normal; 174 | } 175 | } 176 | 177 | p { 178 | font-size: $font-medium; 179 | color: $light-gray; 180 | line-height: 20px; 181 | } 182 | 183 | img { 184 | margin: -10px 20px 0 0; 185 | float: left; 186 | 187 | @media #{$smartphones-1} { 188 | float: none; 189 | margin: 0 0 10px 0; 190 | } 191 | } 192 | } 193 | 194 | .disabled { 195 | opacity: .35; 196 | } -------------------------------------------------------------------------------- /styles/components.scss: -------------------------------------------------------------------------------- 1 | /** 2 | COMPONENTS 3 | =========================== 4 | */ 5 | 6 | 7 | // View all link 8 | 9 | .view-all { 10 | display: inline-block; 11 | padding: 0 10px; 12 | border: 1px solid $dark-blue-2; 13 | height: 20px; 14 | line-height: 20px; 15 | border-radius: 20px; 16 | font-size: 11px; 17 | color: $medium-gray; 18 | cursor: pointer; 19 | margin-top: 20px; 20 | 21 | &:hover { 22 | border-color: $medium-gray; 23 | color: $white; 24 | } 25 | } 26 | 27 | 28 | // Flash messages 29 | .flash-success, 30 | .flash-error, 31 | .transaction-info { 32 | width: 100%; 33 | background-color: $dark-blue-2; 34 | height: 45px; 35 | line-height: 41px; 36 | font-size: $font-medium; 37 | box-sizing: border-box; 38 | padding: 0 3%; 39 | position: fixed; 40 | left: 0; 41 | top: 0; 42 | z-index: 9999; 43 | 44 | img { 45 | float: left; 46 | margin: 11px 10px 0 0; 47 | } 48 | 49 | p { 50 | margin: 0; 51 | float: left; 52 | } 53 | 54 | a { 55 | color: $light-gray; 56 | text-decoration: none; 57 | font-size: 12px; 58 | text-align: right; 59 | float: right; 60 | 61 | &:hover { 62 | color: $white; 63 | } 64 | } 65 | } 66 | 67 | .flash-success { 68 | border-bottom: 3px solid $green; 69 | } 70 | 71 | .flash-pending { 72 | border-bottom: 3px solid $medium-gray; 73 | } 74 | 75 | .flash-error { 76 | border-bottom: 3px solid $red; 77 | } 78 | 79 | .flash-alert { 80 | border-bottom: 3px solid $orange; 81 | } 82 | 83 | 84 | 85 | // Loading 86 | 87 | .loading-overlay { 88 | position: fixed; 89 | top: 0; 90 | left: 0; 91 | width: 100%; 92 | height: 100%; 93 | background: linear-gradient(110deg, rgba(6, 6, 6, .99) 0%, rgba(47, 47, 47, .98) 100%); 94 | display: flex; 95 | opacity: .8; 96 | justify-content: center; 97 | align-items: center; 98 | z-index: 9999; 99 | } 100 | 101 | .sweet-loading { 102 | display: block; 103 | margin: 0 auto; 104 | } 105 | 106 | 107 | // Tooltip 108 | 109 | i { 110 | font-weight: normal; 111 | line-height: 16px; 112 | } 113 | 114 | [data-tooltip]::before { 115 | content: attr(data-tooltip); 116 | } 117 | 118 | [data-tooltip] { 119 | position: relative; 120 | display: inline-block; 121 | } 122 | 123 | [data-tooltip]::before { 124 | position: absolute; 125 | z-index: 999; 126 | } 127 | 128 | [data-tooltip].tooltip-top::before { 129 | bottom: 100%; 130 | margin-bottom: 0; 131 | } 132 | 133 | [data-tooltip].bottom::before { 134 | top: 100%; 135 | } 136 | 137 | [data-tooltip]::before { 138 | visibility: hidden; 139 | opacity: 0; 140 | transition: 141 | opacity 1s; 142 | } 143 | 144 | [data-tooltip]:hover::before { 145 | visibility: visible; 146 | opacity: 1; 147 | } 148 | 149 | [data-tooltip] { 150 | //background: $white; 151 | } 152 | 153 | [data-tooltip]::before { 154 | background: #333; 155 | color: $white; 156 | padding: 8px 12px; 157 | min-width: 165px; 158 | text-align: center; 159 | border-radius: 6px; 160 | font-size: $font-small; 161 | box-sizing: border-box; 162 | font-style: normal; 163 | right: 0; 164 | } 165 | .default-component { 166 | width: 800px; 167 | max-width: 615px; 168 | padding-bottom: 100px; 169 | margin: 0 auto; 170 | 171 | @media #{$smartphones-1} { 172 | width: 90%; 173 | margin: auto; 174 | max-width: 100%; 175 | padding-bottom: 35px; 176 | } 177 | } 178 | 179 | .form-button { 180 | background: linear-gradient(to right, $brand-green, $brand-red, $brand-blue); 181 | display: inline-block; 182 | border-radius: 30px; 183 | border: none; 184 | padding: 0; 185 | 186 | div { 187 | border-radius: 30px; 188 | text-decoration: none; 189 | font-weight: 600; 190 | background: $dark-blue; 191 | height: 38px; 192 | line-height: 36px; 193 | display: inline-block; 194 | font-size: 14px; 195 | text-align: center; 196 | box-sizing: border-box; 197 | padding: 0 35px; 198 | color: $white; 199 | margin: 2px; 200 | cursor: pointer; 201 | 202 | &:hover { 203 | opacity: .8; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /public/dashboard/discord-icon-purple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------