├── .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 |
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 |
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 |
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 |
6 |
--------------------------------------------------------------------------------
/public/close-modal.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/close.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
16 | )}
17 |
18 | );
19 | }
20 |
21 | export default Loader;
22 |
--------------------------------------------------------------------------------
/public/add.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/onboarding/big-success-icon.svg:
--------------------------------------------------------------------------------
1 |
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 |
11 |
--------------------------------------------------------------------------------
/public/info-icon.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/public/social/telegram-icon.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
10 |
--------------------------------------------------------------------------------
/public/social/linkedin-icon.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
13 |
--------------------------------------------------------------------------------
/public/onboarding/professional-call-icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/onboarding/current-icon.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/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 |
8 |
--------------------------------------------------------------------------------
/public/alert-icon.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
22 |
--------------------------------------------------------------------------------
/public/chains/polygon-matic-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
5 |
--------------------------------------------------------------------------------
/public/dashboard/community/rewards-icon-green.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/public/dashboard/community/benefits-icon-green.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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 |
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 |
11 |
--------------------------------------------------------------------------------
/public/coins/usdc-icon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/dashboard/community/social-icon-green.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/public/dashboard/contractor/social-icon-blue.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/public/dashboard/professional/social-icon-red.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/public/dashboard/community/members-icon-green.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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 |
45 | {t("transaction-success")}
46 |
47 |
52 | {t("view-on")}
53 |
54 |
55 | )
56 | }
57 |
58 | if (transactionPending) {
59 | return (
60 |
61 |
62 |
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 |
82 | {displayMessage}
83 |
84 |
85 | )
86 | }
87 |
88 | if (transactionFail) {
89 | let displayMessage = errorMessage
90 | return (
91 |
92 |
100 |
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 |
15 |
--------------------------------------------------------------------------------