├── devnet-backup
├── db-linux
│ └── 0
│ │ ├── rocksDb
│ │ ├── LOCK
│ │ ├── CURRENT
│ │ ├── IDENTITY
│ │ ├── 000031.sst
│ │ └── MANIFEST-000025
│ │ └── sqlite
│ │ ├── pact-v1-chain-0.sqlite
│ │ ├── pact-v1-chain-1.sqlite
│ │ ├── pact-v1-chain-10.sqlite
│ │ ├── pact-v1-chain-11.sqlite
│ │ ├── pact-v1-chain-12.sqlite
│ │ ├── pact-v1-chain-13.sqlite
│ │ ├── pact-v1-chain-14.sqlite
│ │ ├── pact-v1-chain-15.sqlite
│ │ ├── pact-v1-chain-16.sqlite
│ │ ├── pact-v1-chain-17.sqlite
│ │ ├── pact-v1-chain-18.sqlite
│ │ ├── pact-v1-chain-19.sqlite
│ │ ├── pact-v1-chain-2.sqlite
│ │ ├── pact-v1-chain-3.sqlite
│ │ ├── pact-v1-chain-4.sqlite
│ │ ├── pact-v1-chain-5.sqlite
│ │ ├── pact-v1-chain-6.sqlite
│ │ ├── pact-v1-chain-7.sqlite
│ │ ├── pact-v1-chain-8.sqlite
│ │ └── pact-v1-chain-9.sqlite
├── db-macos
│ └── 0
│ │ ├── rocksDb
│ │ ├── LOCK
│ │ ├── CURRENT
│ │ ├── IDENTITY
│ │ ├── 000009.sst
│ │ └── MANIFEST-000004
│ │ └── sqlite
│ │ ├── pact-v1-chain-0.sqlite
│ │ ├── pact-v1-chain-1.sqlite
│ │ ├── pact-v1-chain-10.sqlite
│ │ ├── pact-v1-chain-11.sqlite
│ │ ├── pact-v1-chain-12.sqlite
│ │ ├── pact-v1-chain-13.sqlite
│ │ ├── pact-v1-chain-14.sqlite
│ │ ├── pact-v1-chain-15.sqlite
│ │ ├── pact-v1-chain-16.sqlite
│ │ ├── pact-v1-chain-17.sqlite
│ │ ├── pact-v1-chain-18.sqlite
│ │ ├── pact-v1-chain-19.sqlite
│ │ ├── pact-v1-chain-2.sqlite
│ │ ├── pact-v1-chain-3.sqlite
│ │ ├── pact-v1-chain-4.sqlite
│ │ ├── pact-v1-chain-5.sqlite
│ │ ├── pact-v1-chain-6.sqlite
│ │ ├── pact-v1-chain-7.sqlite
│ │ ├── pact-v1-chain-8.sqlite
│ │ └── pact-v1-chain-9.sqlite
└── README.md
├── 03-charkha-lending
├── frontend
│ ├── .env.example
│ ├── screenshots
│ │ ├── 01-deployment.png
│ │ ├── 05-submit-lend.png
│ │ ├── 13-repay-loan.png
│ │ ├── 08-claimed-chrk.png
│ │ ├── 09-proposal-open.png
│ │ ├── 04-with-participants.png
│ │ ├── 11-proposal-accepted.png
│ │ ├── 02-initial-market-state.png
│ │ ├── 03-initial-account-state.png
│ │ ├── 12-market-after-proposal.png
│ │ ├── 06-initial-governance-state.png
│ │ ├── 07-submit-proposal-failure.png
│ │ └── 10-market-before-proposal.png
│ ├── src
│ │ ├── env.d.ts
│ │ ├── components
│ │ │ ├── RouteLink.tsx
│ │ │ ├── UserDetails.tsx
│ │ │ ├── AccountModal.tsx
│ │ │ └── ControllerModal.tsx
│ │ ├── contracts
│ │ │ ├── README.md
│ │ │ ├── cwKDA.ts
│ │ │ ├── cwCHRK.ts
│ │ │ ├── cwKETH.ts
│ │ │ ├── keth.ts
│ │ │ ├── interfaces
│ │ │ │ ├── fungible-v2.ts
│ │ │ │ └── market.ts
│ │ │ ├── chrk.ts
│ │ │ ├── oracle.ts
│ │ │ └── governance.ts
│ │ ├── pact-api.ts
│ │ ├── main.tsx
│ │ ├── routes
│ │ │ └── root.tsx
│ │ ├── coinmarketcap-api.ts
│ │ └── constants.tsx
│ ├── index.html
│ ├── package.json
│ ├── vite.config.js
│ └── README.md
├── Charkha-Protocol-Examples.pdf
├── Charkha-Protocol-Whitepaper.pdf
├── charkha-keys.yaml
├── guide
│ └── README.md
├── contracts
│ ├── verify.repl
│ ├── setup.repl
│ ├── README.md
│ ├── oracle
│ │ ├── oracle.repl
│ │ └── oracle.pact
│ └── interfaces
│ │ └── market-interface.pact
└── README.md
├── package.json
├── .gitmodules
├── 02-goliath-wallet
├── goliath.png
├── src
│ ├── main.tsx
│ ├── contracts
│ │ ├── README.md
│ │ └── coin-v5.ts
│ ├── components
│ │ ├── README.md
│ │ ├── ReturnFundsModal.tsx
│ │ ├── RequestFundsModal.tsx
│ │ └── AdminModal.tsx
│ ├── pact-api.ts
│ ├── accounts.ts
│ └── App.tsx
├── index.html
├── vite.config.js
├── package.json
└── README.md
├── pnpm-workspace.yaml
├── theme
├── README.md
├── package.json
├── components
│ ├── Form.tsx
│ ├── Container.tsx
│ ├── Text.tsx
│ ├── Spinner.tsx
│ ├── Icon.tsx
│ ├── GlobalStyles.tsx
│ ├── Request.tsx
│ └── Button.tsx
└── styled.config.ts
├── pact-api-utils
├── package.json
├── README.md
├── pact-code.ts
├── pact-request-hooks.ts
└── types
│ └── pact-lang-api.d.ts
├── 01-faucet-contract
├── internal
│ ├── README.md
│ └── localhost.js
├── keys
│ ├── sender00.yaml
│ ├── test-user.yaml
│ └── goliath-faucet.yaml
├── request
│ ├── local
│ │ ├── faucet-details.yaml
│ │ ├── faucet-contract-details.yaml
│ │ ├── user-limits.yaml
│ │ └── user-details.yaml
│ └── send
│ │ ├── request-funds-over-limit.yaml
│ │ ├── set-user-account-limit.yaml
│ │ ├── set-user-request-limit.yaml
│ │ ├── return-funds.yaml
│ │ ├── deploy-faucet-contract.yaml
│ │ ├── request-funds.yaml
│ │ └── fund-faucet-account.yaml
└── run-deploy-contract.js
├── .editorconfig
├── .gitignore
├── tsconfig.json
├── 00-core-concepts
└── README.md
├── flake.nix
├── LICENSE
├── pact-repl-utils
├── init-contracts.repl
├── contracts
│ ├── fungible-xchain-v1.pact
│ ├── fungible-v2.pact
│ └── fungible-v2-modified.pact
├── README.md
├── init-namespaces.repl
├── init-gasmodel.repl
├── init.repl
└── init-accounts.repl
├── devshell.toml
└── flake.lock
/devnet-backup/db-linux/0/rocksDb/LOCK:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/rocksDb/LOCK:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/.env.example:
--------------------------------------------------------------------------------
1 | VITE_CMC_API_KEY=
2 |
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/rocksDb/CURRENT:
--------------------------------------------------------------------------------
1 | MANIFEST-000025
2 |
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/rocksDb/CURRENT:
--------------------------------------------------------------------------------
1 | MANIFEST-000004
2 |
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/rocksDb/IDENTITY:
--------------------------------------------------------------------------------
1 | d8d936d9-dc78-47dc-a76b-24d019f06973
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/rocksDb/IDENTITY:
--------------------------------------------------------------------------------
1 | 1e8aff8a-41ed-4d46-a4ec-178f1881bf00
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "real-world-pact",
3 | "version": "0.0.0",
4 | "license": "MIT"
5 | }
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "devnet"]
2 | path = devnet
3 | url = https://github.com/kadena-io/devnet
4 | ignore = untracked
5 |
--------------------------------------------------------------------------------
/02-goliath-wallet/goliath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/02-goliath-wallet/goliath.png
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "02-goliath-wallet"
3 | - "03-charkha-lending/frontend"
4 | - "pact-api-utils"
5 | - "theme"
6 |
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/rocksDb/000031.sst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/rocksDb/000031.sst
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/rocksDb/000009.sst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/rocksDb/000009.sst
--------------------------------------------------------------------------------
/03-charkha-lending/Charkha-Protocol-Examples.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/Charkha-Protocol-Examples.pdf
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/rocksDb/MANIFEST-000025:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/rocksDb/MANIFEST-000025
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/rocksDb/MANIFEST-000004:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/rocksDb/MANIFEST-000004
--------------------------------------------------------------------------------
/03-charkha-lending/Charkha-Protocol-Whitepaper.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/Charkha-Protocol-Whitepaper.pdf
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-0.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-0.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-1.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-1.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-10.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-10.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-11.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-11.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-12.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-12.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-13.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-13.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-14.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-14.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-15.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-15.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-16.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-16.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-17.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-17.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-18.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-18.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-19.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-19.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-2.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-2.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-3.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-3.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-4.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-4.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-5.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-5.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-6.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-6.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-7.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-7.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-8.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-8.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-linux/0/sqlite/pact-v1-chain-9.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-linux/0/sqlite/pact-v1-chain-9.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-0.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-0.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-1.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-1.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-10.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-10.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-11.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-11.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-12.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-12.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-13.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-13.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-14.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-14.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-15.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-15.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-16.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-16.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-17.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-17.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-18.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-18.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-19.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-19.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-2.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-2.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-3.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-3.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-4.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-4.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-5.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-5.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-6.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-6.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-7.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-7.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-8.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-8.sqlite
--------------------------------------------------------------------------------
/devnet-backup/db-macos/0/sqlite/pact-v1-chain-9.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/devnet-backup/db-macos/0/sqlite/pact-v1-chain-9.sqlite
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/01-deployment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/01-deployment.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/05-submit-lend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/05-submit-lend.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/13-repay-loan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/13-repay-loan.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/08-claimed-chrk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/08-claimed-chrk.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/09-proposal-open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/09-proposal-open.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/04-with-participants.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/04-with-participants.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/11-proposal-accepted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/11-proposal-accepted.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/02-initial-market-state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/02-initial-market-state.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/03-initial-account-state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/03-initial-account-state.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/12-market-after-proposal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/12-market-after-proposal.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/06-initial-governance-state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/06-initial-governance-state.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/07-submit-proposal-failure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/07-submit-proposal-failure.png
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/screenshots/10-market-before-proposal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomashoneyman/real-world-pact/HEAD/03-charkha-lending/frontend/screenshots/10-market-before-proposal.png
--------------------------------------------------------------------------------
/02-goliath-wallet/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 |
5 | ReactDOM.createRoot(document.getElementById("root")!).render();
6 |
--------------------------------------------------------------------------------
/theme/README.md:
--------------------------------------------------------------------------------
1 | # Theme
2 |
3 | This directory contains the theme for Goliath. The code here is needed for the app, but it's uncommented because this is typical frontend code that has nothing to do with Pact or Chainweb.
4 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface ImportMetaEnv {
4 | readonly VITE_CMC_API_KEY: string;
5 | }
6 |
7 | interface ImportMeta {
8 | readonly env: ImportMetaEnv;
9 | }
10 |
--------------------------------------------------------------------------------
/pact-api-utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@real-world-pact/utils",
3 | "private": true,
4 | "dependencies": {
5 | "pact-lang-api": "^4.3.5",
6 | "react": "^18.0.0"
7 | },
8 | "devDependencies": {
9 | "@types/react": "^18.0.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/01-faucet-contract/internal/README.md:
--------------------------------------------------------------------------------
1 | # Internal Scripts
2 |
3 | These helper scripts are internal to the project and simply make it easier to test out making requests. They have no Pact or Chainweb-specific code and solely exist to make the Node scripts in this directory zero-dependency.
4 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/components/RouteLink.tsx:
--------------------------------------------------------------------------------
1 | import { LinkStyles } from "@real-world-pact/theme/components/Text";
2 | import { styled } from "@real-world-pact/theme/styled.config";
3 | import { Link } from "react-router-dom";
4 |
5 | export const RouteLink = styled(Link, LinkStyles);
6 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/README.md:
--------------------------------------------------------------------------------
1 | # Contracts
2 |
3 | The files in this directory are bindings to various Pact modules that our frontend refers to. There are, for example, bindings to every one of the [Pact contracts we wrote](../../../contracts/). There are also bindings to other modules and interfaces, too, such as the `fungible-v2` interface.
4 |
--------------------------------------------------------------------------------
/03-charkha-lending/charkha-keys.yaml:
--------------------------------------------------------------------------------
1 | # This file contains the keypair used to control the Charkha admin account. A
2 | #
3 | # These keys were generated via the Pact 4.3.1 CLI:
4 | # pact --genkey > charkha-keys.yaml
5 | public: 550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf
6 | secret: 2a9d9112e96e8a299091e067879c50d1cbefbd9c7fff78e2623d7e3f0cd2bf45
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [*.ts]
16 | max_line_length = 100
17 |
18 | [*.tsx]
19 | max_line_length = 100
20 |
--------------------------------------------------------------------------------
/01-faucet-contract/keys/sender00.yaml:
--------------------------------------------------------------------------------
1 | # There are a number of genesis accounts on devnet that exist and have funds
2 | # that can be used for testing:
3 | # https://github.com/kadena-io/chainweb-node/blob/master/pact/genesis/devnet/keys.yaml
4 | #
5 | # These keys are for the 'sender00' account.
6 | public: 368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca
7 | secret: 251a920c403ae8c8f65f59142316af3c82b631fba46ddea92ee8c95035bd2898
8 |
--------------------------------------------------------------------------------
/theme/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@real-world-pact/theme",
3 | "private": true,
4 | "dependencies": {
5 | "@real-world-pact/utils": "workspace:*",
6 | "@radix-ui/colors": "^0.1.8",
7 | "@radix-ui/react-dialog": "^0.1.8-rc.39",
8 | "@radix-ui/react-icons": "^1.1.1",
9 | "@stitches/react": "^1.2.8",
10 | "react": "^18.0.0",
11 | "react-dom": "^18.0.0"
12 | },
13 | "devDependencies": {
14 | "@types/react": "^18.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Devnet
2 | devnet/db
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | pnpm-debug.log*
11 | lerna-debug.log*
12 |
13 | node_modules
14 | dist
15 | dist-ssr
16 | *.local
17 |
18 | # Editor directories and files
19 | .vscode/*
20 | !.vscode/extensions.json
21 | .idea
22 | .DS_Store
23 | *.suo
24 | *.ntvs*
25 | *.njsproj
26 | *.sln
27 | *.sw?
28 |
29 | # Pact
30 | .pact-history
31 |
32 | # Hidden
33 | **/*.env
34 |
--------------------------------------------------------------------------------
/02-goliath-wallet/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Goliath
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Charkha
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/theme/components/Form.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "../styled.config";
2 |
3 | export const Fieldset = styled("fieldset", {
4 | display: "flex",
5 | flexDirection: "column",
6 | gap: "$2",
7 | marginBottom: "$3",
8 | });
9 |
10 | export const Label = styled("label", {
11 | fontSize: "$xs",
12 | color: "$crimson11",
13 | });
14 |
15 | export const Input = styled("input", {
16 | flex: "1",
17 | display: "inline-flex",
18 | alignItems: "center",
19 | justifyContent: "center",
20 | });
21 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/contracts/README.md:
--------------------------------------------------------------------------------
1 | # Contracts
2 |
3 | This directory implements bindings to two vital contracts:
4 |
5 | - `coin-v5.ts` binds to the [coin-v5.pact](https://github.com/kadena-io/chainweb-node/blob/master/pact/coin-contract/v5/coin-v5.pact) foundational contract. We're only using one function from this contract, so the file is small.
6 | - `goliath-faucet.ts` binds to the [faucet.pact](../../../01-faucet-contract/faucet.pact) contract we developed in Project 1. This is a full set of bindings: all constants and functions are represented here in TypeScript.
7 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/local/faucet-details.yaml:
--------------------------------------------------------------------------------
1 | # This local request looks up the details associated with the 'goliath-faucet
2 | # address. If this account does not yet exist then this request will fail with
3 | # a "row not found: goliath-faucet" error.
4 | #
5 | # To execute this request:
6 | # faucet-request --local faucet-details
7 | #
8 | # If you would like to fund the faucet account, please see the
9 | # `request/send/fund-faucet-account.yaml` request file.
10 |
11 | code: (coin.details "goliath-faucet")
12 | publicMeta:
13 | chainId: "0"
14 | gasLimit: 600
15 |
--------------------------------------------------------------------------------
/01-faucet-contract/keys/test-user.yaml:
--------------------------------------------------------------------------------
1 | # This file contains a keypair usable for creating a test user account on devnet
2 | # that can exercise the Goliath faucet API. We can use this account to request
3 | # funds and ensure that they were received properly. On testnet, this would be
4 | # an actual user account that wishes to use the faucet.
5 | #
6 | # These keys were generated via the Pact v4.3.1 CLI:
7 | # pact --genkey > keys/test-user.yaml
8 | public: eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0
9 | secret: 73ee51d18cdd0bbc3a944b9dcd6cd6f109082974753216043cf157db8cdbdeb1
10 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/local/faucet-contract-details.yaml:
--------------------------------------------------------------------------------
1 | # This local request looks up the details associated with the 'goliath-faucet
2 | # smart contract. It will return the contents of the smart contract, if it has
3 | # been deployed to the Chainweb node.
4 | #
5 | # To execute this request:
6 | # faucet-request --local faucet-contract-details
7 | #
8 | # If you would like to deploy the faucet smart contract, please see the
9 | # `request/send/deploy-faucet-contract.yaml` request file.
10 |
11 | code: (describe-module "free.goliath-faucet")
12 | publicMeta:
13 | chainId: "0"
14 | gasLimit: 600
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | },
19 | "include": ["theme", "02-goliath-wallet", "03-charkha-lending", "pact-api-utils"],
20 | }
21 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/pact-api.ts:
--------------------------------------------------------------------------------
1 | import { NetworkId } from "pact-lang-api";
2 | import { HostName, PactAPI } from "@real-world-pact/utils/pact-request";
3 | import { makeUsePactRequest } from "@real-world-pact/utils/pact-request-hooks";
4 |
5 | const networkId: NetworkId = "development";
6 | const hostname: HostName = "localhost:8080";
7 | const chainId: string = "0";
8 |
9 | // This new 'pactAPI' is configured with our defaults and can be used to execute
10 | // 'local' (read-only) and 'send' (modifies the blockchain) requests.
11 | export const pactAPI = new PactAPI({ networkId, hostname, chainId });
12 | export const usePactRequest = makeUsePactRequest(pactAPI);
13 |
--------------------------------------------------------------------------------
/01-faucet-contract/keys/goliath-faucet.yaml:
--------------------------------------------------------------------------------
1 | # This file contains the keypair used to control the Goliath faucet contract. A
2 | # keypair consists of a public key, which is shared with others (for example, it
3 | # is listed in the keyset that guards the goliath-faucet account), as well as a
4 | # secret key, which should never be shared or committed. We've only committed
5 | # this file because this is a demo project.
6 | #
7 | # These keys were generated via the Pact v4.3.1 CLI:
8 | # pact --genkey > keys/goliath-faucet.yaml
9 | public: 550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf
10 | secret: 2a9d9112e96e8a299091e067879c50d1cbefbd9c7fff78e2623d7e3f0cd2bf45
11 |
--------------------------------------------------------------------------------
/theme/components/Container.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "../styled.config";
2 |
3 | export const Grid = styled("div", { display: "grid" });
4 |
5 | export const Flex = styled("div", { display: "flex" });
6 |
7 | export const Box = styled("div", {});
8 |
9 | export const Container = styled("div", {
10 | margin: "0 auto",
11 |
12 | defaultVariants: {
13 | size: "md",
14 | },
15 |
16 | variants: {
17 | size: {
18 | sm: {
19 | maxWidth: "$container-sm",
20 | },
21 | md: {
22 | maxWidth: "$container-md",
23 | },
24 | lg: {
25 | maxWidth: "$container-lg",
26 | },
27 | xl: {
28 | maxWidth: "$container-xl",
29 | },
30 | },
31 | },
32 | });
33 |
--------------------------------------------------------------------------------
/theme/components/Text.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "@stitches/react";
2 |
3 | export const Header = styled("h1", {
4 | marginBottom: "$2",
5 | });
6 |
7 | export const LinkStyles = {
8 | color: "$mauve12",
9 | textDecoration: "underline",
10 | fontWeight: "bold",
11 |
12 | "&:visited": {
13 | color: "$mauve12",
14 | },
15 |
16 | "&:active": {
17 | color: "$mauve12",
18 | },
19 |
20 | "&:hover": {
21 | cursor: "pointer",
22 | textDecoration: "none",
23 | },
24 | };
25 |
26 | export const Link = styled("a", LinkStyles);
27 |
28 | export const Text = styled("p", {
29 | variants: {
30 | color: {
31 | primary: {
32 | color: "$crimson11",
33 | },
34 | },
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/02-goliath-wallet/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | /* Support use of Node builtins in the browser, ie. Buffer */
5 | import NodeGlobalsPolyfillPlugin from "@esbuild-plugins/node-globals-polyfill";
6 | import rollupNodePolyFill from "rollup-plugin-node-polyfills";
7 |
8 | // https://vitejs.dev/config/
9 | export default defineConfig({
10 | plugins: [react()],
11 | optimizeDeps: {
12 | esbuildOptions: {
13 | define: {
14 | global: "globalThis",
15 | },
16 | plugins: [NodeGlobalsPolyfillPlugin({ buffer: true })],
17 | },
18 | },
19 | build: {
20 | rollupOptions: {
21 | plugins: [rollupNodePolyFill()],
22 | },
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/00-core-concepts/README.md:
--------------------------------------------------------------------------------
1 | # Core Concepts
2 |
3 | The Core Concepts series is an introduction to building decentralized applications in the Kadena ecosystem using the Pact programming language. It's a companion to the projects in Real World Pact and should be read first. The series has three articles:
4 |
5 | 1. [Introduction to Blockchain Development with Kadena](./01-Introduction.md)
6 | 2. [Learn Pact in 20 Minutes](./02-Pact-In-20-Minutes.md)
7 | 3. [Testing and Formal Verification in the Pact REPL](./03-Testing-In-The-Pact-REPL.md)
8 |
9 | The Real World Pact repository demonstrates these concepts through several real-world projects building from a simple faucet contract to a full-featured lending app with formal verification and an accompanying whitepaper.
10 |
--------------------------------------------------------------------------------
/03-charkha-lending/guide/README.md:
--------------------------------------------------------------------------------
1 | # Charkha Development Guide
2 |
3 | Welcome to the Charkha development guide! This guide describes how to translate the [Charkha white paper](../Charkha-Protocol-Whitepaper.pdf) into a set of smart contracts. Along the way we'll explore related DeFi topics such as price oracles, bridges, and implementing fungible tokens.
4 |
5 | In short, the white paper specifies the protocol, the smart contracts implement the protocol, and this development guide describes how we moved from the specification to the implementation.
6 |
7 | ## Table of Contents
8 |
9 | 1. [Price Oracles](./01-Price-Oracles.md)
10 | 2. [Fungible Tokens](./02-Fungible-Tokens.md)
11 | 3. [Governance and CHRK](./03-Governance-CHRK-Rewards.md)
12 | 4. [The Charkha Controller](./04-Charkha-Controller.md)
13 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/local/user-limits.yaml:
--------------------------------------------------------------------------------
1 | # This request asks the Goliath faucet contract for details on the per-request
2 | # and per-account limits set for the user account.
3 | #
4 | # You can execute this request:
5 | # faucet-request --local user-account-details
6 | #
7 | # This request will only succeed if the user account exists (ie. has received
8 | # funds). To create this account, deploy the faucet smart contract and then
9 | # request funds for the user account:
10 | #
11 | # faucet-deploy
12 | # faucet-request --send request-funds --signers goliath-faucet
13 | #
14 | # Then you can run this request to see account limits.
15 |
16 | code: (free.goliath-faucet.get-limits "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0")
17 | publicMeta:
18 | chainId: "0"
19 | gasLimit: 600
20 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 |
4 | import { createBrowserRouter, RouterProvider, Navigate } from "react-router-dom";
5 | import Governance from "./routes/governance";
6 | import Markets from "./routes/markets";
7 | import Root from "./routes/root";
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: "/",
12 | element: ,
13 | children: [
14 | {
15 | index: true,
16 | element: ,
17 | },
18 | {
19 | path: "/markets",
20 | element: ,
21 | },
22 | {
23 | path: "/governance",
24 | element: ,
25 | },
26 | ],
27 | },
28 | ]);
29 |
30 | ReactDOM.createRoot(document.getElementById("root")!).render();
31 |
--------------------------------------------------------------------------------
/theme/components/Spinner.tsx:
--------------------------------------------------------------------------------
1 | import { keyframes, styled } from "../styled.config";
2 |
3 | const spin = keyframes({
4 | "0%": {
5 | transform: "rotate(0deg)",
6 | },
7 | "100%": {
8 | transform: "rotate(360deg)",
9 | },
10 | });
11 |
12 | export const Spinner = styled("span", {
13 | display: "inline-block",
14 | borderColor: "$crimson9",
15 | borderStyle: "solid",
16 | borderRadius: "99999px",
17 | borderWidth: "2px",
18 | borderBottomColor: "transparent",
19 | borderLeftColor: "transparent",
20 | animation: `${spin} 0.45s linear infinite`,
21 |
22 | variants: {
23 | size: {
24 | small: {
25 | height: "$3",
26 | width: "$3",
27 | },
28 | medium: {
29 | height: "$5",
30 | width: "$5",
31 | },
32 | },
33 | },
34 |
35 | defaultVariants: {
36 | size: "small",
37 | },
38 | });
39 |
--------------------------------------------------------------------------------
/02-goliath-wallet/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@real-world-pact/goliath",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build"
8 | },
9 | "dependencies": {
10 | "@radix-ui/react-icons": "^1.1.1",
11 | "@real-world-pact/theme": "workspace:*",
12 | "@real-world-pact/utils": "workspace:*",
13 | "formik": "^2.2.9",
14 | "pact-lang-api": "^4.3.5",
15 | "react": "^18.0.0",
16 | "react-dom": "^18.0.0"
17 | },
18 | "devDependencies": {
19 | "@esbuild-plugins/node-globals-polyfill": "^0.1.1",
20 | "@esbuild-plugins/node-modules-polyfill": "^0.1.4",
21 | "@types/react": "^18.0.0",
22 | "@types/react-dom": "^18.0.0",
23 | "@vitejs/plugin-react": "^1.3.0",
24 | "buffer": "^6.0.3",
25 | "esbuild": "^0.15.1",
26 | "typescript": "^4.6.3",
27 | "vite": "^2.9.9"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@real-world-pact/charkha",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build"
8 | },
9 | "dependencies": {
10 | "@real-world-pact/theme": "workspace:*",
11 | "@real-world-pact/utils": "workspace:*",
12 | "axios": "^1.3.2",
13 | "formik": "^2.2.9",
14 | "react": "^18.0.0",
15 | "react-dom": "^18.0.0",
16 | "react-router-dom": "^6.8.1",
17 | "zustand": "^4.3.3"
18 | },
19 | "devDependencies": {
20 | "@esbuild-plugins/node-globals-polyfill": "^0.1.1",
21 | "@esbuild-plugins/node-modules-polyfill": "^0.1.4",
22 | "@types/react": "^18.0.0",
23 | "@types/react-dom": "^18.0.0",
24 | "@vitejs/plugin-react": "^1.3.0",
25 | "buffer": "^6.0.3",
26 | "esbuild": "^0.15.1",
27 | "typescript": "^4.6.3",
28 | "vite": "^2.9.9"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/components/README.md:
--------------------------------------------------------------------------------
1 | # Components
2 |
3 | This directory contains a few React components we're using to build the wallet UI. There is one component for each major section:
4 |
5 | - `AccountOverview.tsx` displays the overall account balance and per-chain balances.
6 | - `AdminModal.tsx` displays the admin modal for changing per-request and per-account limits via the faucet smart contract.
7 | - `RequestFundsModal.tsx` displays the modal for requesting funds from the faucet smart contract.
8 | - `ReturnFundsModal.tsx` displays the modal for returning funds to the faucet smart contract.
9 | - `Transactions.tsx` displays the list of transactions section, including the "transaction details" modal.
10 |
11 | All of these components are standard React, and so they're lightly commented. Feel free to read them to see how we render based on Pact request statuses and sometimes make new requests of our own, but for the most part these are application-specific.
12 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | /* Support use of Node builtins in the browser, ie. Buffer */
5 | import NodeGlobalsPolyfillPlugin from "@esbuild-plugins/node-globals-polyfill";
6 | import rollupNodePolyFill from "rollup-plugin-node-polyfills";
7 |
8 | // https://vitejs.dev/config/
9 | export default defineConfig({
10 | plugins: [react()],
11 | optimizeDeps: {
12 | esbuildOptions: {
13 | define: {
14 | global: "globalThis",
15 | },
16 | plugins: [NodeGlobalsPolyfillPlugin({ buffer: true })],
17 | },
18 | },
19 | server: {
20 | proxy: {
21 | "/proxy": {
22 | target: "https://pro-api.coinmarketcap.com/",
23 | changeOrigin: true,
24 | rewrite: (path) => path.replace(/^\/proxy/, ""),
25 | },
26 | },
27 | },
28 | build: {
29 | rollupOptions: {
30 | plugins: [rollupNodePolyFill()],
31 | },
32 | },
33 | });
34 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/cwKDA.ts:
--------------------------------------------------------------------------------
1 | /*
2 | This module supplies requests for the 'free.cwKDA' Pact module.
3 | */
4 |
5 | import { LocalRequest, SendRequest } from "@real-world-pact/utils/pact-request";
6 | import { charkhaKeyPair, sender02Address, sender02KeyPair } from "../constants";
7 |
8 | export const init = (ref: string): SendRequest => ({
9 | code: {
10 | cmd: "free.cwKDA.init",
11 | args: [{ ref }],
12 | },
13 | sender: sender02Address,
14 | gasLimit: 500,
15 | signers: [
16 | {
17 | ...sender02KeyPair,
18 | clist: [{ name: "coin.GAS", args: [] }],
19 | },
20 | {
21 | ...charkhaKeyPair,
22 | clist: [{ name: "free.cwKDA.ADMIN", args: [] }],
23 | },
24 | ],
25 | transformResponse: (response) => response as string,
26 | });
27 |
28 | export const isInitialized = (): LocalRequest => ({
29 | code: { cmd: "free.cwKDA.is-initialized", args: [] },
30 | transformResponse: (response) => response as boolean,
31 | });
32 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/cwCHRK.ts:
--------------------------------------------------------------------------------
1 | /*
2 | This module supplies requests for the 'free.cwCHRK' Pact module.
3 | */
4 |
5 | import { LocalRequest, SendRequest } from "@real-world-pact/utils/pact-request";
6 | import { charkhaKeyPair, sender02Address, sender02KeyPair } from "../constants";
7 |
8 | export const init = (ref: string): SendRequest => ({
9 | code: {
10 | cmd: "free.cwCHRK.init",
11 | args: [{ ref }],
12 | },
13 | sender: sender02Address,
14 | gasLimit: 500,
15 | signers: [
16 | {
17 | ...sender02KeyPair,
18 | clist: [{ name: "coin.GAS", args: [] }],
19 | },
20 | {
21 | ...charkhaKeyPair,
22 | clist: [{ name: "free.cwCHRK.ADMIN", args: [] }],
23 | },
24 | ],
25 | transformResponse: (response) => response as string,
26 | });
27 |
28 | export const isInitialized = (): LocalRequest => ({
29 | code: { cmd: "free.cwCHRK.is-initialized", args: [] },
30 | transformResponse: (response) => response as boolean,
31 | });
32 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/cwKETH.ts:
--------------------------------------------------------------------------------
1 | /*
2 | This module supplies requests for the 'free.cwKETH' Pact module.
3 | */
4 |
5 | import { LocalRequest, SendRequest } from "@real-world-pact/utils/pact-request";
6 | import { charkhaKeyPair, sender02Address, sender02KeyPair } from "../constants";
7 |
8 | export const init = (ref: string): SendRequest => ({
9 | code: {
10 | cmd: "free.cwKETH.init",
11 | args: [{ ref }],
12 | },
13 | sender: sender02Address,
14 | gasLimit: 500,
15 | signers: [
16 | {
17 | ...sender02KeyPair,
18 | clist: [{ name: "coin.GAS", args: [] }],
19 | },
20 | {
21 | ...charkhaKeyPair,
22 | clist: [{ name: "free.cwKETH.ADMIN", args: [] }],
23 | },
24 | ],
25 | transformResponse: (response) => response as string,
26 | });
27 |
28 | export const isInitialized = (): LocalRequest => ({
29 | code: { cmd: "free.cwKETH.is-initialized", args: [] },
30 | transformResponse: (response) => response as boolean,
31 | });
32 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:nixos/nixpkgs/release-22.11";
4 |
5 | flake-utils.url = "github:numtide/flake-utils";
6 |
7 | flake-compat = {
8 | url = "github:edolstra/flake-compat";
9 | flake = false;
10 | };
11 |
12 | devshell = {
13 | url = "github:numtide/devshell";
14 | inputs.nixpkgs.follows = "nixpkgs";
15 | };
16 |
17 | pact-nix = {
18 | url = "github:thomashoneyman/pact-nix";
19 | inputs.nixpkgs.follows = "nixpkgs";
20 | };
21 | };
22 |
23 | outputs = {
24 | self,
25 | nixpkgs,
26 | pact-nix,
27 | flake-utils,
28 | devshell,
29 | ...
30 | }:
31 | flake-utils.lib.eachDefaultSystem (system: let
32 | pactOverlay = _: _: {
33 | pact = pact-nix.packages.${system}.pact;
34 | };
35 |
36 | pkgs = import nixpkgs {
37 | inherit system;
38 | overlays = [pactOverlay devshell.overlays.default];
39 | };
40 | in {
41 | devShell = pkgs.devshell.mkShell {
42 | imports = [(pkgs.devshell.importTOML ./devshell.toml)];
43 | };
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/keth.ts:
--------------------------------------------------------------------------------
1 | /*
2 | This module supplies requests for the 'free.KETH' Pact module.
3 | */
4 |
5 | import Pact from "pact-lang-api";
6 | import { SendRequest } from "@real-world-pact/utils/pact-request";
7 | import { charkhaKeyPair } from "../constants";
8 |
9 | export interface MintArgs {
10 | sender: string;
11 | senderKeys: Pact.KeyPair;
12 | receiver: string;
13 | receiverGuard: Pact.KeySet;
14 | amount: Pact.PactDecimal;
15 | }
16 |
17 | export const mint = (args: MintArgs): SendRequest => ({
18 | code: {
19 | cmd: "free.KETH.mint",
20 | args: [args.receiver, { cmd: "read-keyset", args: ["receiver-guard"] }, args.amount],
21 | },
22 | data: { "receiver-guard": args.receiverGuard },
23 | sender: args.sender,
24 | gasLimit: 500,
25 | signers: [
26 | {
27 | ...args.senderKeys,
28 | clist: [{ name: "coin.GAS", args: [] }],
29 | },
30 | {
31 | ...charkhaKeyPair,
32 | clist: [{ name: "free.KETH.MINT", args: [args.receiver, args.amount] }],
33 | },
34 | ],
35 | transformResponse: (response) => response as string,
36 | });
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Thomas Honeyman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/routes/root.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { Box, Flex } from "@real-world-pact/theme/components/Container";
4 | import { GlobalStyles } from "@real-world-pact/theme/components/GlobalStyles";
5 | import { CharkhaLogo, Navbar } from "@real-world-pact/theme/components/Navbar";
6 | import { RouteLink } from "../components/RouteLink";
7 | import { Outlet } from "react-router-dom";
8 | import { AccountModal } from "../components/AccountModal";
9 | import Loader from "./loader";
10 |
11 | const Root = () => {
12 | const CenterNav = () => (
13 |
14 |
15 | Markets
16 |
17 | Governance
18 |
19 | );
20 |
21 | return (
22 |
23 |
24 | } center={}>
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default Root;
38 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/local/user-details.yaml:
--------------------------------------------------------------------------------
1 | # Accounts on Chainweb are generally referred to using a k: prefix and then
2 | # their public key. This is preferred to vanity accounts like 'goliath-faucet'.
3 | # Below, we're looking up the account associated with the public key from the
4 | # test-user.yaml keys. This is the account that requests funds in our tests.
5 | #
6 | # The response will contain, among other things, the current balance associated
7 | # with the account. Therefore you can use this request to verify that requesting
8 | # funds to this account succeeded and the correct amount arrived!
9 | #
10 | # You can execute this request:
11 | # faucet-request --local user-account-details
12 | #
13 | # This request will only succeed if the user account exists (ie. has received
14 | # funds). To create this account, deploy the faucet smart contract and then
15 | # request funds for the user account:
16 | # faucet-deploy (or ./run-deploy-contract.js if not using Nix)
17 | # faucet-request --send request-funds --signers goliath-faucet
18 | #
19 | # Then you can look up details on the user account.
20 |
21 | code: (coin.details "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0")
22 | publicMeta:
23 | chainId: "0"
24 | gasLimit: 600
25 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/coinmarketcap-api.ts:
--------------------------------------------------------------------------------
1 | /* This module exposes requests to the CoinMarketCap API necessary to get quotes
2 | for our various currencies. For the full details on how we arrived at these
3 | requests, please see the Charkha development guide under the Price Oracle section.
4 | */
5 |
6 | import axios from "axios";
7 |
8 | const cmcAPI = axios.create({
9 | // We have to proxy requests to the CoinMarketCap API in our development
10 | // setup; see the vite.config.js file for the configuration.
11 | baseURL: "/proxy",
12 | headers: { "X-CMC_PRO_API_KEY": import.meta.env.VITE_CMC_API_KEY },
13 | });
14 |
15 | const KDA = 5647;
16 | const KETH = 1027;
17 | const CHRK = 5692;
18 |
19 | export interface Prices {
20 | KDA: number;
21 | KETH: number;
22 | CHRK: number;
23 | }
24 |
25 | export const getPrices = async (): Promise => {
26 | try {
27 | const { data } = await cmcAPI.get(`/v2/cryptocurrency/quotes/latest?id=${KDA},${KETH},${CHRK}`);
28 | const result = {
29 | KDA: data.data[KDA].quote.USD.price as number,
30 | KETH: data.data[KETH].quote.USD.price as number,
31 | CHRK: data.data[CHRK].quote.USD.price as number,
32 | };
33 | return result;
34 | } catch (error) {
35 | return error as string;
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/pact-api-utils/README.md:
--------------------------------------------------------------------------------
1 | # Pact API Utils
2 |
3 | The `pact-api-utils` directory contains helper modules for working with the [`pact-lang-api`](https://github.com/kadena-io/pact-lang-api) library to make requests to the Pact endpoint on your local Chainweb node. There are four files:
4 |
5 | - `types/pact-lang-api.d.ts` contains TypeScript type definitions for the parts of the `pact-lang-api` library that we're using. The underlying library is written in JavaScript, so this gives us types to work with.
6 | - `src/pact-code.ts` has helpers for writing Pact code in JavaScript and then formatting it into a string appropriate to send to a Chainweb node for execution. We'll use this whenever we write Pact code.
7 | - `src/pact-request.ts` has helpers for constructing requests to send to Chainweb. The requests we'll build are similar to the request files we used in Project 1, except they're fully configurable. Even better, we have helper functions to execute the request and record its status — either pending, or an error result, or a success result — as well as parse the result into a TypeScript type.
8 | - `src/pact-request-hooks.ts` is a layer on top of `pact-request` that integrates the requests with React Hooks so it's easy to make a request and track its status (pending, error, or success) in state. We'll use these hooks in our UI.
9 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/interfaces/fungible-v2.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This file implements functions and types for the 'fungible-v2' fungible token
4 | interface, which all KIP-0005 compliant tokens must implement. Accordingly,
5 | these bindings are usable with any fungible-v2-based contract.
6 |
7 | */
8 |
9 | import Pact from "pact-lang-api";
10 | import {
11 | coercePactNumber,
12 | coercePactObject,
13 | coercePactValue,
14 | LocalRequest,
15 | } from "@real-world-pact/utils/pact-request";
16 |
17 | // The (account-details) schema
18 | export interface AccountDetails {
19 | account: string;
20 | balance: number;
21 | guard: Pact.KeySet;
22 | }
23 |
24 | export const details = (module: string, account: string): LocalRequest => ({
25 | code: { cmd: `${module}.details`, args: [account] },
26 | transformResponse: (response) => {
27 | const object = coercePactObject(response);
28 | return {
29 | account: object["account"] as string,
30 | balance: coercePactNumber(object["balance"]),
31 | guard: coercePactValue(object["guard"]),
32 | };
33 | },
34 | });
35 |
36 | export const getBalance = (module: string, account: string): LocalRequest => ({
37 | code: { cmd: `${module}.get-balance`, args: [account] },
38 | transformResponse: coercePactNumber,
39 | });
40 |
--------------------------------------------------------------------------------
/theme/components/Icon.tsx:
--------------------------------------------------------------------------------
1 | import { CheckCircledIcon, CrossCircledIcon, TimerIcon } from "@radix-ui/react-icons";
2 | import { styled } from "@stitches/react";
3 |
4 | export const SuccessIcon = styled(CheckCircledIcon, {
5 | color: "$green9",
6 |
7 | variants: {
8 | size: {
9 | small: {
10 | height: "$3",
11 | width: "$3",
12 | },
13 | medium: {
14 | height: "$5",
15 | width: "$5",
16 | },
17 | },
18 | },
19 |
20 | defaultVariants: {
21 | size: "small",
22 | },
23 | });
24 |
25 | export const ErrorIcon = styled(CrossCircledIcon, {
26 | color: "$crimson11",
27 |
28 | variants: {
29 | size: {
30 | small: {
31 | height: "$3",
32 | width: "$3",
33 | },
34 | medium: {
35 | height: "$5",
36 | width: "$5",
37 | },
38 | },
39 | },
40 |
41 | defaultVariants: {
42 | size: "small",
43 | },
44 | });
45 |
46 | export const NotStartedIcon = styled(TimerIcon, {
47 | color: "$mauve9",
48 |
49 | variants: {
50 | size: {
51 | small: {
52 | height: "$3",
53 | width: "$3",
54 | },
55 | medium: {
56 | height: "$5",
57 | width: "$5",
58 | },
59 | },
60 | },
61 |
62 | defaultVariants: {
63 | size: "small",
64 | },
65 | });
66 |
--------------------------------------------------------------------------------
/devnet-backup/README.md:
--------------------------------------------------------------------------------
1 | # Devnet Database Backup
2 |
3 | The devnet simulation blockchain begins with no blockchain history and support for only early versions of Pact. We, however, want to use features of recent Pact versions in our code. These features will fail until we reach a late-enough block height on devnet when those features were enabled.
4 |
5 | Fortunately, devnet stores its state in the denvent/db/0 directory. As described in the Chainweb documentation, [we can snapshot this directory](https://github.com/kadena-io/devnet#database-bind-mount) so that devnet begins from a sufficient block height.
6 |
7 | This directory contains an appropriate snapshot. When you first run `devnet-start` in the development shell your devnet checkout will be initialized with the proper snapshot; subsequent runs will reuse the existing directory. When you run `devnet-clean` the devnet directory will reset to the snapshot.
8 |
9 | To see the current block height of the entire chain, run `devnet-start` and then:
10 |
11 | ```sh
12 | curl -s http://localhost:8080/chainweb/0.0/development/cut | jq '.hashes."'0'".height'
13 | ```
14 |
15 | At the time of writing, the most recent Pact versions are supported via Chainweb 2.17, which requires a block height of 470:
16 |
17 | https://github.com/kadena-io/chainweb-node/blob/323bce436dfbadbf1863580a25d7afddcd76005f/src/Chainweb/Version.hs#L1022
18 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/contracts/coin-v5.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This file implements functions and types for the 'coin-v5' fundamental contract
4 | deployed by Kadena onto Chainweb. The coin contract provides an API for creating
5 | accounts and transferring funds.
6 |
7 | This file only includes functions and types from the coin module that we need
8 | for our project, and isn't a full reproduction of the contract.
9 |
10 | Contract source:
11 | https://github.com/kadena-io/chainweb-node/blob/master/pact/coin-contract/v5/coin-v5.pact
12 |
13 | */
14 |
15 | import Pact from "pact-lang-api";
16 | import { coercePactValue, LocalRequest } from "@real-world-pact/utils/pact-request";
17 |
18 | // The address to look up details about.
19 | export interface DetailsArgs {
20 | address: string;
21 | }
22 |
23 | // The type of a successful call to the (coin.details) function.
24 | export interface DetailsResponse {
25 | account: string;
26 | balance: number;
27 | guard: Pact.KeySet;
28 | }
29 |
30 | // Look up the details for the given address using (coin.details)
31 | // https://github.com/kadena-io/chainweb-node/blob/56e99ae421d2269a657e3bb3780c6d707e5149a0/pact/coin-contract/v5/coin-v5.pact#L304
32 | export const details = (args: DetailsArgs): LocalRequest => ({
33 | code: { cmd: "coin.details", args: [args.address] },
34 | transformResponse: coercePactValue,
35 | });
36 |
--------------------------------------------------------------------------------
/pact-repl-utils/init-contracts.repl:
--------------------------------------------------------------------------------
1 | ; Our Pact modules sometimes rely on other modules that have been deployed to
2 | ; Chainweb. For example, the "coin" contract manages the KDA token, and we can
3 | ; use it to transfer KDA, create accounts, and more. This contract exists on
4 | ; Chainweb, but it doesn't exist in the REPL.
5 | ;
6 | ; If we want to be able to test contracts that depend on contracts deployed to
7 | ; Chainweb, then we need to load those dependencies into the REPL, too. To
8 | ; "deploy" a contract in the repl, use the (load) function:
9 | ; https://pact-language.readthedocs.io/en/latest/pact-functions.html#load
10 | ;
11 | ; The (load) function takes a filepath. For that reason, we've saved copies of
12 | ; several foundational contracts we might rely on in the 'contracts' directory.
13 | ; We can load these contracts into the REPL one-by-one.
14 | (begin-tx)
15 | (load "./contracts/fungible-v2-modified.pact")
16 | (load "./contracts/fungible-xchain-v1.pact")
17 | (load "./contracts/coin-v5.pact")
18 |
19 | ; If a module contains tables and they are not initialized as part of the
20 | ; smart contract, then they will need to be created on deployment. The 'coin'
21 | ; contract is one example.
22 | ;
23 | ; The coin-v5 contract defines two tables (you can find them by looking for
24 | ; calls to the (deftable) function in the contract).
25 | (create-table coin.coin-table)
26 | (create-table coin.allocation-table)
27 | (commit-tx)
28 | (print "Loaded essential Kadena contracts.")
29 |
--------------------------------------------------------------------------------
/pact-repl-utils/contracts/fungible-xchain-v1.pact:
--------------------------------------------------------------------------------
1 | ; The coin-v5 contract implements two interfaces: fungible-v2 and
2 | ; fungible-xchain-v1. To verify the contract implements these interfaces
3 | ; correctly, we need to be able to load their contents. This is a copy of the
4 | ; fungible-xchain-v1 interface deployed to Chainweb.
5 | ;
6 | ; https://github.com/kadena-io/chainweb-node/blob/master/pact/coin-contract/v4/fungible-xchain-v1.pact
7 | (interface fungible-xchain-v1
8 |
9 | " This interface offers a standard capability for cross-chain \
10 | \ transfers and associated events. "
11 |
12 | (defcap TRANSFER_XCHAIN:bool
13 | ( sender:string
14 | receiver:string
15 | amount:decimal
16 | target-chain:string
17 | )
18 | @doc " Managed capability sealing AMOUNT for transfer \
19 | \ from SENDER to RECEIVER on TARGET-CHAIN. Permits \
20 | \ any number of cross-chain transfers up to AMOUNT."
21 |
22 | @managed amount TRANSFER_XCHAIN-mgr
23 | )
24 |
25 | (defun TRANSFER_XCHAIN-mgr:decimal
26 | ( managed:decimal
27 | requested:decimal
28 | )
29 | @doc " Allows TRANSFER-XCHAIN AMOUNT to be less than or \
30 | \ equal managed quantity as a one-shot, returning 0.0."
31 | )
32 |
33 | (defcap TRANSFER_XCHAIN_RECD:bool
34 | ( sender:string
35 | receiver:string
36 | amount:decimal
37 | source-chain:string
38 | )
39 | @doc "Event emitted on receipt of cross-chain transfer."
40 | @event
41 | )
42 | )
43 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/send/request-funds-over-limit.yaml:
--------------------------------------------------------------------------------
1 | # This transaction uses the 'request-funds' function from the faucet to request
2 | # more than the default 20.0 KDA limit. To successfully send this request, you
3 | # must first use the 'set-user-request-limit' request file to update the limit.
4 | #
5 | # To execute this request:
6 | # faucet-request --send request-funds-over-limit --signers goliath-faucet
7 | #
8 | # This request is uncommented. For details on (request-funds), please see the
9 | # companion file:
10 | # ./request/send/request-funds.yaml
11 |
12 | networkId: "development"
13 | type: exec
14 | code: (free.goliath-faucet.request-funds (read-msg "receiver") (read-keyset "receiver-guard") (read-decimal "amount"))
15 |
16 | data:
17 | receiver: "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0"
18 | amount: 50.0
19 | receiver-guard:
20 | keys:
21 | - "eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0"
22 | pred: "keys-all"
23 |
24 | signers:
25 | - public: 550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf
26 | caps:
27 | - name: "coin.TRANSFER"
28 | args:
29 | [
30 | "goliath-faucet",
31 | "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0",
32 | 50.0,
33 | ]
34 |
35 | - name: "coin.GAS"
36 | args: []
37 |
38 | publicMeta:
39 | chainId: "0"
40 | sender: "goliath-faucet"
41 | gasLimit: 900
42 | gasPrice: 0.0000001
43 | ttl: 600
44 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/constants.tsx:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | In Charkha you are automatically given the 'sender00' account, which has access
4 | to a ton of KDA on the devnet network.
5 |
6 | In the real world you should never commit private keys.
7 |
8 | */
9 |
10 | import { KeyPair, KeySet } from "pact-lang-api";
11 |
12 | export const charkhaAddress: string = "charkha";
13 |
14 | // The key pair for the Charkha admin account.
15 | export const charkhaKeyPair: KeyPair = {
16 | publicKey: "550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf",
17 | secretKey: "2a9d9112e96e8a299091e067879c50d1cbefbd9c7fff78e2623d7e3f0cd2bf45",
18 | };
19 |
20 | export const charkhaKeyset: KeySet = {
21 | keys: [charkhaKeyPair.publicKey],
22 | pred: "keys-all",
23 | };
24 |
25 | export const sender01Address: string = "sender01";
26 |
27 | export const sender01KeyPair: KeyPair = {
28 | publicKey: "6be2f485a7af75fedb4b7f153a903f7e6000ca4aa501179c91a2450b777bd2a7",
29 | secretKey: "2beae45b29e850e6b1882ae245b0bab7d0689ebdd0cd777d4314d24d7024b4f7",
30 | };
31 |
32 | export const sender01Keyset: KeySet = {
33 | keys: [sender01KeyPair.publicKey],
34 | pred: "keys-all",
35 | };
36 | export const sender02Address: string = "sender02";
37 |
38 | export const sender02KeyPair: KeyPair = {
39 | publicKey: "3a9dd532d73dace195dbb64d1dba6572fb783d0fdd324685e32fbda2f89f99a6",
40 | secretKey: "9b54e924f7acdb03ad4e471308f9a512dac26a50398b41cab8bfe7a496804dbd",
41 | };
42 |
43 | export const sender02Keyset: KeySet = {
44 | keys: [sender02KeyPair.publicKey],
45 | pred: "keys-all",
46 | };
47 |
--------------------------------------------------------------------------------
/01-faucet-contract/internal/localhost.js:
--------------------------------------------------------------------------------
1 | /* INTERNAL
2 |
3 | The code below is for making an HTTP POST request and has little to do with Pact
4 | or Chainweb directly. Read it if you'd like, but you can safely ignore it, too.
5 | */
6 | const http = require("http");
7 |
8 | exports.post = ({ path, body }) => {
9 | return new Promise((resolve, reject) => {
10 | const options = {
11 | host: "localhost",
12 | port: "8080",
13 | method: "POST",
14 | path,
15 | headers: {
16 | "Content-Type": "application/json",
17 | },
18 | };
19 |
20 | const req = http.request(options, (res) => {
21 | const chunks = [];
22 | res.on("data", (data) => {
23 | chunks.push(data);
24 | });
25 | res.on("end", () => {
26 | let result = Buffer.concat(chunks).toString();
27 | if (result.startsWith("")) {
28 | reject(
29 | "Received connection timeout. You may need to restart devnet or try this request again."
30 | );
31 | } else {
32 | if (result.includes("Validation failed for hash")) {
33 | console.log(result);
34 | reject(result);
35 | } else {
36 | try {
37 | const parsed = JSON.parse(result);
38 | resolve(parsed);
39 | } catch (err) {
40 | console.log(`Failed to parse JSON response: ${err}`);
41 | reject(err);
42 | }
43 | }
44 | }
45 | });
46 | });
47 | req.on("error", reject);
48 | if (body) {
49 | req.write(body);
50 | }
51 | req.end();
52 | });
53 | };
54 |
--------------------------------------------------------------------------------
/pact-repl-utils/README.md:
--------------------------------------------------------------------------------
1 | # Pact REPL Utilities
2 |
3 | The Pact REPL is a useful tool for testing smart contract code. A REPL session contains Pact code and, when run, mimics how a blockchain like Chainweb would execute that code. The REPL also provides several REPL-only functions that can be used to measure gas consumption, commit and roll back transactions, throw exceptions, and more.
4 |
5 | However, the REPL is also minimal. It does not have any of the namespaces, contracts, guards, or other data that exists on Chainweb. All significant Pact code bases will require that the REPL is populated with this data. For example, our faucet contract relies on the 'coin-v5' Kadena contract, which defines operations for the KDA token. In order to refer to that dependency we must "deploy" it to our REPL environment.
6 |
7 | This directory stores a number of helper files for initializing a REPL environment that mimics what we expect Chainweb to contain at the time we deploy our actual contracts. Each of our test REPL files will begin with this line:
8 |
9 | ```clojure
10 | (load "../pact-repl-utils/init.repl")
11 | ```
12 |
13 | This will execute the `init.repl` session, which constructs our REPL environment. See each `init-*` file for a description of what is being initialized and why. After initialization, we will have:
14 |
15 | - Basic namespaces like `free` and `user`
16 | - Several foundational contracts, such as the `coin` contract that governs the KDA token
17 | - Three accounts (`sender00`, `sender01`, and `sender01`), along with their respective keysets (`sender00-keyset`, etc.), each funded with 1000 KDA. Note: these accounts are also present on devnet, so you can write code for both the REPL and devnet test environments with these accounts.
18 |
--------------------------------------------------------------------------------
/pact-repl-utils/init-namespaces.repl:
--------------------------------------------------------------------------------
1 | ; All Pact modules must exist within a namespace on Chainweb, except for basic
2 | ; contracts provided by Kadena. There are two namespaces that anyone can use on
3 | ; Chainweb: the "free" namespace and the "user" namespace. Other namespaces can
4 | ; be created with Kadena's sign-off, but for our contracts we'll assume we don't
5 | ; have the ability to create namespaces.
6 | ;
7 | ; The "free" and "user" namespaces exist on Chainweb, but they don't exist in
8 | ; the REPL environment. If we want to load Pact modules that we have written in
9 | ; the "free" namespace into the REPL, then we need to ensure that namespace
10 | ; exists in the REPL. We can do that with (define-namespace):
11 | ; https://pact-language.readthedocs.io/en/stable/pact-functions.html#define-namespace
12 | ;
13 | ; Defining a namespace requires that we provide two guards. The first guard is
14 | ; the user who will manage the namespace. It must be satisfied in order to
15 | ; deploy code to the given namespace. The second guard is the blockchain admin,
16 | ; and it must be satisfied in order to create the new namespace.
17 | ;
18 | ; In practice, you only need to care about any of this if the Kadena team grants
19 | ; you a namespace of your own. In the REPL environment we will simply create
20 | ; these namespaces using mock guards.
21 | (begin-tx)
22 | (module test-ns GOVERNANCE
23 | (defcap GOVERNANCE () true)
24 | (defconst NAMESPACE_GUARD (create-user-guard (succeed)))
25 | (defun succeed () true)
26 | )
27 |
28 | (define-namespace "free" test-ns.NAMESPACE_GUARD test-ns.NAMESPACE_GUARD)
29 | (define-namespace "user" test-ns.NAMESPACE_GUARD test-ns.NAMESPACE_GUARD)
30 | (commit-tx)
31 | (print "Loaded 'free' and 'user' namespaces.")
32 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/interfaces/market.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This file implements functions and types for the 'market-interface' interface
4 | for market tokens such as cwKDA, cwKETH, and cwCHRK.
5 |
6 | It only includes types and functions that we actually use, rather than the
7 | entire module.
8 |
9 | */
10 |
11 | import {
12 | coercePactNumber,
13 | coercePactObject,
14 | LocalRequest,
15 | } from "@real-world-pact/utils/pact-request";
16 | import { MarketToken } from "../controller";
17 |
18 | // The address to look up details about.
19 | export interface Participant {
20 | lastUpdated: number;
21 | lastRateIndex: number;
22 | balance: number;
23 | borrows: number;
24 | }
25 |
26 | export interface GetParticipantArgs {
27 | market: MarketToken;
28 | account: string;
29 | }
30 |
31 | export const getParticipant = (args: GetParticipantArgs): LocalRequest => ({
32 | code: { cmd: `free.${args.market}.get-participant`, args: [args.account] },
33 | transformResponse: (response) => {
34 | const object = coercePactObject(response);
35 | return {
36 | lastUpdated: coercePactNumber(object["last-updated"]),
37 | lastRateIndex: coercePactNumber(object["last-rate-index"]),
38 | balance: coercePactNumber(object["balance"]),
39 | borrows: coercePactNumber(object["borrows"]),
40 | };
41 | },
42 | });
43 |
44 | export const getBorrow = (args: GetParticipantArgs): LocalRequest => ({
45 | code: { cmd: `free.${args.market}.get-borrow`, args: [args.account] },
46 | transformResponse: coercePactNumber,
47 | });
48 |
49 | export const getSupply = (args: GetParticipantArgs): LocalRequest => ({
50 | code: { cmd: `free.${args.market}.get-supply`, args: [args.account] },
51 | transformResponse: coercePactNumber,
52 | });
53 |
--------------------------------------------------------------------------------
/theme/components/GlobalStyles.tsx:
--------------------------------------------------------------------------------
1 | import { globalCss } from "@stitches/react";
2 | import { ReactElement } from "react";
3 |
4 | const globalStyles = globalCss({
5 | "*, *:before, *:after": {
6 | boxSizing: "border-box",
7 | },
8 |
9 | "*": {
10 | margin: 0,
11 | },
12 |
13 | "html, body": {
14 | height: "100%",
15 | },
16 |
17 | body: {
18 | "-webkit-font-smoothing": "antialiased",
19 | backgroundColor: "$mauve1",
20 | fontFamily: "$sansSerif",
21 | fontSize: "$md",
22 | lineHeight: "$base",
23 | color: "$mauve12",
24 | },
25 |
26 | fieldset: {
27 | border: 0,
28 | padding: "0.01em 0 0 0",
29 | margin: 0,
30 | minWidth: 0,
31 | },
32 |
33 | "img, picture, video, canvas, svg": {
34 | display: "block",
35 | maxWidth: "100%",
36 | },
37 |
38 | "input, button, textarea, select": {
39 | font: "inherit",
40 | },
41 |
42 | "p, h1, h2, h3, h4, h5, h6": {
43 | overflowWrap: "break-word",
44 | },
45 |
46 | "h1, h2, h3, h4, h5, h6": {
47 | fontWeight: "bold",
48 | },
49 |
50 | input: {
51 | borderRadius: 4,
52 | padding: "$1 $2",
53 | fontSize: "$base",
54 | color: "$mauve12",
55 | height: 35,
56 | border: "1px solid $mauve11",
57 |
58 | "&:focus": {
59 | outline: "1px solid $crimson11",
60 | border: "1px solid $crimson11",
61 | },
62 | },
63 |
64 | h1: {
65 | fontSize: "$3xl",
66 | lineHeight: "$xs",
67 | },
68 |
69 | h2: {
70 | fontSize: "$2xl",
71 | lineHeight: "$xs",
72 | },
73 |
74 | h3: {
75 | fontSisez: "$xl",
76 | lineHeight: "$xs",
77 | },
78 | });
79 |
80 | interface GlobalProps {
81 | children: ReactElement;
82 | }
83 |
84 | export const GlobalStyles = ({ children }: GlobalProps) => {
85 | globalStyles();
86 | return children;
87 | };
88 |
--------------------------------------------------------------------------------
/03-charkha-lending/contracts/verify.repl:
--------------------------------------------------------------------------------
1 | ; This file formally verifies the Charkha lending protocol by verifying each
2 | ; contract in turn.
3 | (load "setup.repl")
4 | (env-data {})
5 | (env-sigs [])
6 |
7 | (print "\n----- FORMAL VERIFICATION -----\n")
8 | (print "Setting up dynamic references...")
9 |
10 | ; We need to use the (env-dynref) repl-only function to tell formal verification
11 | ; what contract to use when it encounters a modref.
12 | (env-dynref fungible-v2 coin)
13 | (env-dynref free.charkha-market-iface free.cwKDA)
14 | (env-dynref free.charkha-controller-iface free.charkha-controller)
15 |
16 | ; Strangely, verifying the KETH / CHRK contracts produces an internal Pact error,
17 | ; though it's possible to verify directly with 'pact tokens/keth.repl'.
18 | ; test.repl:1:0: singular: empty traversal CallStack (from HasCallStack): error, called at src/Control/Lens/Traversal.hs:700:46 in lens-5.1.1-6e50c72297cf6c966244f2898e4d136a18b9658082880c54bccabf8eb5d9ce58:Control.Lens.Traversal singular, called at src-tool/Pact/Analyze/Types/Eval.hs:711:5 in pact-4.4-inplace:Pact.Analyze.Types.Eval typedCell, called at src-tool/Pact/Analyze/Eval/Term.hs:333:21 in pact-4.4-inplace:Pact.Analyze.Eval.Term
19 | ; (verify "free.KETH")
20 | ; (print "Verified free.KETH")
21 |
22 | ; (verify "free.CHRK")
23 | ; (print "Verified free.CHRK")
24 |
25 | (verify "free.charkha-oracle")
26 | (print "Verified free.charkha-oracle")
27 |
28 |
29 | ; The tokens below currently have an infinite hang when verifying. This can be
30 | ; avoided by removing the (validate-borrowing-capacity) function from transfer.
31 | ; (verify "free.cwKDA")
32 | ; (verify "free.cwCHRK")
33 | ; (verify "free.cwKETH")
34 |
35 | (verify "free.charkha-governance")
36 | (print "Verified free.charkha-governance")
37 |
38 | (verify "free.charkha-controller")
39 | (print "Verified free.charkha-controller")
40 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/chrk.ts:
--------------------------------------------------------------------------------
1 | /*
2 | This module supplies requests for the 'free.CHRK' Pact module.
3 | */
4 |
5 | import { LocalRequest, PactAPI, SendRequest } from "@real-world-pact/utils/pact-request";
6 | import { KeyPair, KeySet } from "pact-lang-api";
7 | import { charkhaKeyPair, sender02Address, sender02KeyPair } from "../constants";
8 |
9 | export const init = (ref: string): SendRequest => ({
10 | code: {
11 | cmd: "free.CHRK.init",
12 | args: [{ ref }],
13 | },
14 | sender: sender02Address,
15 | gasLimit: 750,
16 | signers: [
17 | {
18 | ...sender02KeyPair,
19 | clist: [{ name: "coin.GAS", args: [] }],
20 | },
21 | {
22 | ...charkhaKeyPair,
23 | clist: [{ name: "free.CHRK.ADMIN", args: [] }],
24 | },
25 | ],
26 | transformResponse: (response) => response as string,
27 | });
28 |
29 | export const isInitialized = (): LocalRequest => ({
30 | code: { cmd: "free.CHRK.is-initialized", args: [] },
31 | transformResponse: (response) => response as boolean,
32 | });
33 |
34 | interface ClaimCreateArgs {
35 | account: string;
36 | accountKeys: KeyPair;
37 | accountGuard: KeySet;
38 | }
39 |
40 | // In this function we will both accrue and claim, for convenience.
41 | export const claimCreate = (args: ClaimCreateArgs): SendRequest => ({
42 | code: [
43 | {
44 | cmd: "free.CHRK.accrue",
45 | args: [],
46 | },
47 | {
48 | cmd: "free.CHRK.claim-create",
49 | args: [args.account, { cmd: "read-keyset", args: ["user-keyset"] }],
50 | },
51 | ],
52 | data: { "user-keyset": args.accountGuard },
53 | sender: args.account,
54 | gasLimit: 10000,
55 | signers: [
56 | {
57 | ...args.accountKeys,
58 | clist: [{ name: "coin.GAS", args: [] }],
59 | },
60 | ],
61 | transformResponse: (response) => response as string,
62 | });
63 |
--------------------------------------------------------------------------------
/pact-repl-utils/init-gasmodel.repl:
--------------------------------------------------------------------------------
1 | ; One of the most important concepts when building a dapp on a blockchain like
2 | ; Chainweb is gas: computations on chain cost gas, which is paid for using KDA.
3 | ; This cost is either paid by users of the dapp or on the user's behalf by the
4 | ; dapp itself. Either way, if you create a dapp, you need to be able to estimate
5 | ; the cost of running your contracts.
6 | ;
7 | ; The Pact REPL provides a handy way to measure gas consumption in a contract
8 | ; via the (env-gas*) family of functions.
9 | ; https://pact-language.readthedocs.io/en/latest/pact-functions.html#env-gas
10 | ;
11 | ; * (env-gasmodel) lets you specify how to store record of gas consumption,
12 | ; which you have to set up before taking any measurements.
13 | ; * (env-gaslog) lets you measure gas consumption over a specific block.
14 | ; * (env-gas) lets you read the current gas count if provided no arguments,
15 | ; and lets you manually set the gas count if provided an integer (such as 0
16 | ; to reset the counter to 0).
17 | ; * (env-gaslimit) lets you set a limit to the units of gas that can be
18 | ; consumed in the REPL session. It will throw an error if exceeded. The
19 | ; maximum gas that can be consumed in a single transaction is 150000. That
20 | ; can be useful limit to set when you want to measure a single .pact file
21 | ; and see how much gas it would cost to execute the entire thing in one
22 | ; transaction on Chainweb.
23 | ;
24 | ; Note that gas consumption depends in part on the size of the Pact code sent
25 | ; to Chainweb, whether you are using transaction data for the arguments or
26 | ; inlining the arguments into the Pact code, and so on. It's always a good idea
27 | ; to treat gas measurements as estimates and round them up by at least 1% when
28 | ; submitting requests to Chainweb.
29 | (env-gasmodel "table")
30 | (env-gaslimit 1000000)
31 | (print "Initialized gas model 'table'")
32 |
--------------------------------------------------------------------------------
/pact-repl-utils/init.repl:
--------------------------------------------------------------------------------
1 | ; The REPL environment allows us to execute Pact code and inspect the results.
2 | ; We can use it to simulate how our contract will behave once deployed to
3 | ; Chainweb. However: we aren't actually using Chainweb. That means there
4 | ; technically are no nodes, no transactions, no keysets, no existing contracts,
5 | ; and so on. Therefore we must set up our REPL environment so that it includes
6 | ; data that our contract would expect to exist.
7 | ;
8 | ; This REPL file initializes all the data we would expect to exist on Chainweb
9 | ; at the time we deploy our contracts. See each loaded REPL file for an
10 | ; explanation of why and how that data is being initialized.
11 | ;
12 | ; You can execute this file from the command line with:
13 | ; $ pact init.repl
14 | ;
15 | ; Alternately, to execute this file and remain in the REPL so you can interact
16 | ; with the running program, run `pact` in the terminal and then:
17 | ; pact> (load "init.repl")
18 |
19 | ; We initialize the "free" and "user" namespaces, the only two which we can use
20 | ; to deploy our contracts.
21 | (load "init-namespaces.repl")
22 |
23 | ; We inititialize foundational Kadena contracts we expect to be present on-chain
24 | ; such as the 'coin' contract.
25 | (load "init-contracts.repl")
26 |
27 | ; We create and fund three accounts (sender00, sender01, sender02). These aren't
28 | ; present on Chainweb, but they are present on our local simulation blockchain,
29 | ; devnet, so test code that refers to these accounts will work in both
30 | ; environments. We can use these accounts to create and fund other accounts, or
31 | ; as basic test accounts.
32 | ;
33 | ; This relies on the coin contract so it must be loaded after 'init-contracts'.
34 | (load "init-accounts.repl")
35 |
36 | ; The gas model allows us to track gas consumption during our REPL session. We
37 | ; load it last because we don't want our general initialization to count.
38 | (load "init-gasmodel.repl")
39 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/send/set-user-account-limit.yaml:
--------------------------------------------------------------------------------
1 | # This transaction can be used to update the per-account transfer limit enforced
2 | # in the faucet contract. This operation can only be taken by the Goliath faucet
3 | # account, so this transaction must be signed with those keys.
4 | #
5 | # To execute this request:
6 | # faucet-request --send set-user-account-limit --signers goliath-faucet
7 |
8 | networkId: "development"
9 | type: exec
10 |
11 | # We can use the 'set-account-limit' function to change the per-account limit
12 | # for a particular account.
13 | code: (free.goliath-faucet.set-account-limit (read-msg "receiver") (read-decimal "new-limit"))
14 |
15 | # Our transaction data supplies the data our code will read from. The receiver
16 | # should be the k: account for our test user account and new-limit should be the
17 | # desired amount of KDA to serve as the new account limit.
18 | #
19 | # For this test request we'll raise the limit from 100.0 KDA to 200.0 KDA per
20 | # account. Note that the contract disallows lowering the limit or setting it
21 | # to the same value as the old limit. Try sending this request twice and you'll
22 | # see it fail.
23 | data:
24 | receiver: "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0"
25 | new-limit: 200.0
26 |
27 | # Next, this function is guarded by the SET_LIMIT capability, which in turn
28 | # requires that the Goliath faucet keys signed the transaction.
29 | signers:
30 | - public: 550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf
31 | caps:
32 | - name: "free.goliath-faucet.SET_LIMIT"
33 | args: []
34 |
35 | - name: "coin.GAS"
36 | args: []
37 |
38 | publicMeta:
39 | chainId: "0"
40 | sender: "goliath-faucet"
41 | # In the faucet.repl file we calculated the gas consumption for a call to the
42 | # (set-account-limit) function. We've rounded that number up for our gas limit.
43 | gasLimit: 250
44 | gasPrice: 0.0000001
45 | ttl: 600
46 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/send/set-user-request-limit.yaml:
--------------------------------------------------------------------------------
1 | # This transaction can be used to update the per-request transfer limit enforced
2 | # in the faucet contract. This operation can only be taken by the Goliath faucet
3 | # account, so this transaction must be signed with those keys.
4 | #
5 | # To execute this request:
6 | # faucet-request --send set-user-request-limit --signers goliath-faucet
7 |
8 | networkId: "development"
9 | type: exec
10 |
11 | # We can use the 'set-request-limit' function to change the per-account limit
12 | # for a particular account.
13 | code: (free.goliath-faucet.set-request-limit (read-msg "receiver") (read-decimal "new-limit"))
14 |
15 | # Our transaction data supplies the data our code will read from. The receiver
16 | # should be the k: account for our test user account and new-limit should be the
17 | # desired amount of KDA to serve as the new account limit.
18 | #
19 | # For this test request we'll raise the limit from 20.0 KDA to 50.0 KDA per
20 | # account. Note that the contract disallows lowering the limit or setting it
21 | # to the same value as the old limit. Try sending this request twice and you'll
22 | # see it fail.
23 | data:
24 | receiver: "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0"
25 | new-limit: 50.0
26 |
27 | # Next, this function is guarded by the SET_LIMIT capability, which in turn
28 | # requires # that the Goliath faucet keys signed the transaction.
29 | signers:
30 | - public: 550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf
31 | caps:
32 | - name: "free.goliath-faucet.SET_LIMIT"
33 | args: []
34 |
35 | - name: "coin.GAS"
36 | args: []
37 |
38 | publicMeta:
39 | chainId: "0"
40 | sender: "goliath-faucet"
41 | # In the faucet.repl file we calculated the gas consumption for a call to the
42 | # (set-request-limit) function. We've rounded that number up for our gas limit.
43 | gasLimit: 250
44 | gasPrice: 0.0000001
45 | ttl: 600
46 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/components/UserDetails.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Grid } from "@real-world-pact/theme/components/Container";
2 | import { Label } from "@real-world-pact/theme/components/Form";
3 | import { RequestLoader } from "@real-world-pact/theme/components/Request";
4 | import { Text } from "@real-world-pact/theme/components/Text";
5 | import { ReactElement, ReactNode } from "react";
6 | import { AssetName } from "../contracts/controller";
7 | import { useUserStore } from "../state";
8 |
9 | export const Balances = (): ReactElement => {
10 | const balances = useUserStore((state) => state.balances);
11 |
12 | const entries = Array.from(balances.entries()).map(([key, value]) => {
13 | return (
14 |
15 | 0.0} request={value}>
16 | {(parsed) => {parsed}}
17 |
18 |
19 | );
20 | });
21 |
22 | return (
23 |
24 |
25 | {entries}
26 |
27 | );
28 | };
29 |
30 | interface BalancePair {
31 | asset: AssetName;
32 | children: ReactNode;
33 | }
34 |
35 | export const BalancePair = ({ asset, children }: BalancePair): ReactElement => {
36 | return (
37 |
38 | {asset}:
39 | {children}
40 |
41 | );
42 | };
43 |
44 | export const BorrowingCapacity = (): ReactElement => {
45 | const capacity = useUserStore((state) => state.borrowingCapacity);
46 | return (
47 |
48 |
49 |
50 |
51 | {(parsed) => {parsed}}
52 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/pact-api.ts:
--------------------------------------------------------------------------------
1 | /* PACT API CONFIGURATION
2 |
3 | This file contains some constants to configure our use of the Pact API via the
4 | pact-api-utils library written as part of this project.
5 |
6 | To switch our application from running in "development" mode to running in
7 | "production" mode is as simple as switching the Chainweb hostname and network we
8 | are targeting!
9 |
10 | */
11 |
12 | import { NetworkId } from "pact-lang-api";
13 | import { HostName, PactAPI } from "@real-world-pact/utils/pact-request";
14 | import {
15 | makeUsePactRequest,
16 | makeUsePactRequestAllChains,
17 | } from "@real-world-pact/utils/pact-request-hooks";
18 |
19 | // The network identifier is used to determine which network you intend your
20 | // transaction to be a part of: the "mainnet" network (production), the "testnet"
21 | // network (a test network maintained by Kadena), or the "development" network (our
22 | // local devnet instance). The hostname is used to route requests to the Chainweb
23 | // node we want to reach.
24 | const networkId: NetworkId = "development";
25 |
26 | // The location of the Chainweb node we're targeting. In our case this is the
27 | // host where devnet is running, but for production this would be the server
28 | // where you are hosting a Chainweb node.
29 | const hostname: HostName = "localhost:8080";
30 |
31 | // We deploy the faucet to Chain 0, so by default our requests will go to this
32 | // chain.
33 | const chainId: string = "0";
34 |
35 | // This new 'pactAPI' is configured with our defaults and can be used to execute
36 | // 'local' (read-only) and 'send' (modifies the blockchain) requests.
37 | export const pactAPI = new PactAPI({ networkId, hostname, chainId });
38 |
39 | // Use this hook to send a request and track its status in state
40 | export const usePactRequest = makeUsePactRequest(pactAPI);
41 |
42 | // Use this hook to send a request on all 20 chains and track their statuses.
43 | export const usePactRequestAllChains = makeUsePactRequestAllChains(pactAPI);
44 |
--------------------------------------------------------------------------------
/03-charkha-lending/contracts/setup.repl:
--------------------------------------------------------------------------------
1 | ; The Charkha contracts are intertwined and require the Charkha admin key to
2 | ; deploy, so this setup REPL file makes it a little easier to test by loading
3 | ; all the contracts together.
4 | ;
5 | ; Once loaded, you can test just the contract you care about, but without having
6 | ; to do the setup. You can see this setup used in the test.repl file, or you can
7 | ; set up your own additional test files using this setup.
8 | (load "../../pact-repl-utils/init.repl")
9 |
10 | ; First we'll load the interfaces, which don't require any keys to be present.
11 | (begin-tx)
12 | (load "interfaces/market-interface.pact")
13 | (load "interfaces/controller-interface.pact")
14 | (commit-tx)
15 |
16 | ; Then we'll load the contracts that do require Charkha to deploy them. Each
17 | ; contract specifies the 'charkha-admin-keyset' as administrator so we'll start
18 | ; by registering that keyset.
19 | (begin-tx)
20 | (env-data { "charkha-admin-keyset": { "keys": [ "charkha-admin-key" ], "pred": "keys-all" } })
21 | (namespace "free")
22 | (define-keyset "free.charkha-admin-keyset" (read-keyset "charkha-admin-keyset"))
23 | (commit-tx)
24 | (print "Defined the charkha-admin-keyset")
25 |
26 | ; We can then "deploy" as the administrator:
27 | (begin-tx)
28 | (env-data { "init": true })
29 | (env-sigs [ { "key": "charkha-admin-key", "caps": [] } ])
30 | (load "oracle/oracle.pact")
31 | (load "tokens/keth.pact")
32 | (load "tokens/chrk.pact")
33 | (load "markets/cwCHRK.pact")
34 | (load "markets/cwKETH.pact")
35 | (load "markets/cwKDA.pact")
36 | (load "governance.pact")
37 | (load "controller.pact")
38 | (commit-tx)
39 | (print "Loaded the Charkha and token contracts")
40 |
41 | ; Finally, since several of these contracts refer to another, they need to be
42 | ; initialized with the proper references:
43 | (begin-tx)
44 | (free.CHRK.init free.charkha-controller)
45 | (free.cwKDA.init free.charkha-controller)
46 | (free.cwKETH.init free.charkha-controller)
47 | (free.cwCHRK.init free.charkha-controller)
48 | (commit-tx)
49 | (print "Initialized contract references. Ready for testing!")
50 |
51 | (env-data {})
52 | (env-sigs [])
53 | (env-gas 0)
54 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/accounts.ts:
--------------------------------------------------------------------------------
1 | /* ACCOUNTS
2 |
3 | This module stores the public & private key pairs for some accounts we will use
4 | in our application.
5 |
6 | This is extremely dangerous! In the real world, you should NEVER commit a
7 | public/private key pair to your repository. If someone takes your private key
8 | then they can control your account. We are storing keys in this module ONLY
9 | for demonstration purposes.
10 |
11 | */
12 |
13 | import * as Pact from "pact-lang-api";
14 |
15 | export interface ChainwebAccount {
16 | address: string;
17 | keys: Pact.KeyPair;
18 | }
19 |
20 | // The faucet account owns the faucet contract and is able to upgrade it. It is
21 | // also the account that we'll use to pay gas fees and transfer funds to users.
22 | //
23 | // See also 01-faucet-contract/request/keys/goliath-faucet.yaml for the actual key
24 | // file that we generated with the Pact CLI.
25 | export const faucetAccount: ChainwebAccount = {
26 | address: "goliath-faucet",
27 | keys: {
28 | publicKey: "550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf",
29 | secretKey: "2a9d9112e96e8a299091e067879c50d1cbefbd9c7fff78e2623d7e3f0cd2bf45",
30 | },
31 | };
32 |
33 | // This is the keyset that governs the faucet contract account. We'll need to
34 | // satisfy this keyset any time we take an action on behalf of the faucet,
35 | // which according to the "keys-all" predicate means that the private key
36 | // corresponding with the public key in the "keys" array must have signed the
37 | // transaction.
38 | export const faucetKeySet: Pact.KeySet = {
39 | keys: [faucetAccount.keys.publicKey],
40 | pred: "keys-all",
41 | };
42 |
43 | // We also need to know the address of the wallet user account. In the real
44 | // world you would store this in a database, but in this example app we simply
45 | // generate new keys on every load of the application.
46 |
47 | // You can use this as an example of how to generate new keys in your own app!
48 | const userKeys: Pact.KeyPair = Pact.crypto.genKeyPair();
49 |
50 | export const userAccount: ChainwebAccount = {
51 | address: `k:${userKeys.publicKey}`,
52 | keys: userKeys,
53 | };
54 |
55 | export const userKeySet: Pact.KeySet = {
56 | keys: [userAccount.keys.publicKey],
57 | pred: "keys-all",
58 | };
59 |
--------------------------------------------------------------------------------
/03-charkha-lending/contracts/README.md:
--------------------------------------------------------------------------------
1 | # Contracts
2 |
3 | This directory holds the Charkha smart contracts, the [protocol unit tests](./test.repl), and the [protocol formal verification](./verify.repl).
4 |
5 | I recommend reading through the contracts in the same order they are described [in the Charkha guide](../guide/), which is to say in the following order:
6 |
7 | 1. **Price Oracle** ([contract](./oracle/oracle.pact), [tests](./oracle/oracle.repl))
8 |
9 | This contract is a simple refresher on Pact contracts, as it has minimal functionality and serves as a simple database.
10 |
11 | 2. **KETH Ethereum Bridge Token** ([contract](./tokens/keth.pact), [tests](./tokens/keth.repl))
12 |
13 | This contract demonstrates how to implement the fungible-v2 interface for a KIP-0005 token, and should be read before any other tokens (the other contract files omit comments around implementing the interface).
14 |
15 | 3. **CHRK Governance & Rewards Token** ([contract](./tokens/chrk.pact))
16 |
17 | This contract demonstrates a more advanced token with a capped maximum supply and restricted minting.
18 |
19 | 4. **Charkha Market Tokens** ([interface](./interfaces/market-interface.pact), [cwKDA](./markets/cwKDA.pact), [cwKETH](./markets/cwKETH.pact), [cwCHRK](./markets/cwCHRK.pact))
20 |
21 | These contracts demonstrate how to write an interface of our own and then implement several simple tokens against it.
22 |
23 | 5. **Governance Contract** ([contract](./governance.pact))
24 |
25 | This contract is a more complex Pact contract which demonstrates how to implement more sophisticated governance rules; this contract implemetns the Charkha formulas that can be changed according to a vote, where holding 1 CHRK token allows you 1 vote.
26 |
27 | 6. **Charkha Controlling Contract** ([interface](./interfaces/controller-interface.pact), [contract](./controller.pact))
28 |
29 | This contract brings all the others together into the full implementation of the Charkha protocol. It should be read after all the others, and demonstrates a real-world, sophisticated Pact contract.
30 |
31 | The entirety of the Charkha protocol is contained in these contracts. The basic contracts like the oracle and KETH token have accompanying `.repl` test files you can use to see the contract in action. The overall protocol has a [test.repl file](./test.repl) containing the full tests for the protocol.
32 |
33 | Each contract also has an accompanying TypeScript binding in [the Charkha app](../frontend/src/contracts) so you can see how this Pact code translates to TypeScript API calls.
34 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/send/return-funds.yaml:
--------------------------------------------------------------------------------
1 | # This transaction exercises the 'return-funds' function from the faucet
2 | # contract. We'll return funds from the test-user account.
3 | #
4 | # To execute this request:
5 | # faucet-request --send return-funds --signers test-user,goliath-faucet
6 |
7 | networkId: "development"
8 | type: exec
9 |
10 | # To return funds from our test user account we should use the (return-funds)
11 | # function from the faucet contract. This will credit the transfer against our
12 | # overall account limit.
13 | code: (free.goliath-faucet.return-funds (read-msg "account") (read-decimal "amount"))
14 |
15 | # Our transaction data supplies the data our code will read from. The 'account'
16 | # is our test user account, and the 'amount' is the amount of funds we want to
17 | # return to the faucet.
18 | data:
19 | account: "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0"
20 | amount: 20.0
21 |
22 | # Next, we'll turn to the transaction signers. The (return-funds) function
23 | # relies on the (coin.transfer) function to send funds from the user account to
24 | # the faucet account, which means the user account must sign the transaction
25 | # with the (coin.TRANSFER) capability:
26 | # https://github.com/kadena-io/chainweb-node/blob/56e99ae421d2269a657e3bb3780c6d707e5149a0/pact/coin-contract/v5/coin-v5.pact#L80-L84
27 | #
28 | # As usual, the faucet will pay the gas, so that account will sign the
29 | # transaction as well.
30 | signers:
31 | # This is the public key for the user account.
32 | - public: eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0
33 | caps:
34 | - name: "coin.TRANSFER"
35 | args: [
36 | # The sender argument is the user account
37 | "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0",
38 | # The receiver argument is the faucet account
39 | "goliath-faucet",
40 | # The amount matches the amount specified in transaction data.
41 | 20.0,
42 | ]
43 |
44 | # This is the public key for the faucet account, which will pay the gas fees.
45 | - public: 550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf
46 | caps:
47 | - name: "coin.GAS"
48 | args: []
49 |
50 | publicMeta:
51 | chainId: "0"
52 | sender: "goliath-faucet"
53 | # In the faucet.repl file we calculated the gas consumption for a call to the
54 | # (return-funds) function. We've rounded that number up for our gas limit.
55 | gasLimit: 900
56 | gasPrice: 0.0000001
57 | ttl: 600
58 |
--------------------------------------------------------------------------------
/theme/styled.config.ts:
--------------------------------------------------------------------------------
1 | import { blackA, whiteA, mauve, crimson, green, red } from "@radix-ui/colors";
2 | import { createStitches } from "@stitches/react";
3 |
4 | const spaceSizeScale = {
5 | 1: "0.25rem",
6 | 2: "0.5rem",
7 | 3: "0.75rem",
8 | 4: "1rem",
9 | 5: "1.25rem",
10 | 6: "1.5rem",
11 | 7: "1.75rem",
12 | 8: "2rem",
13 | 9: "2.25rem",
14 | 10: "2.5rem",
15 | 12: "3rem",
16 | 14: "3.5rem",
17 | 16: "4rem",
18 | 20: "5rem",
19 | 24: "6rem",
20 | 28: "7rem",
21 | 32: "8rem",
22 | 36: "9rem",
23 | 40: "10rem",
24 | 44: "11rem",
25 | 48: "12rem",
26 | 52: "13rem",
27 | 56: "14rem",
28 | 60: "15rem",
29 | 64: "16rem",
30 | 72: "18rem",
31 | 80: "20rem",
32 | 96: "24rem",
33 | };
34 |
35 | export const { css, styled, keyframes } = createStitches({
36 | theme: {
37 | colors: {
38 | ...crimson,
39 | ...mauve,
40 | ...green,
41 | ...red,
42 | ...whiteA,
43 | ...blackA,
44 | },
45 | space: spaceSizeScale,
46 | fonts: {
47 | sansSerif:
48 | "-apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif",
49 | mono: "Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace",
50 | },
51 | fontSizes: {
52 | "2xs": "0.5rem",
53 | xs: "0.75rem",
54 | sm: "0.875rem",
55 | md: "1rem",
56 | lg: "1.125rem",
57 | xl: "1.25rem",
58 | "2xl": "1.5rem",
59 | "3xl": "1.875rem",
60 | "4xl": "2.25rem",
61 | "5xl": "3rem",
62 | "6xl": "3.75rem",
63 | "7xl": "4.5rem",
64 | "8xl": "6rem",
65 | },
66 | fontWeights: {},
67 | lineHeights: {
68 | "2xs": 0.9,
69 | xs: 1,
70 | sm: 1.1,
71 | md: 1.3,
72 | base: 1.5,
73 | lg: 1.7,
74 | },
75 | letterSpacings: {},
76 | sizes: {
77 | ...spaceSizeScale,
78 | "container-sm": "640px",
79 | "container-md": "768px",
80 | "container-lg": "1024px",
81 | "container-xl": "1280px",
82 | },
83 | borderWidths: {},
84 | borderStyles: {},
85 | radii: {
86 | none: 0,
87 | sm: "0.125rem",
88 | base: "0.25rem",
89 | md: "0.375rem",
90 | lg: "0.5rem",
91 | xl: "0.75rem",
92 | "2xl": "1rem",
93 | "3xl": "1.5rem",
94 | full: "9999px",
95 | },
96 | shadows: {},
97 | zIndices: {},
98 | transitions: {},
99 | },
100 | });
101 |
--------------------------------------------------------------------------------
/theme/components/Request.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ExecError,
3 | EXEC_ERROR,
4 | PENDING,
5 | RequestStatus,
6 | REQUEST_ERROR,
7 | SUCCESS,
8 | } from "@real-world-pact/utils/pact-request";
9 | import { FailureResult } from "pact-lang-api";
10 | import { ReactElement } from "react";
11 | import { Flex } from "./Container";
12 | import { ErrorIcon, NotStartedIcon } from "./Icon";
13 | import { Spinner } from "./Spinner";
14 | import { Text } from "./Text";
15 |
16 | export interface RequestLoaderProps {
17 | request: null | RequestStatus;
18 | showError?: boolean;
19 | onExecError?: (error: FailureResult) => a;
20 | children: (parsed: a) => ReactElement;
21 | }
22 |
23 | export function RequestLoader({
24 | request,
25 | showError,
26 | onExecError,
27 | children,
28 | }: RequestLoaderProps): ReactElement {
29 | const iconCSS = { marginTop: "1px" };
30 | if (!request) return ;
31 | if (request.status === PENDING) return ;
32 | if (request.status === SUCCESS) return children(request.parsed);
33 | if (request.status === EXEC_ERROR && onExecError)
34 | return children(onExecError(request.response.result));
35 | if (showError) {
36 | return (
37 |
38 |
39 |
40 |
41 | );
42 | } else {
43 | return ;
44 | }
45 | }
46 |
47 | export interface RequestErrorProps {
48 | request: null | RequestStatus | (null | RequestStatus)[];
49 | [x: string]: unknown;
50 | }
51 |
52 | export const RequestErrorMessage = ({
53 | request,
54 | ...props
55 | }: RequestErrorProps): null | ReactElement => {
56 | const ErrorMessage = ({ req }: { req: RequestStatus }): null | ReactElement =>
57 | req.status === EXEC_ERROR ? (
58 |
59 | Pact execution failed ({req.response.result.error.type}) {req.response.result.error.message}
60 |
61 | ) : req.status === REQUEST_ERROR ? (
62 |
63 | Request to node failed: {req.message}
64 |
65 | ) : null;
66 |
67 | if (!request) {
68 | return null;
69 | } else if (!Array.isArray(request)) {
70 | return ;
71 | } else {
72 | const noNulls = request.flatMap((value) => (value ? [value] : []));
73 | const firstError = noNulls.find(
74 | (value) => value.status === EXEC_ERROR || value.status === REQUEST_ERROR
75 | );
76 | return firstError === undefined ? null : ;
77 | }
78 | };
79 |
--------------------------------------------------------------------------------
/theme/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from "../styled.config";
2 |
3 | export const Button = styled("button", {
4 | color: "$mauve12",
5 | fontWeight: "bold",
6 | padding: "$2 $3",
7 | borderRadius: "$full",
8 | borderWidth: "0.125rem",
9 | borderStyle: "solid",
10 | fontSize: "$sm",
11 | minWidth: "6rem",
12 |
13 | "&:hover": {
14 | cursor: "pointer",
15 | },
16 |
17 | defaultVariants: {
18 | variant: "primary",
19 | },
20 |
21 | variants: {
22 | variant: {
23 | primary: {
24 | backgroundColor: "$crimson9",
25 | borderColor: "$crimson9",
26 | "&:hover": {
27 | backgroundColor: "$crimson10",
28 | borderColor: "$crimson10",
29 | },
30 | "&:disabled": {
31 | cursor: "unset",
32 | color: "$blackA10",
33 | backgroundColor: "$blackA4",
34 | borderColor: "$blackA5",
35 | },
36 | },
37 |
38 | secondary: {
39 | backgroundColor: "$mauve2",
40 | borderColor: "$mauve2",
41 | "&:hover": {
42 | backgroundColor: "$mauve3",
43 | borderColor: "$mauve3",
44 | },
45 | "&:disabled": {
46 | cursor: "unset",
47 | color: "$blackA10",
48 | backgroundColor: "$blackA4",
49 | borderColor: "$blackA5",
50 | },
51 | },
52 | },
53 |
54 | outlined: {
55 | true: {},
56 | },
57 | },
58 |
59 | compoundVariants: [
60 | {
61 | variant: "primary",
62 | outlined: true,
63 | css: {
64 | color: "$crimson10",
65 | backgroundColor: "$mauve2",
66 | borderColor: "$crimson10",
67 | "&:hover": {
68 | color: "$crimson11",
69 | backgroundColor: "$crimson5",
70 | borderColor: "$crimson11",
71 | },
72 | },
73 | },
74 |
75 | {
76 | variant: "secondary",
77 | outlined: true,
78 | css: {
79 | borderColor: "$mauve12",
80 | "&:hover": {
81 | borderColor: "$mauve12",
82 | },
83 | "&:disabled": {
84 | borderColor: "$mauve10",
85 | },
86 | },
87 | },
88 | ],
89 | });
90 |
91 | export const IconButton = styled("button", {
92 | all: "unset",
93 | fontFamily: "inherit",
94 | borderRadius: "100%",
95 | height: 25,
96 | width: 25,
97 | display: "inline-flex",
98 | alignItems: "center",
99 | justifyContent: "center",
100 | color: "crimson11",
101 | position: "absolute",
102 | top: 10,
103 | right: 10,
104 | cursor: "pointer",
105 |
106 | "&:hover": { backgroundColor: "$crimson4" },
107 | "&:focus": { boxShadow: "0 0 0 2px $crimson7" },
108 | });
109 |
--------------------------------------------------------------------------------
/03-charkha-lending/contracts/oracle/oracle.repl:
--------------------------------------------------------------------------------
1 | (load "../../../pact-repl-utils/init.repl")
2 |
3 | ; To set up our test environment we must register the Charkha admin keyset (as
4 | ; it is required for the contract deployment).
5 | (begin-tx)
6 | (env-data { "charkha-admin-keyset": { "keys": [ "charkha-admin-key" ], "pred": "keys-all" } })
7 | (namespace "free")
8 | (define-keyset "free.charkha-admin-keyset" (read-keyset "charkha-admin-keyset"))
9 | (commit-tx)
10 |
11 | ; Next, we can sign a transaction in which we "deploy" the oracle contract.
12 | (begin-tx)
13 | (env-data { "init": true })
14 | (env-sigs [ { "key": "charkha-admin-key", "caps": [] } ])
15 | (load "./oracle.pact")
16 | (commit-tx)
17 | (print "Loaded oracle contract! Ready for testing...")
18 |
19 | ; Now we can begin testing! First, we'll verify the contract.
20 | (verify "free.charkha-oracle")
21 |
22 | ; Next, let's register the assets that are used in our contract.
23 | (begin-tx)
24 | (use free.charkha-oracle)
25 | (env-sigs [ { "key": "charkha-admin-key", "caps": [ (ADMIN) ] } ])
26 | (register-asset "KDA" 0.0)
27 | (register-asset "CHRK" 0.0)
28 | (register-asset "KETH" 0.0)
29 |
30 | ; We can also test out some failure cases.
31 | (expect-failure "Cannot register an asset twice." (register-asset "KDA" 0.0))
32 | (expect-failure "Cannot set asset price to a negative number." (register-asset "KDA2" -0.00000000001))
33 | (env-sigs [])
34 | (expect-failure "Cannot register an asset without the ADMIN capability." (register-asset "KDA2" 1))
35 | (commit-tx)
36 |
37 | ; While our trickiest function is registering an asset, we can ensure we've got
38 | ; tests for our other module functions as well.
39 | (begin-tx)
40 | (use free.charkha-oracle)
41 | (expect "Assets are CHRK, KDA, KETH" [ "CHRK", "KDA", "KETH" ] (get-assets))
42 |
43 | (expect-failure "Cannot set prices without the ADMIN capability." (set-price "KDA" 1.11))
44 | (expect-failure "Cannot set asset price if the asset is not registered." (set-price "KDA2" 1.0))
45 |
46 | (env-sigs [ { "key": "charkha-admin-key", "caps": [ (ADMIN) ] } ])
47 | (expect "KDA price was originally 0.0" 0.0 (get-price "KDA"))
48 | (set-price "KDA" 1.11)
49 | (expect "KDA price has been set" 1.11 (get-price "KDA"))
50 |
51 | ; Next, let's make sure that setting prices reflects the correct block time. By
52 | ; default the REPL will set the block-time to the epoch, ie. "1970-01-01T00:00:00Z",
53 | ; but we can override this environment data with (env-chain-data). This needs to
54 | ; be an actual parsed time value:
55 | ; https://pact-language.readthedocs.io/en/stable/pact-reference.html#time-formats
56 | (env-chain-data { "block-time": (time "2023-06-01T00:00:00Z") })
57 | (set-price "KDA" 2.410)
58 | (expect "KDA price and time were updated" { "usd-price": 2.410, "last-updated": (time "2023-06-01T00:00:00Z") } (get-asset "KDA"))
59 | (commit-tx)
60 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/oracle.ts:
--------------------------------------------------------------------------------
1 | /*
2 | This module supplies requests for the 'free.charkha-oracle' Pact module.
3 | */
4 |
5 | import Pact from "pact-lang-api";
6 | import { coercePactValue, LocalRequest, SendRequest } from "@real-world-pact/utils/pact-request";
7 | import { AssetName } from "./controller";
8 | import { charkhaKeyPair, sender02Address, sender02KeyPair } from "../constants";
9 | import { isPactDecimal, parsePactDecimal } from "@real-world-pact/utils/pact-code";
10 |
11 | export interface AssetPrice {
12 | lastUpdated: Date;
13 | usdPrice: number;
14 | }
15 |
16 | export interface RegisterAssetArgs {
17 | symbol: AssetName;
18 | price: Pact.PactDecimal;
19 | }
20 |
21 | export const registerAsset = (args: RegisterAssetArgs): SendRequest => ({
22 | code: { cmd: "free.charkha-oracle.register-asset", args: [args.symbol, args.price] },
23 | sender: sender02Address,
24 | gasLimit: 500,
25 | signers: [
26 | {
27 | ...charkhaKeyPair,
28 | clist: [{ name: "free.charkha-oracle.ADMIN", args: [] }],
29 | },
30 | {
31 | ...sender02KeyPair,
32 | clist: [{ name: "coin.GAS", args: [] }],
33 | },
34 | ],
35 | transformResponse: (response) => response as string,
36 | });
37 |
38 | export interface SetPriceArgs {
39 | symbol: AssetName;
40 | price: Pact.PactDecimal;
41 | }
42 |
43 | export const setPrice = (args: SetPriceArgs): SendRequest => ({
44 | code: { cmd: "free.charkha-oracle.set-price", args: [args.symbol, args.price] },
45 | sender: sender02Address,
46 | gasLimit: 500,
47 | signers: [
48 | {
49 | ...charkhaKeyPair,
50 | clist: [{ name: "free.charkha-oracle.ADMIN", args: [] }],
51 | },
52 | {
53 | ...sender02KeyPair,
54 | clist: [{ name: "coin.GAS", args: [] }],
55 | },
56 | ],
57 | transformResponse: (response) => response as string,
58 | });
59 |
60 | export const getAssets = (): LocalRequest => ({
61 | code: { cmd: "free.charkha-oracle.get-assets", args: [] },
62 | transformResponse: coercePactValue,
63 | });
64 |
65 | export const getAsset = (symbol: AssetName): LocalRequest => ({
66 | code: { cmd: "free.charkha-oracle.get-asset", args: [symbol] },
67 | transformResponse: (response: Pact.PactValue) => {
68 | const respObj: { "last-updated": string; "usd-price": number } = coercePactValue(response);
69 | return {
70 | lastUpdated: new Date(respObj["last-updated"]),
71 | usdPrice: respObj["usd-price"],
72 | };
73 | },
74 | });
75 |
76 | export const getPrice = (symbol: AssetName): LocalRequest => ({
77 | code: { cmd: "free.charkha-oracle.get-price", args: [symbol] },
78 | transformResponse: (response: Pact.PactValue) => {
79 | if (isPactDecimal(response)) {
80 | return parseFloat(response.decimal);
81 | } else {
82 | return coercePactValue(response);
83 | }
84 | },
85 | });
86 |
--------------------------------------------------------------------------------
/03-charkha-lending/contracts/interfaces/market-interface.pact:
--------------------------------------------------------------------------------
1 | (namespace "free")
2 |
3 | (interface charkha-market-iface
4 |
5 | ; ----------
6 | ; SCHEMA
7 |
8 | (defschema participant
9 | @doc
10 | "Schema for the state maintained about a market participant, updated when\
11 | \that participant interacts with the market. This also serves as the \
12 | \accounts table for the fungible-v2 interface."
13 |
14 | ; Block height of the last update, ie. last interest rate change
15 | last-updated:integer
16 | ; The interest rate index at the time of the last update, which is used to
17 | ; calculate total interest owed at the time of the next update
18 | last-rate-index:decimal
19 | ; The user's total collateral in this market (in tokens, not the asset).
20 | balance:decimal
21 | ; The user's total borrows in this market.
22 | borrows:decimal
23 | ; The user's guard for the account.
24 | guard:guard)
25 |
26 | ; ---------
27 | ; FUNCTIONS
28 |
29 | ; We do not accrue interest for borrowers over time and instead just keep
30 | ; track of the interest rate index. However, sometimes we do need to apply
31 | ; the index to a borrower's balance (ie. accrue interest), such as when
32 | ; checking whether they have exceeded their borrowing capacity and can be
33 | ; liquidated or when they move to repay their interest.
34 | (defun accrue-interest:decimal (account:string)
35 | @doc
36 | "Accrue interest owed to a borrower according to the interest rate index,\
37 | \returning their new borrow total.")
38 |
39 | ; A user should be able to supply or borrow funds on Charkha with zero
40 | ; participation from the protocol itself. To facilitate crediting cTokens to
41 | ; a user account without the Charkha signature, we use a two-step pattern in
42 | ; which the protocol records the funds a user has supplied or borrowed and
43 | ; then immediately calls the (apply-balance-change) function in the associated
44 | ; market contract to credit or debit those funds.
45 | ;
46 | ; This function is unguarded (anyone can call it), but its implementation
47 | ; should always ask the controller contract for the correct amount.
48 | (defun apply-balance-change:string (account:string guard:guard)
49 | @doc
50 | "Add tokens to an account that has supplied funds or remove them from an \
51 | \account that has borrowed funds.")
52 |
53 | (defun get-participant:object{participant} (account:string)
54 | @doc "Read data about a market participant.")
55 |
56 | (defun get-borrow:decimal (account:string)
57 | @doc
58 | "Get the total borrow amount for a market participant denominated in the\
59 | \underlying asset. May be out of date; if you need the up-to-date borrow \
60 | \with interest applied, use (accrue-interest).")
61 |
62 | (defun get-supply:decimal (account:string)
63 | @doc
64 | "Get the total supply amount for a market participant denominated in the \
65 | \underlying asset. To see the cToken balance, use (get-balance).")
66 |
67 | )
68 |
--------------------------------------------------------------------------------
/01-faucet-contract/run-deploy-contract.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /* To deploy the faucet contract we must take two steps:
4 |
5 | 1. We need to fund the faucet account, if it doesn't already exist.
6 | 2. We need to use the faucet account to deploy the contract, if it hasn't
7 | already been deployed.
8 |
9 | Let's take both steps in this short script. This script reuses the functionality
10 | of run-request.js, so you can run these requests from the command line yourself
11 | if you would like. Just provide the given arguments to the ./run-request.js
12 | script directly.
13 | */
14 |
15 | const { parseArgs } = require("./internal/parse-args");
16 | const { runRequest } = require("./run-request");
17 |
18 | // Our first function will fund the faucet account, if necessary.
19 | const fundFaucetIfNeeded = async () => {
20 | const detailsArgs = ["--local", "faucet-details"];
21 | const faucetDetails = await parseArgs(detailsArgs).then(runRequest);
22 |
23 | if (
24 | faucetDetails.status === "failure" &&
25 | faucetDetails.error.message.includes("row not found: goliath-faucet")
26 | ) {
27 | console.log(
28 | "Faucet account not found on local Chainweb node. Funding faucet account..."
29 | );
30 | const fundArgs = ["--send", "fund-faucet-account", "--signers", "sender00"];
31 | const result = await parseArgs(fundArgs).then(runRequest);
32 | if (result.status === "success") {
33 | console.log(`Funded! Cost ${result.gas} gas.`);
34 | } else {
35 | throw new Error(`Failed to fund account: ${result.error}`);
36 | }
37 | } else if (faucetDetails.status === "failure") {
38 | throw new Error(
39 | `Unexpected error getting faucet account details: ${faucetDetails.error.message}`
40 | );
41 | } else {
42 | console.log(
43 | `Faucet account found with ${faucetDetails.data.balance} in funds.`
44 | );
45 | }
46 | };
47 |
48 | const deployFaucetIfNeeded = async () => {
49 | const detailArgs = ["--local", "faucet-contract-details"];
50 | const contractDetails = await parseArgs(detailArgs).then(runRequest);
51 |
52 | if (contractDetails.status === "failure") {
53 | console.log(
54 | "Faucet contract not found on local Chainweb node. Deploying contract..."
55 | );
56 | const deployArgs = [
57 | "--send",
58 | "deploy-faucet-contract",
59 | "--signers",
60 | "goliath-faucet",
61 | ];
62 | const deployResult = await parseArgs(deployArgs).then(runRequest);
63 | if (deployResult.status === "success") {
64 | console.log(`Deployed! Cost: ${deployResult.gas} gas.`);
65 | } else {
66 | throw new Error(
67 | `Failed to deploy contract: ${JSON.stringify(
68 | deployResult.error,
69 | null,
70 | 2
71 | )}`
72 | );
73 | }
74 | } else {
75 | console.log(
76 | `Faucet contract exists with the name ${contractDetails.data.name} and hash ${contractDetails.data.hash}`
77 | );
78 | }
79 | };
80 |
81 | // Finally, we can bring the two together to fund our faucet and then deploy
82 | // the contract using the faucet account.
83 | const main = async () => {
84 | await fundFaucetIfNeeded();
85 | await deployFaucetIfNeeded();
86 | };
87 |
88 | main();
89 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/send/deploy-faucet-contract.yaml:
--------------------------------------------------------------------------------
1 | # This YAML file describes a transaction that, when executed, will deploy the
2 | # faucet contract to Chainweb.
3 | #
4 | # To execute this request (you must have funded the faucet account):
5 | # faucet-request --send deploy-faucet-contract --signers goliath-faucet
6 | #
7 | # Alternately, to fund the faucet account _and_ deploy the contract:
8 | # faucet-deploy
9 |
10 | networkId: "development"
11 | type: "exec"
12 |
13 | # To deploy our contract we need to send its entire contents to Chainweb as a
14 | # transaction. When a Chainweb node receives a module it will attempt to
15 | # register it in the given namespace.
16 | codeFile: "../../faucet.pact"
17 |
18 | # The 'data' key is for JSON data we want to include with our transaction. As a
19 | # general rule, any use of (read-msg), (read-keyset), and so on in your contract
20 | # indicates data that must be included here.
21 | #
22 | # Our contract reads the transaction data twice:
23 | # - (read-keyset "goliath-faucet-keyset")
24 | # - (read-msg "init")
25 | data:
26 | goliath-faucet-keyset:
27 | # On deployment, our contract will register a new keyset reference on
28 | # Chainweb named "goliath-faucet-keyset". The keyset at this reference will
29 | # govern the goliath-faucet module, which means the module can only be
30 | # upgraded by this keyset.
31 | #
32 | # We want our faucet account to control the module, so we'll reuse the
33 | # keyset that guards our faucet account, ie. the goliath-faucet.yaml keys.
34 | keys:
35 | - "550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf"
36 | pred: "keys-all"
37 |
38 | # Next, our contract looks for an 'init' key to determine whether it should
39 | # initialize data (for example, whether it should create tables). This request
40 | # deploys the contract, so we'll set this to true.
41 | init: true
42 |
43 | signers:
44 | # The contract verifies that the keyset that governs the goliath-faucet module
45 | # is satisfied by the transaction signatures. Since the goliath-faucet module
46 | # is governed by the goliath-faucet account, we need that account to sign this
47 | # transaction.
48 | - public: "550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf"
49 |
50 | publicMeta:
51 | # The faucet contract only works on chain 0, so that's where we'll deploy it.
52 | chainId: "0"
53 |
54 | # The contract should be deployed by the faucet account, which means the
55 | # faucet account is responsible for paying the gas for this transaction. You
56 | # must have used the 'fund-faucet-account.yaml' request to fund the faucet
57 | # account before you can use this deployment request file.
58 | sender: "goliath-faucet"
59 |
60 | # To determine the gas limit for most requests you can simply execute the Pact
61 | # code in the REPL, use (env-gaslog) to measure consumption, and round up the
62 | # result. However, deployment is different; you can't simply measure a call to
63 | # (load "faucet.pact") as it will provide an inaccurate measure.
64 | #
65 | # Instead, I first set the gas limit to 150000 (the maximum) and deployed the
66 | # contract to our local simulation Chainweb. Then, I recorded the gas
67 | # consumption that the node reported and round it up.
68 | gasLimit: 80000
69 |
70 | gasPrice: 0.0000001
71 | ttl: 600
72 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/components/ReturnFundsModal.tsx:
--------------------------------------------------------------------------------
1 | /* RETURN FUNDS MODAL
2 |
3 | The 'request funds' modal lets the user request funds from the faucet contract.
4 | This modal is a simple form that lets the user provide a KDA amount and simply
5 | calls back to the App.tsx module to make the request.
6 |
7 | This file isn't commented because this is standard React code.
8 |
9 | */
10 |
11 | import { Button } from "@real-world-pact/theme/components/Button";
12 | import { Fieldset, Input, Label } from "@real-world-pact/theme/components/Form";
13 | import { Text } from "@real-world-pact/theme/components/Text";
14 |
15 | import * as Pact from "pact-lang-api";
16 |
17 | import { Formik } from "formik";
18 | import { ReactNode, useState } from "react";
19 | import { parsePactDecimal } from "@real-world-pact/utils/pact-code";
20 | import { ControlledModal, FormSubmitButton, Modal } from "@real-world-pact/theme/components/Modal";
21 |
22 | export interface ReturnFundsModalProps {
23 | onSubmit: (amount: Pact.PactDecimal) => any;
24 | [x: string]: unknown;
25 | }
26 |
27 | export const ReturnFundsModal = ({ onSubmit, ...props }: ReturnFundsModalProps) => {
28 | const [open, setOpen] = useState(false);
29 |
30 | const title = "Return KDA";
31 |
32 | const description = (
33 |
34 | You can send KDA to other Kadena addresses. You simply need to know the address of their
35 | account (their k: account). However, since this is a development environment, we've built a
36 | smart contract you can return funds to. You can only send up to the amount you have received
37 | from the faucet (ie. your current balance).
38 |
39 | );
40 |
41 | const trigger = (
42 |
45 | );
46 |
47 | return (
48 |
55 | {
58 | const parsed = parsePactDecimal(values.amount) as Pact.PactDecimal;
59 | onSubmit(parsed);
60 | }}
61 | validate={(values) => {
62 | let errors: { amount?: string } = {};
63 | if (values.amount === "") errors.amount = "Required.";
64 | const parsed = parsePactDecimal(values.amount);
65 | if (typeof parsed === "string") errors.amount = parsed;
66 | return errors;
67 | }}
68 | >
69 | {(formik) => (
70 |
87 | )}
88 |
89 |
90 | );
91 | };
92 |
--------------------------------------------------------------------------------
/pact-api-utils/pact-code.ts:
--------------------------------------------------------------------------------
1 | import * as Pact from "pact-lang-api";
2 |
3 | // When we hit the Pact API we must send some Pact code. It can be awkward to
4 | // represent Pact in JavaScript because the syntax is so different; we'll use
5 | // this type to capture applying a function to arguments.
6 | export interface PactCode {
7 | cmd: string;
8 | args: Array;
9 | }
10 |
11 | export const isPactCode = (p: any): p is PactCode => p.cmd && Array.isArray(p.args);
12 | export const isPactInt = (p: any): p is Pact.PactInt => p.int;
13 | export const isPactDecimal = (p: any): p is Pact.PactDecimal => p.decimal;
14 | export const isPactReference = (p: any): p is Pact.PactReference => p.ref;
15 | export const isPactDate = (p: any): p is Pact.PactDate => p.timep;
16 |
17 | // A helper function to format our representation of Pact code into a string
18 | // that we can send off to Chainweb for evaluation.
19 | export const formatPactCode = (code: PactCode | PactCode[]): string => {
20 | const formatPactValue = (value: Pact.PactValue): string => {
21 | if (typeof value === "boolean") {
22 | return value.toString();
23 | } else if (typeof value === "string") {
24 | return `"${value}"`;
25 | } else if (isPactInt(value)) {
26 | return value.int;
27 | } else if (isPactDecimal(value)) {
28 | return value.decimal;
29 | } else if (isPactReference(value)) {
30 | return value.ref;
31 | } else if (isPactDate(value)) {
32 | return value.timep;
33 | } else if (Array.isArray(value)) {
34 | return `[ ${value.map(formatPactValue)} ]`;
35 | } else {
36 | for (const key in value) {
37 | value[key] = formatPactValue(value[key]);
38 | }
39 | return JSON.stringify(value);
40 | }
41 | };
42 |
43 | const format = (value: PactCode | Pact.PactValue): string => {
44 | if (isPactCode(value)) {
45 | return formatPactCode(value);
46 | } else {
47 | return formatPactValue(value);
48 | }
49 | };
50 |
51 | if (isPactCode(code)) {
52 | return `(${code.cmd} ${code.args.map(format).join(" ")})`;
53 | } else {
54 | return code.map(formatPactCode).join("\n");
55 | }
56 | };
57 |
58 | // We'll validate that the provided input is parseable into a Pact decimal.
59 | export const parsePactDecimal = (value: string): string | Pact.PactDecimal => {
60 | const split = value.split(".");
61 | const numbers = new RegExp("[0-9]+$");
62 | if (split.length === 1) {
63 | const parsed = parseFloat(split[0]);
64 | if (!numbers.test(split[0])) {
65 | return "Can only contain digits and a decimal point.";
66 | } else if (isNaN(parsed) || parsed < 0) {
67 | return "Invalid number.";
68 | } else {
69 | return { decimal: `${split[0]}.0` };
70 | }
71 | } else if (split.length === 2) {
72 | if (!numbers.test(split[0]) || !numbers.test(split[1])) {
73 | return "Can only contain digits and a decimal point.";
74 | }
75 | const parsedFirst = parseFloat(split[0]);
76 | const parsedSecond = parseFloat(split[1]);
77 | if (isNaN(parsedFirst) || parsedFirst < 0) {
78 | return "Invalid number before the decimal place.";
79 | } else if (isNaN(parsedSecond) || parsedSecond < 0) {
80 | return "Invalid number after the decimal place.";
81 | } else {
82 | return { decimal: `${parsedFirst.toString()}.${parsedSecond.toString()}` };
83 | }
84 | } else {
85 | return "Invalid number (more than one decimal point).";
86 | }
87 | };
88 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/send/request-funds.yaml:
--------------------------------------------------------------------------------
1 | # This transaction exercises the 'request-funds' function from the faucet
2 | # contract. By default it transfers 20.0 KDA, but feel free to change the amount
3 | # so you can explore how the contract responds!
4 | #
5 | # We'll use the request-funds operation on behalf of an account that we control
6 | # for testing's sake. The request-funds function in the faucet contract uses the
7 | # `coin.transfer-create` function under the hood, which has a few requirements:
8 | #
9 | # - The request must be signed with the TRANSFER capability
10 | # - The request must include a keyset for the destination address. If the
11 | # address does not exist, then the transfer will create the account and guard
12 | # it with the given keyset. If the address does exist, then the transfer will
13 | # verify the guard, and only transfer the funds if it matches.
14 | #
15 | # To execute this request:
16 | # faucet-request --send request-funds --signers goliath-faucet
17 |
18 | networkId: "development"
19 | type: exec
20 |
21 | # To fund our new test user account we should use the 'request-funds' function
22 | # from the faucet contract. This function requires the account that should
23 | # receive funds, the intended keyset to guard that account, and the amount of
24 | # KDA to transfer.
25 | code: (free.goliath-faucet.request-funds (read-msg "receiver") (read-keyset "receiver-guard") (read-decimal "amount"))
26 |
27 | # Our transaction data supplies the data our code will read from. The receiver
28 | # should be the k: account for our test user account and the keyset should
29 | # ensure that the keys from the test-user.yaml key pair must have signed any
30 | # transaction that takes sensitive actions on behalf of the user account.
31 | #
32 | # The contract only allows transfers of up to 20.0 KDA at a time initially, so
33 | # we'll stick to that limit. (Try tweaking the amount, though, and see what
34 | # happens when we exceed the limit!)
35 | data:
36 | receiver: "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0"
37 | amount: 20.0
38 | receiver-guard:
39 | # The public key is taken from the test-user.yaml key pair.
40 | keys:
41 | - "eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0"
42 | pred: "keys-all"
43 |
44 | # Next, we'll turn to the transaction signers. The (request-funds) function
45 | # relies on the (coin.transfer) function, which in turn requires that the
46 | # transaction has been signed with the (coin.TRANSFER) capability:
47 | # https://github.com/kadena-io/chainweb-node/blob/56e99ae421d2269a657e3bb3780c6d707e5149a0/pact/coin-contract/v5/coin-v5.pact#L80-L84
48 | signers:
49 | - public: 550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf
50 | caps:
51 | - name: "coin.TRANSFER"
52 | args: [
53 | # The sender argument is the faucet account
54 | "goliath-faucet",
55 | # The receiver argument is the user account
56 | "k:eed1f83db0fcced5a668c2e397bedfe3ed33643f0f919426edca52bbd2e215c0",
57 | # The amount matches the amount specified in transaction data.
58 | 20.0,
59 | ]
60 |
61 | - name: "coin.GAS"
62 | args: []
63 |
64 | publicMeta:
65 | chainId: "0"
66 | sender: "goliath-faucet"
67 | # In the faucet.repl file we calculated the gas consumption for a call to the
68 | # (request-funds) function. We've rounded that number up for our gas limit.
69 | gasLimit: 950
70 | gasPrice: 0.0000001
71 | ttl: 600
72 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/components/RequestFundsModal.tsx:
--------------------------------------------------------------------------------
1 | /* REQUEST FUNDS MODAL
2 |
3 | The 'request funds' modal lets the user request funds from the faucet contract.
4 | This modal is a simple form that lets the user provide a KDA amount and simply
5 | calls back to the App.tsx module to make the request.
6 |
7 | This file isn't commented because this is standard React code.
8 |
9 | */
10 |
11 | import { Button } from "@real-world-pact/theme/components/Button";
12 | import { Fieldset, Input, Label } from "@real-world-pact/theme/components/Form";
13 | import { Text } from "@real-world-pact/theme/components/Text";
14 |
15 | import * as Pact from "pact-lang-api";
16 |
17 | import { Formik } from "formik";
18 | import { useState } from "react";
19 | import { parsePactDecimal } from "@real-world-pact/utils/pact-code";
20 | import { ControlledModal, FormSubmitButton } from "@real-world-pact/theme/components/Modal";
21 |
22 | export interface RequestFundsModalProps {
23 | onSubmit: (amount: Pact.PactDecimal) => any;
24 | [x: string]: unknown;
25 | }
26 |
27 | export const RequestFundsModal = ({ onSubmit, ...props }: RequestFundsModalProps) => {
28 | const [open, setOpen] = useState(false);
29 |
30 | const title = "Request Funds";
31 |
32 | const description = (
33 |
34 | Other Kadena addresses can send their KDA to you. You simply need to share your account
35 | address with them (your k: account). However, we've built a smart contract that you can ask to
36 | send you funds. This smart contract limits how much KDA it will send you per request and the
37 | total KDA it will send to any account. However, you can change these limits using the Admin
38 | section in the navbar. If you reach your limit, try raising it using the 'Admin' modal!
39 |
40 | );
41 |
42 | const trigger = (
43 |
46 | );
47 |
48 | return (
49 |
56 | {
59 | console.log(values);
60 | const parsed = parsePactDecimal(values.amount) as Pact.PactDecimal;
61 | onSubmit(parsed);
62 | }}
63 | validate={(values) => {
64 | let errors: { amount?: string } = {};
65 | if (values.amount === "") errors.amount = "Required.";
66 | const parsed = parsePactDecimal(values.amount);
67 | if (typeof parsed === "string") errors.amount = parsed;
68 | return errors;
69 | }}
70 | >
71 | {(formik) => (
72 |
89 | )}
90 |
91 |
92 | );
93 | };
94 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Charkha UI
2 |
3 | The Charkha frontend directory contains TypeScript + React code for the lending protocol frontend. This provides a pleasant interface to explore the protocol and is a small example of writing an app that uses Chainweb as its backend. It is not thoroughly-commented simply because this is a Pact demonstration and not a React one.
4 |
5 | ## In Action
6 |
7 | When you first load the application you will see the deployment screen. This screen only renders if you haven't deployed the protocol yet. Once deployed, you won't see this screen anymore.
8 |
9 | 
10 |
11 | The protocol begins with three markets and fresh price data from CoinMarketCap to seed the oracle contract. Markets have so suppliers or borrowers at first, so they are all static.
12 |
13 | 
14 |
15 | The top of the application is the navbar and includes a modal for you to look at your account balances and, if you want, bridge ETH to KETH. At first your account simply shows your KDA balance, as you have no other assets yet.
16 |
17 | 
18 |
19 | The application automatically kicks off some lending activity using accounts that already exist on devnet (sender01 and sender02). Here's the markets page once there are some lenders and borrowers — notice the change in interest rates! Interest rates and exchange rates will continue to be calculated in the background by the protocol.
20 |
21 | 
22 |
23 | You are able to participate in the markets, too. Let's lend some KDA and start earning interest and CHRK reward tokens:
24 |
25 | 
26 |
27 | Next, let's turn to the community governance features of Charkha. At first, there are no proposals and you haven't earned any CHRK tokens.
28 |
29 | 
30 |
31 | We can try to submit a proposal, but if you don't have any CHRK tokens then you're not eligible to participate in community governance:
32 |
33 | 
34 |
35 | Let's claim our CHRK tokens from the governance page that we've earned by participating in the KDA market. It's only been a few blocks so we haven't earned much:
36 |
37 | 
38 |
39 | However, it's enough to submit a new proposal to change the KDA market base rate to 10%. This is the base interest rate charged to borrows before utilization and the multiplier are factored in. We can also create proposals to change those market factors, too.
40 |
41 | 
42 |
43 | Proposals must stay open for a specified period of time before voting can be closed. If you try to close the proposal early you'll see an error message. While we wait, let's look at the KDA market interest rates before our change:
44 |
45 | 
46 |
47 | Then we can close voting on our proposal — since we were the only voters, the proposal passes.
48 |
49 | 
50 |
51 | We can now see the effect of community governance on the markets by an increase in interest rates paid in the KDA market:
52 |
53 | 
54 |
55 | You can also see through these screenshots that our oracle has been updating in the background! Notice, for example, that KDA has slightly changed in price.
56 |
57 | ### Wrapping Up
58 |
59 | You can run the application with `charkha-start` if you are using the Nix development shell and with `pnpm run dev` in this directory otherwise.
60 |
--------------------------------------------------------------------------------
/03-charkha-lending/README.md:
--------------------------------------------------------------------------------
1 | # Charkha Lending Project
2 |
3 | The Charkha lending protocol is a complete decentralized lending app built on Kadena's Chainweb blockchain. Decentralized finance has been one of the standout uses of smart contracts and Charkha is inspired by the popular [Compound](https://compound.finance) lending protocol.
4 |
5 | Charkha contains all the components of a real-world DeFi project, including:
6 |
7 | 1. The [Charkha white paper](./Charkha-Protocol-Whitepaper.pdf) which describes the protocol in depth, and which you should read first (it also has [a collection of examples](./Charkha-Protocol-Examples.pdf)).
8 | 2. A [development guide](./guide), which explains how we translated the white paper into smart contract implementations.
9 | 3. A collection of [thoroughly-commented smart contracts](./contracts) that implement the protocol and tests.
10 | 4. A complete [TypeScript + React frontend](./frontend) that shows the current markets and their interest rates and allows you to lend and borrow assets, submit governance proposals, liquidate under-collateralized accounts, and more.
11 |
12 | As you read through the white paper, contracts, and frontend, you'll see how to:
13 |
14 | - Develop sophisticated, real-world smart contracts to implement a "white paper" spec
15 | - Bring off-chain data to the blockchain (real-world asset prices, for example)
16 | - Implement your own tokens according to the KIP-0005 token standard
17 | - Design a rewards accrual system that incentivizes participation in the protocol
18 | - Establish community governance for our smart contracts, where participants submit proposals and vote on protocol changes with their CHRK reward holdings
19 | - Take full advantage of decentralization by designing features that require no participation from an administrator — including borrowing real-world assets like KDA
20 | - Navigate around smart contract performance limitations by establishing index values, snapshotting to the database, and more
21 |
22 | Finally, Charkha is a fully-functioning lending protocol with a corresponding frontend. I encourage you to run the app yourself, lend some funds, submit some governance proposals, and earn some interest!
23 |
24 | ## Features
25 |
26 | Charkha is a lending application that supports several markets. In the main "markets" page you can see your lending and borrowing activity and the current state of the markets supported in the protocol. Asset prices are recorded in USD using the Charkha oracle contract.
27 |
28 | 
29 |
30 | Charkha also allows the community to create and vote on proposals to change the protocol itself. Charkha participants earn the CHRK rewards token by participating in markets and can use this token to vote on proposals. Here's an open proposal that changes the base borrower interest rate in the KDA market:
31 |
32 | 
33 |
34 | To fully explore Charkha, please read the white paper and run the application locally!
35 |
36 | ## Run the Application
37 |
38 | Assuming you have cloned this repository and initialized the `devnet` submodule as described in the main README, you can run Charkha yourself with a simulation of Chainweb with:
39 |
40 | ```sh
41 | # Add your CoinMarketCap API key to the .env file (see the development guide for
42 | # instructions on getting your own API key):
43 | echo "VITE_CMC_API_KEY=" >> 03-charkha-lending/frontend/.env
44 |
45 | # Enter the Nix shell (use nix-shell if your Nix installation does not support flakes)
46 | nix develop
47 |
48 | # Start the simulation blockchain (run devnet-stop to stop the simulation and devnet-clean to reset to a clean state).
49 | devnet-start
50 |
51 | # Start the Charkha frontend (Ctrl+C to exit)
52 | charkha-start
53 | ```
54 |
55 | If successful, you will see the deployment screen:
56 |
57 | 
58 |
--------------------------------------------------------------------------------
/devshell.toml:
--------------------------------------------------------------------------------
1 | # https://numtide.github.io/devshell
2 |
3 | # Faucet Contract
4 |
5 | [[commands]]
6 | name = "faucet-deploy"
7 | category = "01 faucet contract"
8 | command = """
9 | cd $(git rev-parse --show-toplevel)/01-faucet-contract
10 | echo "./01-faucet-contract/run-deploy-contract.js"
11 | ./run-deploy-contract.js
12 | """
13 | help = "Deploy the faucet contract to devnet"
14 |
15 | [[commands]]
16 | name = "faucet-request"
17 | category = "01 faucet contract"
18 | command = """
19 | cd $(git rev-parse --show-toplevel)/01-faucet-contract
20 | ./run-request.js $@
21 | """
22 | help = "Use a request file to send a request to the faucet contract on devnet"
23 |
24 | # Goliath Wallet
25 |
26 | [[commands]]
27 | name = "goliath-start"
28 | category = "02 goliath wallet"
29 | command = """
30 | cd $(git rev-parse --show-toplevel)/02-goliath-wallet
31 | pnpm install
32 | pnpm run dev
33 | """
34 | help = "Start the Goliath wallet frontend (Ctrl+C to stop)"
35 |
36 | # Charkha Lending Protocol
37 |
38 | [[commands]]
39 | name = "charkha-start"
40 | category = "03 charkha lending"
41 | command = """
42 | cd $(git rev-parse --show-toplevel)/03-charkha-lending/frontend
43 | pnpm install
44 | pnpm run dev
45 | """
46 | help = "Start the Charkha lending dapp (Ctrl+C to stop)"
47 |
48 | # Devnet
49 |
50 | [[commands]]
51 | name = "devnet-start"
52 | category = "devnet"
53 | command = """
54 | cd $(git rev-parse --show-toplevel)/devnet
55 | echo "Starting devnet (takes about 60 seconds...)"
56 | git submodule update --init
57 | docker compose pull
58 |
59 | if [ ! -d "db" ]; then
60 | echo "db directory does not exist, creating one from snapshot."
61 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then
62 | cp -r ../devnet-backup/db-linux db
63 | elif [[ "$OSTYPE" == "darwin"* ]]; then
64 | cp -r ../devnet-backup/db-macos db
65 | else
66 | echo "unknown operating system (only linux and macos supported)"
67 | fi
68 | fi
69 |
70 | docker compose -f docker-compose.minimal.yaml up --detach --remove-orphans
71 | """
72 | help = "Start the devnet blockchain simulator"
73 |
74 | [[commands]]
75 | name = "devnet-clean"
76 | category = "devnet"
77 | command = """
78 | cd $(git rev-parse --show-toplevel)/devnet
79 | docker compose -f docker-compose.minimal.yaml down
80 |
81 | if [ -d "db" ]; then
82 | echo "Replacing db directory with snapshot."
83 | sudo rm -rf db
84 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then
85 | cp -r ../devnet-backup/db-linux db
86 | elif [[ "$OSTYPE" == "darwin"* ]]; then
87 | cp -r ../devnet-backup/db-macos db
88 | else
89 | echo "unknown operating system (only linux and macos supported)"
90 | fi
91 | fi
92 | """
93 | help = "Stop the devnet blockchain simulator and reset the database."
94 |
95 | [[commands]]
96 | name = "devnet-stop"
97 | category = "devnet"
98 | command = """
99 | cd $(git rev-parse --show-toplevel)/devnet
100 | docker compose -f docker-compose.minimal.yaml down
101 | """
102 | help = "Stop the devnet blockchain simulator"
103 |
104 | # Tools
105 |
106 | [[commands]]
107 | package = "nodejs-16_x"
108 | category = "tools"
109 | name = "node"
110 | help = "The Node runtime and NPM package manager"
111 |
112 | [[commands]]
113 | package = "pact"
114 | category = "tools"
115 | name = "pact"
116 | help = "The Pact programming language"
117 |
118 | [[commands]]
119 | package = "nodePackages.pnpm"
120 | category = "tools"
121 | name = "pnpm"
122 | help = "The performant node package manager"
123 |
124 | [[commands]]
125 | package = "z3"
126 | category = "tools"
127 | name = "z3"
128 | help = "The Z3 theorem prover"
129 |
130 | # Shell
131 |
132 | [devshell]
133 | name = "real-world-pact"
134 | motd = "{202}🔨 Welcome to the Real World Pact developer shell!{reset}\n$(type -p menu &>/dev/null && menu)\n"
135 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/components/AccountModal.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@real-world-pact/theme/components/Button";
2 | import { Fieldset, Input, Label } from "@real-world-pact/theme/components/Form";
3 | import { Text } from "@real-world-pact/theme/components/Text";
4 |
5 | import * as Pact from "pact-lang-api";
6 |
7 | import { Formik } from "formik";
8 | import { useState } from "react";
9 | import { parsePactDecimal } from "@real-world-pact/utils/pact-code";
10 | import { usePactRequest } from "../pact-api";
11 | import * as keth from "../contracts/keth";
12 | import { syncState, useUserStore } from "../state";
13 | import { Box } from "@real-world-pact/theme/components/Container";
14 | import { Balances, BorrowingCapacity } from "./UserDetails";
15 | import { SUCCESS } from "@real-world-pact/utils/pact-request";
16 | import { ControlledModal, FormRequestButton } from "@real-world-pact/theme/components/Modal";
17 |
18 | export interface AccountModalProps {
19 | [x: string]: unknown;
20 | }
21 |
22 | export const AccountModal = ({ ...props }: AccountModalProps) => {
23 | const [open, setOpen] = useState(false);
24 |
25 | const userStore = useUserStore((state) => ({
26 | address: state.address,
27 | keys: state.keys,
28 | keyset: state.keyset,
29 | }));
30 |
31 | const [mintStatus, mint] = usePactRequest((amount: Pact.PactDecimal) =>
32 | keth.mint({
33 | // Recall that the sender is the gas payer — this isn't a transfer.
34 | sender: userStore.address,
35 | senderKeys: userStore.keys,
36 | receiver: userStore.address,
37 | receiverGuard: userStore.keyset,
38 | amount,
39 | })
40 | );
41 |
42 | const description = (
43 |
44 | This is your Charkha account. For the dummy application we have given you the 'sender00'
45 | account that comes with a ton of KDA on devnet. Here, see your current balances. You can also
46 | "bridge" ETH to KETH.
47 |
48 | );
49 |
50 | const trigger = (
51 |
54 | );
55 |
56 | return (
57 |
64 | {
67 | const parsed = parsePactDecimal(values.amount) as Pact.PactDecimal;
68 | const result = await mint(parsed);
69 | if (result.status === SUCCESS) {
70 | await syncState();
71 | setOpen(false);
72 | }
73 | }}
74 | validate={(values) => {
75 | let errors: { amount?: string } = {};
76 | if (values.amount === "") errors.amount = "Required.";
77 | const parsed = parsePactDecimal(values.amount);
78 | if (typeof parsed === "string") errors.amount = parsed;
79 | return errors;
80 | }}
81 | >
82 | {(formik) => (
83 |
107 | )}
108 |
109 |
110 | );
111 | };
112 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "devshell": {
4 | "inputs": {
5 | "nixpkgs": [
6 | "nixpkgs"
7 | ],
8 | "systems": "systems"
9 | },
10 | "locked": {
11 | "lastModified": 1692793255,
12 | "narHash": "sha256-yVyj0AE280JkccDHuG1XO9oGxN6bW8ksr/xttXcXzK0=",
13 | "owner": "numtide",
14 | "repo": "devshell",
15 | "rev": "2aa26972b951bc05c3632d4e5ae683cb6771a7c6",
16 | "type": "github"
17 | },
18 | "original": {
19 | "owner": "numtide",
20 | "repo": "devshell",
21 | "type": "github"
22 | }
23 | },
24 | "flake-compat": {
25 | "flake": false,
26 | "locked": {
27 | "lastModified": 1673956053,
28 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
29 | "owner": "edolstra",
30 | "repo": "flake-compat",
31 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
32 | "type": "github"
33 | },
34 | "original": {
35 | "owner": "edolstra",
36 | "repo": "flake-compat",
37 | "type": "github"
38 | }
39 | },
40 | "flake-compat_2": {
41 | "flake": false,
42 | "locked": {
43 | "lastModified": 1673956053,
44 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
45 | "owner": "edolstra",
46 | "repo": "flake-compat",
47 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
48 | "type": "github"
49 | },
50 | "original": {
51 | "owner": "edolstra",
52 | "repo": "flake-compat",
53 | "type": "github"
54 | }
55 | },
56 | "flake-utils": {
57 | "inputs": {
58 | "systems": "systems_2"
59 | },
60 | "locked": {
61 | "lastModified": 1692799911,
62 | "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
63 | "owner": "numtide",
64 | "repo": "flake-utils",
65 | "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
66 | "type": "github"
67 | },
68 | "original": {
69 | "owner": "numtide",
70 | "repo": "flake-utils",
71 | "type": "github"
72 | }
73 | },
74 | "nixpkgs": {
75 | "locked": {
76 | "lastModified": 1688392541,
77 | "narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=",
78 | "owner": "nixos",
79 | "repo": "nixpkgs",
80 | "rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b",
81 | "type": "github"
82 | },
83 | "original": {
84 | "owner": "nixos",
85 | "ref": "release-22.11",
86 | "repo": "nixpkgs",
87 | "type": "github"
88 | }
89 | },
90 | "pact-nix": {
91 | "inputs": {
92 | "flake-compat": "flake-compat_2",
93 | "nixpkgs": [
94 | "nixpkgs"
95 | ]
96 | },
97 | "locked": {
98 | "lastModified": 1693059327,
99 | "narHash": "sha256-2d+INuNui44aZ9lVHl/HnDYeGiw4hOdm1FCBsEfrbNo=",
100 | "owner": "thomashoneyman",
101 | "repo": "pact-nix",
102 | "rev": "2ff23afbf1e135014d7ce29eaf5493176d4cc21a",
103 | "type": "github"
104 | },
105 | "original": {
106 | "owner": "thomashoneyman",
107 | "repo": "pact-nix",
108 | "type": "github"
109 | }
110 | },
111 | "root": {
112 | "inputs": {
113 | "devshell": "devshell",
114 | "flake-compat": "flake-compat",
115 | "flake-utils": "flake-utils",
116 | "nixpkgs": "nixpkgs",
117 | "pact-nix": "pact-nix"
118 | }
119 | },
120 | "systems": {
121 | "locked": {
122 | "lastModified": 1681028828,
123 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
124 | "owner": "nix-systems",
125 | "repo": "default",
126 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
127 | "type": "github"
128 | },
129 | "original": {
130 | "owner": "nix-systems",
131 | "repo": "default",
132 | "type": "github"
133 | }
134 | },
135 | "systems_2": {
136 | "locked": {
137 | "lastModified": 1681028828,
138 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
139 | "owner": "nix-systems",
140 | "repo": "default",
141 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
142 | "type": "github"
143 | },
144 | "original": {
145 | "owner": "nix-systems",
146 | "repo": "default",
147 | "type": "github"
148 | }
149 | }
150 | },
151 | "root": "root",
152 | "version": 7
153 | }
154 |
--------------------------------------------------------------------------------
/pact-repl-utils/contracts/fungible-v2.pact:
--------------------------------------------------------------------------------
1 | ; The coin-v5 contract implements two interfaces: fungible-v2 and
2 | ; fungible-xchain-v1. To verify the contract implements these interfaces
3 | ; correctly, we need to be able to load their contents. This is a copy of the
4 | ; fungible-v2 interface deployed to Chainweb.
5 | ;
6 | ; https://github.com/kadena-io/chainweb-node/blob/master/pact/coin-contract/v2/fungible-v2.pact
7 | (interface fungible-v2
8 |
9 | " Standard for fungible coins and tokens as specified in KIP-0002. "
10 |
11 | ; ----------------------------------------------------------------------
12 | ; Schema
13 |
14 | (defschema account-details
15 | @doc "Schema for results of 'account' operation."
16 | @model [ (invariant (!= "" sender)) ]
17 |
18 | account:string
19 | balance:decimal
20 | guard:guard)
21 |
22 |
23 | ; ----------------------------------------------------------------------
24 | ; Caps
25 |
26 | (defcap TRANSFER:bool
27 | ( sender:string
28 | receiver:string
29 | amount:decimal
30 | )
31 | @doc " Managed capability sealing AMOUNT for transfer from SENDER to \
32 | \ RECEIVER. Permits any number of transfers up to AMOUNT."
33 | @managed amount TRANSFER-mgr
34 | )
35 |
36 | (defun TRANSFER-mgr:decimal
37 | ( managed:decimal
38 | requested:decimal
39 | )
40 | @doc " Manages TRANSFER AMOUNT linearly, \
41 | \ such that a request for 1.0 amount on a 3.0 \
42 | \ managed quantity emits updated amount 2.0."
43 | )
44 |
45 | ; ----------------------------------------------------------------------
46 | ; Functionality
47 |
48 |
49 | (defun transfer:string
50 | ( sender:string
51 | receiver:string
52 | amount:decimal
53 | )
54 | @doc " Transfer AMOUNT between accounts SENDER and RECEIVER. \
55 | \ Fails if either SENDER or RECEIVER does not exist."
56 | @model [ (property (> amount 0.0))
57 | (property (!= sender ""))
58 | (property (!= receiver ""))
59 | (property (!= sender receiver))
60 | ]
61 | )
62 |
63 | (defun transfer-create:string
64 | ( sender:string
65 | receiver:string
66 | receiver-guard:guard
67 | amount:decimal
68 | )
69 | @doc " Transfer AMOUNT between accounts SENDER and RECEIVER. \
70 | \ Fails if SENDER does not exist. If RECEIVER exists, guard \
71 | \ must match existing value. If RECEIVER does not exist, \
72 | \ RECEIVER account is created using RECEIVER-GUARD. \
73 | \ Subject to management by TRANSFER capability."
74 | @model [ (property (> amount 0.0))
75 | (property (!= sender ""))
76 | (property (!= receiver ""))
77 | (property (!= sender receiver))
78 | ]
79 | )
80 |
81 | (defpact transfer-crosschain:string
82 | ( sender:string
83 | receiver:string
84 | receiver-guard:guard
85 | target-chain:string
86 | amount:decimal
87 | )
88 | @doc " 2-step pact to transfer AMOUNT from SENDER on current chain \
89 | \ to RECEIVER on TARGET-CHAIN via SPV proof. \
90 | \ TARGET-CHAIN must be different than current chain id. \
91 | \ First step debits AMOUNT coins in SENDER account and yields \
92 | \ RECEIVER, RECEIVER_GUARD and AMOUNT to TARGET-CHAIN. \
93 | \ Second step continuation is sent into TARGET-CHAIN with proof \
94 | \ obtained from the spv 'output' endpoint of Chainweb. \
95 | \ Proof is validated and RECEIVER is credited with AMOUNT \
96 | \ creating account with RECEIVER_GUARD as necessary."
97 | @model [ (property (> amount 0.0))
98 | (property (!= sender ""))
99 | (property (!= receiver ""))
100 | (property (!= sender receiver))
101 | (property (!= target-chain ""))
102 | ]
103 | )
104 |
105 | (defun get-balance:decimal
106 | ( account:string )
107 | " Get balance for ACCOUNT. Fails if account does not exist."
108 | )
109 |
110 | (defun details:object{account-details}
111 | ( account: string )
112 | " Get an object with details of ACCOUNT. \
113 | \ Fails if account does not exist."
114 | )
115 |
116 | (defun precision:integer
117 | ()
118 | "Return the maximum allowed decimal precision."
119 | )
120 |
121 | (defun enforce-unit:bool
122 | ( amount:decimal )
123 | " Enforce minimum precision allowed for transactions."
124 | )
125 |
126 | (defun create-account:string
127 | ( account:string
128 | guard:guard
129 | )
130 | " Create ACCOUNT with 0.0 balance, with GUARD controlling access."
131 | )
132 |
133 | (defun rotate:string
134 | ( account:string
135 | new-guard:guard
136 | )
137 | " Rotate guard for ACCOUNT. Transaction is validated against \
138 | \ existing guard before installing new guard. "
139 | )
140 |
141 | )
142 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/contracts/governance.ts:
--------------------------------------------------------------------------------
1 | /*
2 | This module supplies requests for the 'free.charkha-governance' Pact module.
3 | */
4 |
5 | import Pact from "pact-lang-api";
6 | import {
7 | coercePactNumber,
8 | coercePactObject,
9 | coercePactValue,
10 | LocalRequest,
11 | SendRequest,
12 | } from "@real-world-pact/utils/pact-request";
13 | import { AssetName } from "./controller";
14 | import { charkhaKeyPair, sender02Address, sender02KeyPair } from "../constants";
15 |
16 | export const STATUS_OPEN = "OPEN";
17 | export const STATUS_REJECTED = "REJECTED";
18 | export const STATUS_ACCEPTED = "ACCEPTED";
19 |
20 | export interface MarketFactors {
21 | "base-rate": number;
22 | multiplier: number;
23 | "reserve-factor": number;
24 | "collateral-ratio": number;
25 | }
26 |
27 | export type ProposalStatus = "OPEN" | "REJECTED" | "ACCEPTED";
28 |
29 | export type ProposalFactor = "base-rate" | "multiplier" | "reserve-factor" | "collateral-ratio";
30 |
31 | export interface Proposal {
32 | author: string;
33 | market: AssetName;
34 | name: string;
35 | created: Date;
36 | status: ProposalStatus;
37 | proposalFactor: ProposalFactor;
38 | proposalValue: number;
39 | for: string[];
40 | against: string[];
41 | }
42 |
43 | export interface InitMarketArgs {
44 | market: AssetName;
45 | initialFactors: MarketFactors;
46 | }
47 |
48 | export const initMarket = ({ market, initialFactors }: InitMarketArgs): SendRequest => ({
49 | code: {
50 | cmd: "free.charkha-governance.init-market",
51 | args: [market, JSON.stringify(initialFactors)],
52 | },
53 | sender: sender02Address,
54 | gasLimit: 1000,
55 | signers: [
56 | {
57 | ...sender02KeyPair,
58 | clist: [{ name: "coin.GAS", args: [] }],
59 | },
60 | {
61 | ...charkhaKeyPair,
62 | clist: [{ name: "free.charkha-governance.ADMIN", args: [] }],
63 | },
64 | ],
65 | transformResponse: (response) => response as string,
66 | });
67 |
68 | export const getMarketFactors = (market: AssetName): LocalRequest => ({
69 | code: { cmd: `free.charkha-governance.get-market-factors`, args: [market] },
70 | transformResponse: coercePactValue,
71 | });
72 |
73 | export type ProposalId = string;
74 |
75 | export const getProposal = (id: ProposalId): LocalRequest => ({
76 | code: { cmd: `free.charkha-governance.get-proposal`, args: [id] },
77 | transformResponse: (response) => {
78 | const parsed = coercePactObject(response);
79 | return {
80 | name: parsed.name as string,
81 | author: parsed.author as string,
82 | status: parsed.status as ProposalStatus,
83 | market: parsed.market as AssetName,
84 | proposalValue: coercePactNumber(parsed["proposal-value"]),
85 | proposalFactor: parsed["proposal-factor"] as ProposalFactor,
86 | for: parsed.for as string[],
87 | against: parsed.against as string[],
88 | created: new Date((parsed.created as Pact.PactDate).timep),
89 | };
90 | },
91 | });
92 |
93 | export const getProposalIds = (): LocalRequest => ({
94 | code: { cmd: `free.charkha-governance.get-proposal-ids`, args: [] },
95 | transformResponse: coercePactValue,
96 | });
97 |
98 | export interface SubmitProposalArgs {
99 | account: string;
100 | accountKeys: Pact.KeyPair;
101 | name: string;
102 | market: AssetName;
103 | factor: ProposalFactor;
104 | newValue: Pact.PactDecimal;
105 | }
106 |
107 | export const submitProposal = ({
108 | account,
109 | accountKeys,
110 | name,
111 | market,
112 | factor,
113 | newValue,
114 | }: SubmitProposalArgs): SendRequest => ({
115 | code: {
116 | cmd: "free.charkha-governance.submit-proposal",
117 | args: [account, name, market, factor, newValue],
118 | },
119 | sender: account,
120 | gasLimit: 1000,
121 | signers: {
122 | ...accountKeys,
123 | clist: [
124 | { name: "coin.GAS", args: [] },
125 | { name: "free.charkha-governance.VOTE", args: [account] },
126 | ],
127 | },
128 | transformResponse: (response) => response as string,
129 | });
130 |
131 | export interface VoteArgs {
132 | account: string;
133 | accountKeys: Pact.KeyPair;
134 | proposalId: ProposalId;
135 | choice: boolean;
136 | }
137 |
138 | export const vote = ({
139 | account,
140 | accountKeys,
141 | proposalId,
142 | choice,
143 | }: VoteArgs): SendRequest => ({
144 | code: { cmd: "free.charkha-governance.vote", args: [account, proposalId, choice] },
145 | sender: account,
146 | gasLimit: 300,
147 | signers: {
148 | ...accountKeys,
149 | clist: [
150 | { name: "coin.GAS", args: [] },
151 | { name: "free.charkha-governance.VOTE", args: [account] },
152 | ],
153 | },
154 | transformResponse: coercePactValue,
155 | });
156 |
157 | export const closeProposal = (proposalId: string): SendRequest => ({
158 | code: { cmd: "free.charkha-governance.close-proposal", args: [proposalId] },
159 | sender: sender02Address,
160 | gasLimit: 1000,
161 | signers: {
162 | ...sender02KeyPair,
163 | clist: [{ name: "coin.GAS", args: [] }],
164 | },
165 | transformResponse: coercePactValue,
166 | });
167 |
--------------------------------------------------------------------------------
/pact-repl-utils/contracts/fungible-v2-modified.pact:
--------------------------------------------------------------------------------
1 | ; The actual fungible-v2 contract contains incorrect formal verification
2 | ; assertions and results in vacuous property warnings when verifying contracts
3 | ; that rely on it (at least as of Pact 4.6).
4 | ;
5 | ; This version of the fungible-v2 contract is an alternative for local use that
6 | ; won't throw up formal verification errors. See also:
7 | ; https://github.com/kadena-io/pact/issues/1136
8 | ; https://github.com/kadena-io/chainweb-node/pull/1595
9 |
10 | (interface fungible-v2
11 |
12 | " Standard for fungible coins and tokens as specified in KIP-0002. "
13 |
14 | ; ----------------------------------------------------------------------
15 | ; Schema
16 |
17 | (defschema account-details
18 | @doc "Schema for results of 'account' operation."
19 | @model [ (invariant (!= "" sender))
20 | (invariant (>= balance 0.0)) ]
21 |
22 | account:string
23 | balance:decimal
24 | guard:guard)
25 |
26 |
27 | ; ----------------------------------------------------------------------
28 | ; Caps
29 |
30 | (defcap TRANSFER:bool
31 | ( sender:string
32 | receiver:string
33 | amount:decimal
34 | )
35 | @doc " Managed capability sealing AMOUNT for transfer from SENDER to \
36 | \ RECEIVER. Permits any number of transfers up to AMOUNT."
37 | @managed amount TRANSFER-mgr
38 | )
39 |
40 | (defun TRANSFER-mgr:decimal
41 | ( managed:decimal
42 | requested:decimal
43 | )
44 | @doc " Manages TRANSFER AMOUNT linearly, \
45 | \ such that a request for 1.0 amount on a 3.0 \
46 | \ managed quantity emits updated amount 2.0."
47 | )
48 |
49 | ; ----------------------------------------------------------------------
50 | ; Functionality
51 |
52 |
53 | (defun transfer:string
54 | ( sender:string
55 | receiver:string
56 | amount:decimal
57 | )
58 | @doc " Transfer AMOUNT between accounts SENDER and RECEIVER. \
59 | \ Fails if either SENDER or RECEIVER does not exist."
60 | @model [ (property (> amount 0.0))
61 | (property (!= sender ""))
62 | (property (!= receiver ""))
63 | (property (!= sender receiver))
64 | ]
65 | )
66 |
67 | (defun transfer-create:string
68 | ( sender:string
69 | receiver:string
70 | receiver-guard:guard
71 | amount:decimal
72 | )
73 | @doc " Transfer AMOUNT between accounts SENDER and RECEIVER. \
74 | \ Fails if SENDER does not exist. If RECEIVER exists, guard \
75 | \ must match existing value. If RECEIVER does not exist, \
76 | \ RECEIVER account is created using RECEIVER-GUARD. \
77 | \ Subject to management by TRANSFER capability."
78 | @model [ (property (> amount 0.0))
79 | (property (!= sender ""))
80 | (property (!= receiver ""))
81 | (property (!= sender receiver))
82 | ]
83 | )
84 |
85 | (defpact transfer-crosschain:string
86 | ( sender:string
87 | receiver:string
88 | receiver-guard:guard
89 | target-chain:string
90 | amount:decimal
91 | )
92 | @doc " 2-step pact to transfer AMOUNT from SENDER on current chain \
93 | \ to RECEIVER on TARGET-CHAIN via SPV proof. \
94 | \ TARGET-CHAIN must be different than current chain id. \
95 | \ First step debits AMOUNT coins in SENDER account and yields \
96 | \ RECEIVER, RECEIVER_GUARD and AMOUNT to TARGET-CHAIN. \
97 | \ Second step continuation is sent into TARGET-CHAIN with proof \
98 | \ obtained from the spv 'output' endpoint of Chainweb. \
99 | \ Proof is validated and RECEIVER is credited with AMOUNT \
100 | \ creating account with RECEIVER_GUARD as necessary."
101 | )
102 |
103 | (defun get-balance:decimal
104 | ( account:string )
105 | @doc " Get balance for ACCOUNT. Fails if account does not exist."
106 | @model [ (property (!= account "")) ]
107 | )
108 |
109 | (defun details:object{account-details}
110 | ( account: string )
111 | @doc " Get an object with details of ACCOUNT. \
112 | \ Fails if account does not exist."
113 | @model [ (property (!= account "")) ]
114 |
115 | )
116 |
117 | (defun precision:integer
118 | ()
119 | "Return the maximum allowed decimal precision."
120 | )
121 |
122 | (defun enforce-unit:bool
123 | ( amount:decimal )
124 | @doc " Enforce minimum precision allowed for transactions."
125 | @model [ (property (>= amount 0.0)) ]
126 | )
127 |
128 | (defun create-account:string
129 | ( account:string
130 | guard:guard
131 | )
132 | @doc " Create ACCOUNT with 0.0 balance, with GUARD controlling access."
133 | @model [ (property (!= account "")) ]
134 | )
135 |
136 | (defun rotate:string
137 | ( account:string
138 | new-guard:guard
139 | )
140 | @doc " Rotate guard for ACCOUNT. Transaction is validated against \
141 | \ existing guard before installing new guard. "
142 | @model [ (property (!= account "")) ]
143 | )
144 |
145 | )
146 |
--------------------------------------------------------------------------------
/01-faucet-contract/request/send/fund-faucet-account.yaml:
--------------------------------------------------------------------------------
1 | # This transaction can be used to create the Goliath faucet account on an
2 | # instance of devnet where it doesn't yet exist. The account will be guarded by
3 | # the keypair listed in the faucet-keys.yaml file.
4 | #
5 | # We can create an account by transferring funds to it and specifying what
6 | # keyset should guard the account. In the real world you'd likely fund your
7 | # admin account by purchasing KDA on an exchange and transferring it to a new
8 | # address. In our tests, we'll use the 'sender00' account that already exists
9 | # on devnet to transfer funds to our faucet address.
10 | #
11 | # To execute this request:
12 | # faucet-request --send fund-faucet-account --signers sender00
13 | #
14 | # Alternately, to fund the faucet account and then use it to deploy the faucet
15 | # smart contract:
16 | # faucet-deploy
17 |
18 | networkId: "development"
19 | type: exec
20 |
21 | # To create our faucet account we need to use the 'transfer-create' function
22 | # from the coin contract:
23 | # https://github.com/kadena-io/chainweb-node/blob/56e99ae421d2269a657e3bb3780c6d707e5149a0/pact/coin-contract/v5/coin-v5.pact#L358-L362
24 | #
25 | # This function requires a sender address, a receiver address, a keyset for the
26 | # receiver address, and the amount of KDA to transfer. In our case, we will use
27 | # the pre-existing 'sender00' account to transfer funds to our faucet account,
28 | # which will have the address 'goliath-faucet'.
29 | #
30 | # We can choose to either write these values directly into our Pact code, or we
31 | # can use (read-msg) and (read-keyset) to read values from the transaction data.
32 | # It's typical to provide structured data like keysets via the transaction data,
33 | # and simple data like numbers and strings inline in the code. However, this is
34 | # a matter of personal preference.
35 | #
36 | # In this case I've opted to provide all arguments to transfer-create via the
37 | # transaction data.
38 | code: (coin.transfer-create (read-msg "sender") (read-msg "receiver") (read-keyset "receiver-guard") (read-decimal "amount"))
39 |
40 | # Our transaction data will supply each argument that our code is looking up.
41 | data:
42 | sender: "sender00"
43 | receiver: "goliath-faucet"
44 | amount: 1000.0
45 | # To take sensitive actions on behalf of the faucet account, we'll use the
46 | # keyset below to ensure the goliath-faucet.yaml keys were used to sign the
47 | # transaction.
48 | receiver-guard:
49 | keys:
50 | - "550fdb22682e109b207a19b6b0ee0d069007d9a3eccedcb2eb7809f4f56b5ecf"
51 | pred: "keys-all"
52 |
53 | # Next, we'll turn to the transaction signers.
54 | #
55 | # The `coin.transfer-create` function has a call to (with-capability). When you
56 | # see this, it's an indicator that the transaction will need to be signed so the
57 | # capability can be granted. Here's the specific line:
58 | # https://github.com/kadena-io/chainweb-node/blob/56e99ae421d2269a657e3bb3780c6d707e5149a0/pact/coin-contract/v5/coin-v5.pact#L377
59 | #
60 | # The TRANSFER capability takes three arguments: sender, receiver, and amount.
61 | # It is a 'managed' capability, which means that the transaction must be signed
62 | # with the signature scoped to this capability. But how do we know what keys
63 | # must be used to sign this capability?
64 | #
65 | # The sender's signature must be scoped to the TRANSFER capability. We know that
66 | # because the TRANSFER capability is composed of two other capabilities, CREDIT
67 | # and DEBIT, which means signing for TRANSFER grants access to both of these:
68 | # https://github.com/kadena-io/chainweb-node/blob/56e99ae421d2269a657e3bb3780c6d707e5149a0/pact/coin-contract/v5/coin-v5.pact#L66-L73
69 | #
70 | # If you review those capabilities you will see that the CREDIT capability is
71 | # a simple enforcement that the receiver address is not an empty string. The
72 | # DEBIT capability, though, looks up a keyset guard stored in the coin table
73 | # and enforces it. Enforcing a keyset guard for the sender account means that
74 | # this transaction must be signed according to the rules of that keyset.
75 | #
76 | # Therefore, the sender must sign this transaction and scope its signature to
77 | # the TRANSFER capability. In our case the sender is the 'sender00' account that
78 | # already exists on devnet.
79 | signers:
80 | # This is the public key of the 'sender00' account, taken from the
81 | # 'keys/sender00.yaml' file.
82 | - public: 368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca
83 | # We must scope this signature to the TRANSFER capability. The arguments are
84 | # the same sender, receiver, and amount used in our Pact transaction data.
85 | # Also, since this account is paying for the gas associated with this
86 | # transaction, we must sign for the GAS capability as well.
87 | caps:
88 | - name: "coin.TRANSFER"
89 | args: ["sender00", "goliath-faucet", 1000.0]
90 |
91 | - name: "coin.GAS"
92 | args: []
93 |
94 | publicMeta:
95 | chainId: "0"
96 | sender: "sender00"
97 | # This is a reasonable limit for a transfer-create operation. You can measure
98 | # calculate this for yourself by calling 'transfer-create' in a REPL file and
99 | # using the (env-gas) functions to measure gas consumption.
100 | gasLimit: 2000
101 | gasPrice: 0.0000001
102 | ttl: 600
103 |
--------------------------------------------------------------------------------
/pact-repl-utils/init-accounts.repl:
--------------------------------------------------------------------------------
1 | ; A Kadena account pairs an address (such as "k:12345" or "alice") with a keyset
2 | ; (a guard that determines what keys control the account).
3 | ;
4 | ; Accounts can be created on Chainweb using the (coin.create-account) function,
5 | ; or by transferring funds to the account with (coin.transfer-create). Since we
6 | ; don't have any accounts that could transfer funds, we'll rely on the former
7 | ; in this file.
8 | ;
9 | ; That said, in the the real world plenty of accounts exist that can transfer us
10 | ; funds. We might purchase KDA on an exchange and tell the exchange to transfer
11 | ; funds to our KDA address on Chainweb. On our local test blockchain, devnet,
12 | ; there are a set of accounts automatically created and funded for us:
13 | ; https://github.com/kadena-io/chainweb-node/blob/8c32fcfff85c4e5b61a9554f0180ca6c90840e42/pact/genesis/devnet/keys.yaml
14 | ; https://github.com/kadena-io/chainweb-node/blob/8c32fcfff85c4e5b61a9554f0180ca6c90840e42/pact/genesis/devnet/grants0.yaml
15 | ;
16 | ; In our REPL sessions we will mimic devnet by creating the same accounts we
17 | ; expect to see there. That way our REPL test code is also usable in the devnet
18 | ; test environment.
19 |
20 | ; First, let's define the public keys for each account, copied from the
21 | ; 'keys.yaml' file linked above.
22 | (begin-tx)
23 | (module test-keys GOVERNANCE
24 | (defcap GOVERNANCE () true)
25 | (defconst SENDER00 "368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca")
26 | (defconst SENDER01 "6be2f485a7af75fedb4b7f153a903f7e6000ca4aa501179c91a2450b777bd2a7")
27 | (defconst SENDER02 "3a9dd532d73dace195dbb64d1dba6572fb783d0fdd324685e32fbda2f89f99a6")
28 | )
29 | (commit-tx)
30 |
31 | ; Next, let's register the keysets that guard each account so that we can refer
32 | ; to them when creating the accounts, signing transactions, and more.
33 | ;
34 | ; Keysets are defined via the (define-keyset) function. This function takes a
35 | ; name and a keyset as arguments. When evaluated, Pact will either register the
36 | ; keyset at the given name on Chainweb or, if the name is already registered,
37 | ; then it will "rotate" (ie. update) the keyset to the new value.
38 | ; https://pact-language.readthedocs.io/en/stable/pact-functions.html#define-keyset
39 | (env-data { "sender00" : [ test-keys.SENDER00 ], "sender01": [ test-keys.SENDER01 ], "sender02": [ test-keys.SENDER02 ] })
40 | (begin-tx)
41 | (namespace "free")
42 | (define-keyset "free.sender00-keyset" (read-keyset "sender00"))
43 | (define-keyset "free.sender01-keyset" (read-keyset "sender01"))
44 | (define-keyset "free.sender02-keyset" (read-keyset "sender02"))
45 | (commit-tx)
46 |
47 | (print "Registered keysets 'sender00-keyset', 'sender01-keyset', and 'sender02-keyset'.")
48 | (env-data {})
49 |
50 | ; Next, we'll use these keysets to create our accounts. This requires that we
51 | ; have access to the 'coin' contract, which manages the KDA token; that must
52 | ; have been loaded into the REPL before this file executes.
53 | ;
54 | ; Accounts can be created on Chainweb using the (coin.create-account) function,
55 | ; or by transferring funds to the account with (coin.transfer-create). In our
56 | ; case we don't have any funds to transfer, so we'll use (coin.create-account)
57 | (begin-tx)
58 | (coin.create-account "sender00" (describe-keyset "free.sender00-keyset"))
59 | (coin.create-account "sender01" (describe-keyset "free.sender01-keyset"))
60 | (coin.create-account "sender02" (describe-keyset "free.sender02-keyset"))
61 | (commit-tx)
62 | (print "Created accounts 'sender00', 'sender01', and 'sender02'.")
63 |
64 | ; Finally, we need to ensure these accounts have some funds. On Chainweb, you
65 | ; can get funds from one of two places: someone transfers you KDA, or you mine
66 | ; the KDA yourself. We don't have access to either source of funding in the
67 | ; REPL, but we can simulate mining KDA by referring again to the coin contract.
68 | ;
69 | ; The coin contract includes a (coinbase) function that is used to reward miners
70 | ; by minting KDA and distributing it to them when they mine a block. We can't
71 | ; call this function on Chainweb because it is protected by the COINBASE
72 | ; capability:
73 | ; https://github.com/kadena-io/chainweb-node/blob/4dc69750eeffbf6eb2ce901b3d951534cc98e9be/pact/coin-contract/coin.pact#L324
74 | ;
75 | ; A function protected by a call to (require-capability) cannot be called from
76 | ; outside the module, because capabilities can only be granted within the module
77 | ; that defined the capability. However, in the REPL environment we have access
78 | ; to a special function to magically grant ourselves a capability:
79 | ; https://pact-language.readthedocs.io/en/latest/pact-functions.html#test-capability
80 | ;
81 | ; Let's use it to give ourselves the COINBASE capability.
82 | (begin-tx)
83 | (test-capability (coin.COINBASE))
84 |
85 | ; Finally, it's time to fund our accounts.
86 | (coin.coinbase "sender00" (describe-keyset "free.sender00-keyset") 100000.0)
87 | (coin.coinbase "sender01" (describe-keyset "free.sender01-keyset") 100000.0)
88 | (coin.coinbase "sender02" (describe-keyset "free.sender02-keyset") 100000.0)
89 | (commit-tx)
90 | (print "Funded sender* accounts each with 100,000.0 KDA.")
91 |
92 | ; Voila! We can now transfer funds from the three sender accounts to create
93 | ; and fund other accounts, or use these accounts as basic test accounts. Code
94 | ; that refers to sender00, sender01, and sender02 will also be usable when
95 | ; testing on devnet, as the accounts are also present there.
96 |
--------------------------------------------------------------------------------
/03-charkha-lending/contracts/oracle/oracle.pact:
--------------------------------------------------------------------------------
1 | ; Our primitive oracle contract records USD price data from the CoinMarketCap
2 | ; API every few minutes. You can think of this contract as a public price
3 | ; database that anyone on the blockchain can read from, but which only a trusted
4 | ; administrator (Charkha) can write to.
5 | (namespace "free")
6 |
7 | (enforce-guard (keyset-ref-guard "free.charkha-admin-keyset"))
8 |
9 | (module charkha-oracle GOVERNANCE
10 | @doc "'charkha-oracle' represents the Charkha Oracle. This contract provides \
11 | \access to USD prices for a set of supported tokens. Prices are updated every\
12 | \few minutes by the Charkha admin account according to CoinMarketCap. It is \
13 | \suitable for test environments only."
14 |
15 | ; We record the current price in USD for each asset Charkha supports. We can
16 | ; add more assets later if we see fit. While it isn't necessary, I've also
17 | ; decided to record the last updated time for each asset so it's possible to
18 | ; see if price data is stale.
19 | ;
20 | ; We will key the table by normal asset symbols like KDA instead of the ids
21 | ; CoinMarketCap uses so that it's easier to understand. But in the real world
22 | ; this would be insufficient.
23 | (defschema asset
24 | @doc "The Charkha oracle contract assets schema. Keyed by asset symbol, e.g. KDA."
25 | @model [ (invariant (>= usd-price 0.0)) ]
26 | usd-price:decimal
27 | last-updated:time)
28 |
29 | (deftable asset-table:{asset})
30 |
31 | (defcap GOVERNANCE ()
32 | (enforce-guard (keyset-ref-guard "free.charkha-admin-keyset")))
33 |
34 | ; We're going to lean on this capability to prevent anyone except the Charkha
35 | ; admins from modifying the asset table.
36 | (defcap ADMIN ()
37 | @doc "A guard for admin-only actions, restricted to the free.charkha-admin-keyset keyset."
38 | (enforce-guard (keyset-ref-guard "free.charkha-admin-keyset")))
39 |
40 | (defun register-asset:string (symbol:string usd-price:decimal)
41 | @doc "Register a new asset in the oracle database (admin-only)."
42 | @model
43 | [ (property (authorized-by "free.charkha-admin-keyset"))
44 | ; It should not be possible to overwrite an asset. To verify this, we
45 | ; verify that we never insert an asset that already existed.
46 | ; https://pact-language.readthedocs.io/en/stable/pact-properties-api.html#row-exists
47 | (property (= false (row-exists asset-table symbol "before")))
48 | ; USD prices must always be non-negative.
49 | (property (>= usd-price 0.0))
50 | ]
51 |
52 | (enforce (>= usd-price 0.0) "Cannot register an asset with a negative USD price.")
53 |
54 | (with-capability (ADMIN)
55 | ; We can get the current time by reading the chain data:
56 | ; https://pact-language.readthedocs.io/en/stable/pact-functions.html#chain-data
57 | ;
58 | ; and retrieving the "block-time" field from the resulting object using "at":
59 | ; https://pact-language.readthedocs.io/en/stable/pact-functions.html#at
60 | ;
61 | ; This will produce a value of type "time" in the standard time format:
62 | ; https://pact-language.readthedocs.io/en/stable/pact-reference.html#time-formats
63 | (let ((current-time:time (at 'block-time (chain-data))))
64 | ; This line is a little tricky. We do not want to let you overwrite an
65 | ; existing entry in the database, which can happen with (write):
66 | ; https://pact-language.readthedocs.io/en/stable/pact-functions.html#write
67 | ;
68 | ; Instead, we'll use (insert), which will fail if the key already exists.
69 | ; https://pact-language.readthedocs.io/en/stable/pact-functions.html#insert
70 | (insert asset-table symbol
71 | { "usd-price": usd-price
72 | , "last-updated": current-time
73 | }))))
74 |
75 | ; This is the primary function in the module: the ability to set the price for
76 | ; a registered asset. This is separated from register-asset in case we want to
77 | ; make registering an asset something CHRK token holders can vote on; we will
78 | ; only ever let the Charkha admin set prices.
79 | (defun set-price:string (symbol:string usd-price:decimal)
80 | @doc "Set the USD price of the given asset (admin-only)."
81 | @model
82 | [ (property (authorized-by "free.charkha-admin-keyset"))
83 | (property (>= usd-price 0.0))
84 | ]
85 |
86 | (enforce (>= usd-price 0.0) "Cannot set an asset to a negative USD price.")
87 | (with-capability (ADMIN)
88 | (let ((current-time:time (at 'block-time (chain-data))))
89 | (update asset-table symbol { "usd-price": usd-price, "last-updated": current-time }))))
90 |
91 | ; The functions from this point onwards are open to anyone and allow read-only
92 | ; access to the assets table.
93 | (defun get-assets:[string] ()
94 | @doc "List all assets with prices recorded by the oracle, e.g. [ 'KDA', 'CHRK', ... ]."
95 | (keys asset-table))
96 |
97 | (defun get-asset:object{asset} (symbol:string)
98 | @doc "Read the USD price and last updated time of the given asset."
99 | (read asset-table symbol))
100 |
101 | (defun get-price:decimal (symbol:string)
102 | @doc "Read the USD price of the given asset."
103 | (with-read asset-table symbol { "usd-price" := usd-price } usd-price))
104 | )
105 |
106 | (if (read-msg "init")
107 | (create-table free.charkha-oracle.asset-table)
108 | "Upgrade complete")
109 |
--------------------------------------------------------------------------------
/pact-api-utils/pact-request-hooks.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This file implements several hooks for managing requests to a Chainweb node. Each
4 | hook should be configured with your PactAPI setup:
5 |
6 | ```ts
7 | const pactAPI = new PactAPI(...) // your configuration
8 | const useRequest = makeUsePactRequest(pactAPI);
9 | ```
10 |
11 | Each of these hooks takes a function ((args: a) => PactRequest), which is a
12 | function from some input data to a request that can be sent to a Chainweb node.
13 | In return, each hook gives you:
14 |
15 | 1. A piece of state tracking the request status(es) of your given request.
16 | 2. A function for making a new request, given the arguments the request needs.
17 |
18 | As a brief example, here's how to track the status of a (coin.details) request:
19 |
20 | ```ts
21 | type DetailsArgs = string;
22 |
23 | interface DetailsResult {
24 | account: string;
25 | balance: number;
26 | guard: Pact.KeySet;
27 | }
28 |
29 | const detailsRequest = (address: DetailsArgs): LocalRequest => ({
30 | code: { cmd: "coin.details", args: [address] },
31 | transformResponse: coercePactValue
32 | });
33 |
34 | const pactAPI = new PactAPI();
35 | const useRequest = makeUsePactRequest(pactAPI);
36 |
37 | const App = () => {
38 | // Using the usePactRequest hook we now get access to the status of the request
39 | // in state and a function to execute the request when given its input.
40 | const [details, fetchDetails] = useRequest(detailsRequest);
41 |
42 | // We can now easily use both the state and the request in our UI.
43 | return (
44 | {details}
45 |
46 | );
47 | }
48 | ```
49 | */
50 |
51 | import { useEffect, useState } from "react";
52 | import {
53 | isSendRequest,
54 | PactAPI,
55 | PactAPIConfig,
56 | PactRequest,
57 | Pending,
58 | PENDING,
59 | RequestResult,
60 | RequestStatus,
61 | } from "./pact-request";
62 |
63 | // A Hook that returns a request status and a function to send a new request.
64 | export const makeUsePactRequest = (api: PactAPI) => {
65 | return (
66 | mkRequest: (args: a) => PactRequest,
67 | config?: Partial
68 | ): [null | RequestStatus, (args: a) => Promise>] => {
69 | const [status, setStatus] = useState>(null);
70 |
71 | const fetch = (args: a) => {
72 | const request = mkRequest(args);
73 | if (isSendRequest(request)) {
74 | return api.sendWithCallback(request, setStatus, config);
75 | } else {
76 | return api.localWithCallback(request, setStatus, config);
77 | }
78 | };
79 |
80 | return [status, fetch];
81 | };
82 | };
83 |
84 | // A Hook that immediately sends a request and returns its status and a function
85 | // to send it again.
86 | export const makeUseImmediatePactRequest = (api: PactAPI) => {
87 | return (request: PactRequest, config?: Partial): RequestStatus => {
88 | const initialState = (): Pending => {
89 | if (isSendRequest(request)) {
90 | return {
91 | status: PENDING,
92 | request: api.format(request, request.sender, request.gasLimit, config),
93 | };
94 | } else {
95 | return {
96 | status: PENDING,
97 | request: api.format(request, "", 150_000, config),
98 | };
99 | }
100 | };
101 |
102 | const [status, setStatus] = useState>(initialState());
103 |
104 | useEffect(() => {
105 | const run = async () => {
106 | if (isSendRequest(request)) {
107 | await api.sendWithCallback(request, setStatus, config);
108 | } else {
109 | await api.localWithCallback(request, setStatus, config);
110 | }
111 | };
112 | run();
113 | }, []);
114 |
115 | return status;
116 | };
117 | };
118 |
119 | export type UsePactRequestAllChainsResult = [RequestStatus[], () => void];
120 |
121 | // A Hook that will execute the given request on all chains and store the 20
122 | // request statuses in an array in state. The array is initialized empty, so
123 | // indexing into it will return null if the request has not been sent.
124 | export const makeUsePactRequestAllChains = (api: PactAPI) => {
125 | return (
126 | mkRequest: (args: a) => PactRequest,
127 | config?: Partial
128 | ): [RequestStatus[], (args: a) => Promise] => {
129 | const [statuses, setStatuses] = useState[]>([]);
130 |
131 | const handleStatusChange = (newStatus: RequestStatus, index: number) => {
132 | setStatuses((oldStatuses) => {
133 | const copy = Array.from(oldStatuses);
134 | copy[index] = newStatus;
135 | return copy;
136 | });
137 | };
138 |
139 | const fetch = async (args: a) => {
140 | const request = mkRequest(args);
141 | await Promise.all(
142 | Array.from({ length: 20 }, async (_, index) => {
143 | if (isSendRequest(request)) {
144 | return api.sendWithCallback(request, (status) => handleStatusChange(status, index), {
145 | ...config,
146 | chainId: index.toString(),
147 | });
148 | } else {
149 | return api.localWithCallback(request, (status) => handleStatusChange(status, index), {
150 | ...config,
151 | chainId: index.toString(),
152 | });
153 | }
154 | })
155 | );
156 | };
157 |
158 | return [statuses, fetch];
159 | };
160 | };
161 |
--------------------------------------------------------------------------------
/pact-api-utils/types/pact-lang-api.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | The pact-lang-api library is written in JavaScript, but we'd like to have types
4 | available to guide our development. This file implements types for v4.3.5.
5 |
6 | Note: These types only cover the functions and data we are using from the
7 | pact-lang-api library. The library includes more than you see here, but this is
8 | enough for Goliath!
9 |
10 | Library source:
11 | https://github.com/kadena-io/pact-lang-api
12 |
13 | */
14 |
15 | declare module "pact-lang-api" {
16 | // Pact can represent huge integers and decimal values, but it has the
17 | // restriction that particularly large values are stringified, and decimals
18 | // must have a decimal point.
19 | interface PactInt {
20 | int: string;
21 | }
22 |
23 | interface PactDate {
24 | timep: string;
25 | }
26 |
27 | interface PactDecimal {
28 | decimal: string;
29 | }
30 |
31 | interface PactReference {
32 | ref: string;
33 | }
34 |
35 | type PactValue =
36 | | string
37 | | PactInt
38 | | PactDecimal
39 | | boolean
40 | | PactValue[]
41 | | PactReference
42 | | PactDate
43 | | { [key: string]: PactValue };
44 |
45 | interface KeyPair {
46 | publicKey: string;
47 | secretKey: string;
48 | }
49 |
50 | interface KeySet {
51 | keys: string[];
52 | pred: string;
53 | }
54 |
55 | interface KeyPairCapabilities extends KeyPair {
56 | clist?: Capability[];
57 | }
58 |
59 | interface Capability {
60 | name: string;
61 | args: PactValue[];
62 | }
63 |
64 | interface TransactionMetadata {
65 | creationTime: number;
66 | ttl: number;
67 | gasLimit: number;
68 | gasPrice: number;
69 | chainId: string;
70 | sender: string;
71 | }
72 |
73 | // The network to target. For devnet, use "development".
74 | type NetworkId = "mainnet01" | "testnet" | "development";
75 |
76 | interface ExecCmd {
77 | networkId: NetworkId;
78 | type?: "exec" | "cont";
79 | keyPairs?: KeyPairCapabilities | KeyPairCapabilities[];
80 | nonce?: string;
81 | envData?: { [key: string]: PactValue | KeySet };
82 | pactCode: string;
83 | meta: TransactionMetadata;
84 | }
85 |
86 | interface PollCmd {
87 | requestKeys: string[];
88 | }
89 |
90 | interface ListenCmd {
91 | listen: string; // request key
92 | }
93 |
94 | /*
95 | Miscellaneous helper types for requests
96 | */
97 |
98 | interface ExecResponseMetadata {
99 | blockHeight: number;
100 | blockTime: number;
101 | prevBlockHash: string;
102 | }
103 |
104 | interface LocalResponseMetadata extends ExecResponseMetadata {
105 | publicMeta: TransactionMetadata;
106 | }
107 |
108 | interface TransactionEvent {
109 | module: { namespace: string | null; name: string };
110 | moduleHash: string;
111 | name: string;
112 | params: string[];
113 | }
114 |
115 | // The contents of the "result" field when Pact execution failed.
116 | interface FailureResult {
117 | status: "failure";
118 | error: {
119 | callStack: string[];
120 | type: string;
121 | message: string;
122 | info: string;
123 | };
124 | }
125 |
126 | // The contents of the "result" field when Pact execution succeeded.
127 | interface SuccessResult {
128 | status: "success";
129 | data: PactValue;
130 | }
131 |
132 | interface ExecResponseImpl {
133 | continuation: null;
134 | events: TransactionEvent[] | null;
135 | gas: number;
136 | metaData: ExecResponseMetadata;
137 | logs: string;
138 | reqKey: string;
139 | txId: number | null;
140 | }
141 |
142 | interface FailedExecResponse extends ExecResponseImpl {
143 | result: FailureResult;
144 | }
145 |
146 | interface SuccessExecResponse extends ExecResponseImpl {
147 | result: SuccessResult;
148 | }
149 |
150 | type ExecResponse = FailedExecResponse | SuccessExecResponse;
151 |
152 | /* The /local endpoint */
153 | interface LocalResponseImpl {
154 | continuation: null;
155 | gas: number;
156 | metaData: LocalResponseMetadata;
157 | logs: string;
158 | reqKey: string;
159 | txId: number | null;
160 | }
161 |
162 | interface FailedLocalResponse extends LocalResponseImpl {
163 | result: FailureResult;
164 | }
165 |
166 | interface SuccessLocalResponse extends LocalResponseImpl {
167 | result: SuccessResult;
168 | }
169 |
170 | type LocalResponse = FailedLocalResponse | SuccessLocalResponse;
171 |
172 | /* The /send endpoint */
173 | interface SendResponse {
174 | requestKeys: string[];
175 | }
176 |
177 | /* The /poll endpoint */
178 | type PollResponse = {
179 | [requestKey: string]: ExecResponse;
180 | };
181 |
182 | /*
183 | Finally, we reach the concrete values exported by pact-lang-api. The interface
184 | and exports here exactly match the exports we are using from the library, so
185 | we can use those functions exactly as they would be used in JavaScript.
186 | However, we now have types to help guide our development:
187 | https://github.com/kadena-io/pact-lang-api/blob/666ab149d0db5e0360d6ff1206bd6dce45ded07a/pact-lang-api.js#L842
188 | */
189 | interface ICrypto {
190 | genKeyPair: () => KeyPair;
191 | }
192 |
193 | const crypto: ICrypto;
194 |
195 | interface IFetch {
196 | local: (cmd: ExecCmd, apiHost: string) => Promise;
197 | send: (cmd: ExecCmd | ExecCmd[], apiHost: string) => Promise;
198 | poll: (cmd: PollCmd, apiHost: string) => Promise;
199 | }
200 |
201 | const fetch: IFetch;
202 | }
203 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/App.tsx:
--------------------------------------------------------------------------------
1 | /* APP
2 |
3 | This component is the main entrypoint for our app. Since the application is
4 | small, the important app state (ie. various requests to the Chainweb node such
5 | as funds requests) will be stored in this component. You can use it as a good
6 | example of how to use the pact-utils helper library to make requests to Chainweb.
7 |
8 | */
9 |
10 | import React, { useEffect } from "react";
11 |
12 | import * as Pact from "pact-lang-api";
13 |
14 | import { Box, Container, Flex } from "@real-world-pact/theme/components/Container";
15 | import { GlobalStyles } from "@real-world-pact/theme/components/GlobalStyles";
16 | import { GoliathLogo, Navbar } from "@real-world-pact/theme/components/Navbar";
17 |
18 | import * as coin from "./contracts/coin-v5";
19 | import * as faucet from "./contracts/goliath-faucet";
20 |
21 | import { userAccount, userKeySet } from "./accounts";
22 |
23 | import { AdminModal } from "./components/AdminModal";
24 | import { RequestFundsModal } from "./components/RequestFundsModal";
25 | import { ReturnFundsModal } from "./components/ReturnFundsModal";
26 | import { AccountOverview } from "./components/AccountOverview";
27 | import { Transactions, useTransactions } from "./components/Transactions";
28 | import { usePactRequest, usePactRequestAllChains } from "./pact-api";
29 | import { SUCCESS } from "@real-world-pact/utils/pact-request";
30 |
31 | const App = () => {
32 | // First we will track the request and account limits associated with our user
33 | // account, as far as the faucet is concerned.
34 | const [limits, runGetLimits] = usePactRequest(faucet.getLimits);
35 |
36 | // Next, we'll use the (coin.details) function to look up our account's
37 | // balances. While we only need to look up account limits on Chain 0 (where
38 | // the faucet is deployed), we want to look up account balances on all chains.
39 | const [details, runDetails] = usePactRequestAllChains(coin.details);
40 |
41 | // Finally, we'll record any funds transfers we make in the UI using the
42 | // component, which gives us a hook for collecting requests
43 | // together into one list.
44 | const [transactions, newTransaction] = useTransactions();
45 |
46 | // After a successful transfer we'll want to refresh the user's balances and
47 | // remaining account limits.
48 | const refreshUserData = async () => {
49 | await runDetails({ address: userAccount.address });
50 | await runGetLimits({ address: userAccount.address });
51 | };
52 |
53 | // We will always request funds and return funds on behalf of the user account
54 | // so we can specialize our two functions to make them easier to use. We'll
55 | // also refresh the user data after a successful transfer.
56 | const userRequestFunds = async (amount: Pact.PactDecimal) => {
57 | const input = { targetAddress: userAccount.address, targetAddressKeySet: userKeySet, amount };
58 | const result = await newTransaction(
59 | faucet.requestFunds(input),
60 | amount,
61 | faucet.FAUCET_ACCOUNT,
62 | input.targetAddress
63 | );
64 | if (result.status === SUCCESS) {
65 | await refreshUserData();
66 | }
67 | };
68 |
69 | const userReturnFunds = async (amount: Pact.PactDecimal) => {
70 | const input = { account: userAccount.address, accountKeys: userAccount.keys, amount };
71 | const result = await newTransaction(
72 | faucet.returnFunds(input),
73 | amount,
74 | input.account,
75 | faucet.FAUCET_ACCOUNT
76 | );
77 | if (result.status === SUCCESS) {
78 | await refreshUserData();
79 | }
80 | };
81 |
82 | // All right! Our requests are all set up and tidy; we can now easy make
83 | // various calls to Chainweb and use their results in our UI. Let's kick
84 | // things off by requesting some KDA from the faucet on the user's behalf.
85 | useEffect(() => {
86 | (async () => {
87 | // First we'll load the user's balances so that they can immediately see
88 | // how much KDA they have.
89 | await runDetails({ address: userAccount.address });
90 | // Then we'll request 15 KDA from the faucet. On app load the user will
91 | // see this transaction in the UI along with its status.
92 | await userRequestFunds({ decimal: "20.0" });
93 | })();
94 | }, []);
95 |
96 | // Our wallet UI is made up of three primary sections: the navbar, the account
97 | // overview, and the transactions.
98 | return (
99 |
100 |
101 | }>
102 |
103 | {
106 | await runGetLimits({ address: userAccount.address });
107 | }}
108 | />
109 | {
112 | await userRequestFunds(amount);
113 | }}
114 | />
115 | {
117 | await userReturnFunds(amount);
118 | }}
119 | />
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | );
129 | };
130 |
131 | export default App;
132 |
--------------------------------------------------------------------------------
/03-charkha-lending/frontend/src/components/ControllerModal.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@real-world-pact/theme/components/Button";
2 | import { Fieldset, Input, Label } from "@real-world-pact/theme/components/Form";
3 | import { Link, Text } from "@real-world-pact/theme/components/Text";
4 |
5 | import * as Pact from "pact-lang-api";
6 |
7 | import { Formik } from "formik";
8 | import { useState } from "react";
9 | import { parsePactDecimal } from "@real-world-pact/utils/pact-code";
10 | import { usePactRequest } from "../pact-api";
11 | import { syncState, useUserStore } from "../state";
12 | import { AssetName } from "../contracts/controller";
13 |
14 | import * as controller from "../contracts/controller";
15 | import { Balances, BorrowingCapacity } from "./UserDetails";
16 | import { SUCCESS } from "@real-world-pact/utils/pact-request";
17 | import { ControlledModal, FormRequestButton } from "@real-world-pact/theme/components/Modal";
18 |
19 | type Action = "BORROW" | "SUPPLY" | "REPAY" | "REDEEM";
20 |
21 | export interface ControllerModalProps {
22 | action: Action;
23 | market: AssetName;
24 | [x: string]: unknown;
25 | }
26 |
27 | export const ControllerModal = ({ action, market, ...props }: ControllerModalProps) => {
28 | const [open, setOpen] = useState(false);
29 |
30 | const userStore = useUserStore((state) => ({
31 | address: state.address,
32 | keys: state.keys,
33 | keyset: state.keyset,
34 | }));
35 |
36 | const mode = (() => {
37 | switch (action) {
38 | case "BORROW":
39 | return {
40 | request: (amount: Pact.PactDecimal) =>
41 | controller.borrow({
42 | // Recall that the sender is the gas payer — this isn't a transfer.
43 | account: userStore.address,
44 | accountKeys: userStore.keys,
45 | accountKeySet: userStore.keyset,
46 | market,
47 | tokens: amount,
48 | }),
49 | title: "Borrow",
50 | action: "borrow from",
51 | };
52 |
53 | case "SUPPLY":
54 | return {
55 | request: (amount: Pact.PactDecimal) =>
56 | controller.supply({
57 | account: userStore.address,
58 | accountKeys: userStore.keys,
59 | market,
60 | amount,
61 | }),
62 | title: "Supply",
63 | action: "supply to",
64 | };
65 |
66 | case "REPAY":
67 | return {
68 | request: (amount: Pact.PactDecimal) =>
69 | controller.repay({
70 | account: userStore.address,
71 | accountKeys: userStore.keys,
72 | market,
73 | amount,
74 | }),
75 | title: "Repay",
76 | action: "repay in",
77 | };
78 |
79 | case "REDEEM":
80 | return {
81 | request: (tokens: Pact.PactDecimal) =>
82 | controller.redeem({
83 | account: userStore.address,
84 | accountKeys: userStore.keys,
85 | market,
86 | tokens,
87 | }),
88 | title: "Redeem",
89 | action: "redeem to",
90 | };
91 | }
92 | })();
93 |
94 | const [requestStatus, request] = usePactRequest(mode.request);
95 |
96 | const description = (
97 |
98 | Below, specify how much you would like to {mode.action} the {market} market.
99 |
100 | );
101 |
102 | const trigger =
103 | action === "BORROW" ? (
104 |
107 | ) : action === "SUPPLY" ? (
108 |
111 | ) : (
112 | {mode.title}
113 | );
114 |
115 | return (
116 |
123 | {
126 | const parsed = parsePactDecimal(values.amount) as Pact.PactDecimal;
127 | const result = await request(parsed);
128 | if (result.status === SUCCESS) {
129 | await syncState();
130 | setOpen(false);
131 | }
132 | }}
133 | validate={(values) => {
134 | let errors: { amount?: string } = {};
135 | if (values.amount === "") errors.amount = "Required.";
136 | const parsed = parsePactDecimal(values.amount);
137 | if (typeof parsed === "string") errors.amount = parsed;
138 | return errors;
139 | }}
140 | >
141 | {(formik) => (
142 |
166 | )}
167 |
168 |
169 | );
170 | };
171 |
--------------------------------------------------------------------------------
/02-goliath-wallet/README.md:
--------------------------------------------------------------------------------
1 | # Goliath Wallet Project
2 |
3 | The Goliath wallet is a TypeScript + React frontend that interacts with Kadena's Chainweb blockchain. It is a minimal but complete frontend application, intended for developers who have built a React application before but are newcomers to Pact and Chainweb. This project builds on the [smart contract we developed in Project 1](../01/faucet-contract/) and demonstrates how to:
4 |
5 | - Use the [pact-lang-api](https://github.com/kadena-io/pact-lang-api) library to send transactions to a Chainweb node for execution and read the results.
6 | - Query our local Chainweb node for information on one chain or across all 20 chains (for example, to retrieve an account's balance on all chains).
7 | - Write an interface for interacting with the faucet smart contract we developed in Project 1
8 |
9 | Goliath is a fully-functioning frontend; I encourage you to run it yourself, make tweaks, and view the results! If you are using the provided Nix shell, then you can use the following commands to run Goliath:
10 |
11 | ```sh
12 | # Enter the Nix shell (use nix-shell if your Nix installation does not support flakes)
13 | nix develop
14 |
15 | # Start the simulation blockchain (run devnet-stop to stop the simulation)
16 | devnet-start
17 |
18 | # Fund the faucet account and deploy the faucet contract
19 | faucet-deploy
20 |
21 | # Start the Goliath application (Ctrl+C to exit)
22 | goliath-start
23 | ```
24 |
25 | This project deals only with the frontend part of building a dapp on Chainweb. I highly recommend that you spend time with [the smart contract project](../01-faucet-contract) before moving on to this one, as this project interacts with the faucet contract we developed.
26 |
27 | ## Wallet Overview
28 |
29 | The Goliath wallet is a simple test wallet for Kadena. It assumes you have `devnet` running and have deployed the faucet contract; this frontend makes requests to the faucet contract on your local Chainweb node, just the same way that you'll make requests to a production Chainweb node when you develop your own applications.
30 |
31 | ### Features
32 |
33 | The Goliath wallet is a simple test wallet for sending and receiving KDA. When you load Goliath in your browser you will be given a new wallet address with no funds. Then, the wallet will request 15 KDA on your behalf from the faucet contract.
34 |
35 | 
36 |
37 | After that, you can begin to transfer funds using your wallet! The wallet UI is made up of three sections: the navbar, account details, and transaction details.
38 |
39 | The navbar provides three actions you can take:
40 |
41 | - The "Admin" action allows you to act as the faucet account and update how the faucet contract works (ie. raise or lower the per-request and per-account limits).
42 | - The "Receive Funds" action allows you to request funds from the faucet account to your account on Chain 0.
43 | - The "Send Funds" action allows you to transfer funds from your account on Chain 0 to another chain or back to the faucet account.
44 |
45 | The account details section displays your account and KDA holdings across all chains. Each time a transfer is processed your balances are refreshed via a call to the `details` function from the foundational `coin` contract.
46 |
47 | Finally, the transaction details section lists each transaction associated with your account. When you initiate a transaction it will be added to the list, and then it will result in a success or failure result and render the associated data from the response.
48 |
49 | ### Development
50 |
51 | You can use the command below to start the wallet frontend in development mode. Feel free to make changes to the source code – the UI will hot-reload with your changes. Remember: this will only work if you are running devnet!
52 |
53 | ```sh
54 | pnpm run dev
55 | ```
56 |
57 | You may wish to open the developer tools with the console and/or network tabs open so that you can see the various requests, responses, and logs we receive from our local Chainweb node.
58 |
59 | ## Project Structure
60 |
61 | The code specific to our wallet is in the `src` directory:
62 |
63 | - `App.tsx` is our application. It initializes data, stores the app state, and assembles our UI from our theme components.
64 | - `contracts` contains TypeScript implementations of our faucet smart contract and some of the `coin-v5` contract. You should read this file and the [faucet smart contract it describes](../01-faucet-contract/faucet.pact) side-by-side. You can also compare the requests to the [request files](../01-faucet-contract/yaml) from Project 1, so you can see how the same requests translate to the request builder.
65 | - `components` contains a few Goliath-specific components, such as the modals for setting account limits, requesting funds, and returning funds.
66 | - `config.ts` contains configuration specific to our application, such as the network ID and chain that our requests will target and the keypairs for various accounts our wallet will control.
67 | - `accounts.ts` contains the account details (addresses, public and private keys) for accounts used by the wallet. In the real world **never** commit your private keys; these are included for demonstration only.
68 |
69 | There is also some code not stored in this directory because it is used both for the wallet and for the [Charkha lending protocol project](../03-charkha-lending/). Specifically:
70 |
71 | - [`pact-api-utils`](../pact-api-utils/) is a helper library built on top of `pact-lang-api` that provides TypeScript definitions for `pact-lang-api`, helpers for writing Pact code in JavaScript, a request builder module that makes it easy to build requests for Chainweb, and a collection of React Hooks for integrating requests into your UI. Feel free to use this code in your own projects!
72 | - [`theme`](../theme) contains the various UI components used to build both frontends. These are typical frontend code and have nothing to do with Pact or Chainweb specifically. This code is not commented.
73 |
--------------------------------------------------------------------------------
/02-goliath-wallet/src/components/AdminModal.tsx:
--------------------------------------------------------------------------------
1 | /* ADMIN MODAL
2 |
3 | The admin modal lets the user act as the faucet administrator account and raise
4 | their per-request and/or per-account limits. This file is standard React; there
5 | isn't Pact-specific code here except for the requests made to set the limits.
6 |
7 | */
8 |
9 | import { useState } from "react";
10 | import { Formik } from "formik";
11 |
12 | import { Fieldset, Input, Label } from "@real-world-pact/theme/components/Form";
13 | import { Link, Text } from "@real-world-pact/theme/components/Text";
14 |
15 | import { parsePactDecimal } from "@real-world-pact/utils/pact-code";
16 | import { userAccount } from "../accounts";
17 | import * as faucet from "../contracts/goliath-faucet";
18 | import { usePactRequest } from "../pact-api";
19 | import { FormRequestButton, ControlledModal } from "@real-world-pact/theme/components/Modal";
20 |
21 | interface AdminModalProps {
22 | onSuccess: () => Promise;
23 | [x: string]: unknown;
24 | }
25 |
26 | // The admin modal lets you change the current per-request or per-account limits
27 | // for the user account. Most of this code is typical React code, so I've left
28 | // out the comments. However, you can see another use of 'useRequest' below.
29 | export const AdminModal = ({ onSuccess, ...props }: AdminModalProps) => {
30 | const [open, setOpen] = useState(false);
31 |
32 | const [setRequestLimit, runSetRequestLimit] = usePactRequest(faucet.setRequestLimit);
33 | const [setAccountLimit, runSetAccountLimit] = usePactRequest(faucet.setAccountLimit);
34 |
35 | const title = "Faucet Contract Admin";
36 |
37 | const description = (
38 |
39 | The faucet smart contract has controls that allow the faucet account to raise or lower the
40 | per-account and per-request limits. You can act as the faucet account and change those limits.
41 |
42 | );
43 |
44 | return (
45 | Admin}
51 | >
52 | {
55 | const account = userAccount.address;
56 | const requestLimit = parsePactDecimal(fields.requestLimit);
57 | const accountLimit = parsePactDecimal(fields.accountLimit);
58 | if (typeof requestLimit !== "string" && typeof accountLimit !== "string") {
59 | await Promise.all([
60 | runSetRequestLimit({ account, amount: requestLimit }),
61 | runSetAccountLimit({ account, amount: accountLimit }),
62 | ]);
63 | } else if (typeof requestLimit !== "string") {
64 | await runSetRequestLimit({ account, amount: requestLimit });
65 | } else if (typeof accountLimit !== "string") {
66 | await runSetAccountLimit({ account, amount: accountLimit });
67 | }
68 | await onSuccess();
69 | }}
70 | validate={(values) => {
71 | const errors: { requestLimit?: string; accountLimit?: string } = {};
72 | const requestError = validateRequestLimit(
73 | values.requestLimit,
74 | faucet.DEFAULT_REQUEST_LIMIT
75 | );
76 | const accountError = validateRequestLimit(
77 | values.accountLimit,
78 | faucet.DEFAULT_ACCOUNT_LIMIT
79 | );
80 | if (requestError !== null) errors.requestLimit = requestError;
81 | if (accountError !== null) errors.accountLimit = accountError;
82 | if (values.requestLimit === "" && values.accountLimit === "")
83 | errors.requestLimit = "At least one field is required.";
84 | return errors;
85 | }}
86 | >
87 | {(formik) => (
88 |
124 | )}
125 |
126 |
127 | );
128 | };
129 |
130 | const validateRequestLimit = (value: string, min: number): null | string => {
131 | const parsed = parsePactDecimal(value);
132 | if (value === "") return null;
133 | if (typeof parsed === "string") {
134 | return parsed;
135 | } else if (parseFloat(parsed.decimal) < min) {
136 | return `Must be greater than existing limit (${min}).`;
137 | }
138 | return null;
139 | };
140 |
--------------------------------------------------------------------------------