├── .changeset ├── README.md └── config.json ├── .github ├── actions │ └── setup │ │ └── action.yml └── workflows │ ├── lint-and-typecheck.yml │ ├── publish-preview.yml │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .nvmrc ├── .prettierignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── commitlint.config.js ├── eslint.config.js ├── examples ├── borrow.ts ├── create-loan.ts ├── deposit-or-collateralise.ts ├── link-address.ts ├── liquidate.ts ├── read-write-account.ts └── user-loan-info.ts ├── media └── repo-header.png ├── package.json ├── pnpm-lock.yaml ├── prettier.config.js ├── src ├── chains │ └── evm │ │ ├── common │ │ ├── constants │ │ │ ├── abi │ │ │ │ ├── arbitrum-node-interface-abi.ts │ │ │ │ ├── bridge-router-abi.ts │ │ │ │ ├── ccip-data-adapter-abi.ts │ │ │ │ ├── erc-20-abi.ts │ │ │ │ ├── wormhole-data-adapter-abi.ts │ │ │ │ └── wormhole-relayer-abi.ts │ │ │ ├── address.ts │ │ │ ├── chain.ts │ │ │ ├── contract.ts │ │ │ └── tokens.ts │ │ ├── types │ │ │ ├── chain.ts │ │ │ ├── contract.ts │ │ │ ├── gmp.ts │ │ │ ├── index.ts │ │ │ ├── module.ts │ │ │ └── tokens.ts │ │ └── utils │ │ │ ├── chain.ts │ │ │ ├── contract.ts │ │ │ ├── gmp.ts │ │ │ ├── message.ts │ │ │ ├── provider.ts │ │ │ └── tokens.ts │ │ ├── hub │ │ ├── constants │ │ │ ├── abi │ │ │ │ ├── account-manager-abi.ts │ │ │ │ ├── bridge-router-hub-abi.ts │ │ │ │ ├── hub-abi.ts │ │ │ │ ├── hub-pool-abi.ts │ │ │ │ ├── hub-rewards-v1-abi.ts │ │ │ │ ├── hub-rewards-v2-abi.ts │ │ │ │ ├── loan-manager-abi.ts │ │ │ │ ├── node-manager-abi.ts │ │ │ │ └── oracle-manager-abi.ts │ │ │ └── chain.ts │ │ ├── modules │ │ │ ├── folks-hub-account.ts │ │ │ ├── folks-hub-gmp.ts │ │ │ ├── folks-hub-loan.ts │ │ │ ├── folks-hub-oracle.ts │ │ │ ├── folks-hub-pool.ts │ │ │ ├── folks-hub-rewards-v1.ts │ │ │ ├── folks-hub-rewards-v2.ts │ │ │ └── index.ts │ │ ├── types │ │ │ ├── account.ts │ │ │ ├── chain.ts │ │ │ ├── loan.ts │ │ │ ├── oracle.ts │ │ │ ├── pool.ts │ │ │ ├── rewards-v1.ts │ │ │ ├── rewards-v2.ts │ │ │ └── token.ts │ │ └── utils │ │ │ ├── chain.ts │ │ │ ├── contract.ts │ │ │ ├── events.ts │ │ │ ├── loan.ts │ │ │ └── message.ts │ │ └── spoke │ │ ├── constants │ │ └── abi │ │ │ ├── bridge-router-spoke-abi.ts │ │ │ ├── spoke-common-abi.ts │ │ │ ├── spoke-rewards-v2-common-abi.ts │ │ │ └── spoke-token-abi.ts │ │ ├── modules │ │ ├── folks-evm-account.ts │ │ ├── folks-evm-gmp.ts │ │ ├── folks-evm-loan.ts │ │ ├── folks-evm-rewards-v2.ts │ │ └── index.ts │ │ ├── types │ │ └── pool.ts │ │ └── utils │ │ └── contract.ts ├── common │ ├── constants │ │ ├── adapter.ts │ │ ├── bytes.ts │ │ ├── chain.ts │ │ ├── gmp.ts │ │ ├── lending.ts │ │ ├── message.ts │ │ ├── pool.ts │ │ ├── reward.ts │ │ └── token.ts │ ├── types │ │ ├── adapter.ts │ │ ├── address.ts │ │ ├── brand.ts │ │ ├── chain.ts │ │ ├── core.ts │ │ ├── gmp.ts │ │ ├── lending.ts │ │ ├── message.ts │ │ ├── module.ts │ │ ├── rewards-v2.ts │ │ ├── rewards.ts │ │ └── token.ts │ └── utils │ │ ├── adapter.ts │ │ ├── address.ts │ │ ├── bytes.ts │ │ ├── chain.ts │ │ ├── formulae.ts │ │ ├── gmp.ts │ │ ├── lending.ts │ │ ├── math-lib.ts │ │ ├── messages.ts │ │ ├── token.ts │ │ └── transaction.ts ├── index.ts ├── types │ └── generics.ts ├── utils │ ├── array.ts │ ├── exhaustive-check.ts │ └── random.ts └── xchain │ ├── core │ └── folks-core.ts │ └── modules │ ├── folks-account.ts │ ├── folks-gmp.ts │ ├── folks-loan.ts │ ├── folks-oracle.ts │ ├── folks-pool.ts │ ├── folks-rewards-v1.ts │ ├── folks-rewards-v2.ts │ └── index.ts ├── tsconfig.build.json └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "Folks-Finance/xchain-js-sdk" }], 4 | "commit": false, 5 | "access": "public", 6 | "baseBranch": "main", 7 | "updateInternalDependencies": "patch" 8 | } 9 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup" 2 | description: "Setup Node.js and pnpm and install dependencies" 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - name: Setup pnpm # by default it uses the version set in the package.json as packageManager 8 | uses: pnpm/action-setup@v4 9 | 10 | - name: Setup Node.js 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version-file: ".nvmrc" 14 | cache: "pnpm" 15 | 16 | - name: Install dependencies 17 | shell: bash 18 | env: 19 | HUSKY: "0" 20 | run: pnpm install --frozen-lockfile 21 | -------------------------------------------------------------------------------- /.github/workflows/lint-and-typecheck.yml: -------------------------------------------------------------------------------- 1 | name: Lint and Typecheck 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | lint: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup 23 | uses: ./.github/actions/setup 24 | 25 | - name: Lint 26 | run: pnpm lint 27 | 28 | typecheck: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v4 33 | 34 | - name: Setup 35 | uses: ./.github/actions/setup 36 | 37 | - name: Typecheck 38 | run: pnpm typecheck 39 | -------------------------------------------------------------------------------- /.github/workflows/publish-preview.yml: -------------------------------------------------------------------------------- 1 | name: Publish Any Commit 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v4 11 | 12 | - name: Setup 13 | uses: ./.github/actions/setup 14 | 15 | - name: Build 16 | run: pnpm build 17 | 18 | - run: pnpx pkg-pr-new publish --compact 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup 19 | uses: ./.github/actions/setup 20 | 21 | - name: Create Release Pull Request or Publish 22 | id: changesets 23 | uses: changesets/action@v1 24 | with: 25 | publish: pnpm run release 26 | title: "chore(release): version packages" 27 | commit: "chore(release): version packages" 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # build 5 | /dist 6 | 7 | # debug 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | .pnpm-debug.log* 12 | 13 | # typescript 14 | *.tsbuildinfo 15 | 16 | # eslint 17 | .eslintcache 18 | 19 | # macos 20 | .DS_Store 21 | 22 | # local env files 23 | .env 24 | .env*.local 25 | 26 | # intellij 27 | .idea 28 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | pnpm dlx commitlint --edit $1 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm exec lint-staged 2 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,ts}": ["eslint --cache --fix", "prettier --write"], 3 | "*.json": ["prettier --write"] 4 | } 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.13.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Formatting using Prettier by default for all languages 3 | "prettier.configPath": "prettier.config.js", 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "editor.formatOnSave": true, 6 | 7 | // Formatting using Prettier for JavaScript and TypeScript, overrides VSCode default. 8 | "[javascript]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | 15 | // Linting using ESLint. 16 | "eslint.validate": ["javascript", "typescript"], 17 | "eslint.experimental.useFlatConfig": true, 18 | "editor.codeActionsOnSave": { 19 | "source.fixAll.eslint": "explicit" 20 | }, 21 | "eslint.workingDirectories": [ 22 | { 23 | "mode": "auto" 24 | } 25 | ], 26 | 27 | "typescript.tsdk": "node_modules/typescript/lib" 28 | } 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to @folks-finance/xchain-sdk 2 | 3 | ## Table of Contents 4 | 5 | - [Contributing to @folks-finance/xchain-sdk](#contributing-to-folks-financexchain-sdk) 6 | - [Table of Contents](#table-of-contents) 7 | - [Using nvm to Manage Node.js Version](#using-nvm-to-manage-nodejs-version) 8 | - [Using pnpm Managed via Corepack](#using-pnpm-managed-via-corepack) 9 | - [Commit Guidelines](#commit-guidelines) 10 | - [Changesets and Automated Versioning](#changesets-and-automated-versioning) 11 | 12 | ### Using nvm to Manage Node.js Version 13 | 14 | We use `nvm` (Node Version Manager) to manage the version of Node.js used in this project. The required Node.js version is specified in the `.nvmrc` file. To ensure you are using the correct version, follow these steps: 15 | 16 | 1. **Install nvm**: If you don't have `nvm` installed, you can install it by following the instructions on the [nvm GitHub repository](https://github.com/nvm-sh/nvm). 17 | 18 | 2. **Activate the Correct Node.js Version**: 19 | 20 | ```bash 21 | nvm install 22 | nvm use 23 | ``` 24 | 25 | These commands will read the .nvmrc file and set your Node.js version accordingly. For more detailed instructions and troubleshooting, refer to the [nvm documentation](https://github.com/nvm-sh/nvm). 26 | 27 | ### Using pnpm Managed via Corepack 28 | 29 | We use `pnpm` as our package manager, which is managed via `corepack`. To get started with `pnpm`, follow these steps: 30 | 31 | 1. **Enable corepack**: 32 | 33 | ```bash 34 | corepack enable 35 | ``` 36 | 37 | 2. **Use pnpm**: After enabling `corepack`, you will be able to use `pnpm` for managing dependencies and running scripts. 38 | 39 | For a more complete guide on installing and using pnpm with corepack, please refer to the [pnpm documentation](https://pnpm.io/installation#using-corepack). 40 | 41 | ### Commit Guidelines 42 | 43 | We use `commitlint` to ensure that all commit messages follow the `conventional-commit` standard. This helps us maintain a clear and consistent commit history. 44 | 45 | The best way to make commits is by using the following command: 46 | 47 | ```bash 48 | pnpm commit 49 | ``` 50 | 51 | This will launch an automated prompt that guides you step by step through writing a commit message according to our conventions. 52 | 53 | For more information on the `conventional-commit` convention, please refer to the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). 54 | 55 | ### Changesets and Automated Versioning 56 | 57 | All commits must go through pull requests. In every pull request where changes are made that modify the SDK, you have to generate a changeset by launching: 58 | 59 | ```bash 60 | pnpm changeset 61 | ``` 62 | 63 | This command will guide you through creating a changeset file that describes the changes made in the pull request and specifies the type of version bump required (patch, minor, major). 64 | 65 | Package versioning, publishing, and the generation of changelogs on GitHub releases are automated via GitHub Actions. This ensures that the package is always up-to-date and that every change is properly versioned, published, and documented without manual intervention. 66 | 67 | For more information on creating and managing changesets, please refer to the [Changesets documentation](https://github.com/changesets/changesets/blob/b59375614b1b3dabdf67806cd202defb314686a8/docs/adding-a-changeset.md). 68 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 FOLKS FINANCE PRIVATE FOUNDATION 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 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { extends: ["@commitlint/config-conventional"] }; 2 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import eslint from "@eslint/js"; 3 | import eslintConfigPrettier from "eslint-config-prettier"; 4 | import eslintPluginImportX from "eslint-plugin-import-x"; 5 | import eslintPluginUnicorn from "eslint-plugin-unicorn"; 6 | import globals from "globals"; 7 | import tseslint from "typescript-eslint"; 8 | 9 | export default tseslint.config( 10 | { ignores: ["dist/"] }, 11 | { 12 | languageOptions: { 13 | parserOptions: { 14 | project: true, 15 | tsconfigRootDir: import.meta.dirname, 16 | }, 17 | globals: { ...globals.browser, ...globals.node }, 18 | }, 19 | }, 20 | eslint.configs.recommended, 21 | ...tseslint.configs.strictTypeChecked, 22 | ...tseslint.configs.stylisticTypeChecked, 23 | eslintConfigPrettier, 24 | { 25 | plugins: { 26 | "import-x": eslintPluginImportX, 27 | "@typescript-eslint": tseslint.plugin, 28 | unicorn: eslintPluginUnicorn, 29 | }, 30 | rules: { 31 | "import-x/consistent-type-specifier-style": ["error", "prefer-top-level"], 32 | "import-x/no-cycle": "warn", 33 | "import-x/no-default-export": "error", 34 | "import-x/no-duplicates": ["error"], 35 | "import-x/no-named-as-default": "off", 36 | "import-x/no-unresolved": "error", 37 | "import-x/order": [ 38 | "error", 39 | { 40 | groups: ["builtin", "external", "internal", "parent", "sibling", "index", "type"], 41 | "newlines-between": "always", 42 | alphabetize: { order: "asc" }, 43 | }, 44 | ], 45 | "@typescript-eslint/array-type": ["error", { default: "generic" }], 46 | "@typescript-eslint/consistent-type-definitions": ["error", "type"], 47 | "@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }], 48 | "@typescript-eslint/restrict-template-expressions": ["error", { allowNumber: true }], 49 | "@typescript-eslint/switch-exhaustiveness-check": "error", 50 | "unicorn/better-regex": "error", 51 | "unicorn/consistent-function-scoping": "error", 52 | "unicorn/expiring-todo-comments": "error", 53 | "unicorn/filename-case": ["error", { case: "kebabCase" }], 54 | "unicorn/no-array-for-each": "error", 55 | "unicorn/no-for-loop": "error", 56 | }, 57 | }, 58 | { 59 | files: ["**/*.js"], 60 | ...tseslint.configs.disableTypeChecked, 61 | }, 62 | { 63 | settings: { 64 | "import-x/parsers": { 65 | "@typescript-eslint/parser": [".ts", ".tsx"], 66 | }, 67 | "import-x/resolver": { 68 | // Load /tsconfig.json 69 | typescript: true, 70 | node: true, 71 | }, 72 | }, 73 | }, 74 | // warn if there are unused eslint-disable directives 75 | { linterOptions: { reportUnusedDisableDirectives: true } }, 76 | ); 77 | -------------------------------------------------------------------------------- /examples/borrow.ts: -------------------------------------------------------------------------------- 1 | import * as dn from "dnum"; 2 | import { createWalletClient, http, parseUnits } from "viem"; 3 | import { mnemonicToAccount } from "viem/accounts"; 4 | 5 | import { 6 | NetworkType, 7 | FolksCore, 8 | FolksLoan, 9 | FolksPool, 10 | FOLKS_CHAIN_ID, 11 | getSupportedMessageAdapters, 12 | Action, 13 | MessageAdapterParamsType, 14 | CHAIN_VIEM, 15 | TESTNET_FOLKS_TOKEN_ID, 16 | } from "../src/index.js"; 17 | 18 | import type { FolksCoreConfig, MessageAdapters, AccountId, LoanId } from "../src/index.js"; 19 | 20 | async function main() { 21 | const network = NetworkType.TESTNET; 22 | const chain = FOLKS_CHAIN_ID.BSC_TESTNET; 23 | const tokenId = TESTNET_FOLKS_TOKEN_ID.BNB; 24 | 25 | const folksConfig: FolksCoreConfig = { network, provider: { evm: {} } }; 26 | 27 | FolksCore.init(folksConfig); 28 | FolksCore.setNetwork(network); 29 | 30 | const MNEMONIC = "your mnemonic here"; 31 | const account = mnemonicToAccount(MNEMONIC); 32 | 33 | const signer = createWalletClient({ 34 | account, 35 | chain: CHAIN_VIEM[chain], 36 | transport: http(), 37 | }); 38 | 39 | const { adapterIds, returnAdapterIds } = getSupportedMessageAdapters({ 40 | action: Action.Borrow, 41 | messageAdapterParamType: MessageAdapterParamsType.ReceiveToken, 42 | network, 43 | sourceFolksChainId: chain, 44 | destFolksChainId: chain, 45 | folksTokenId: tokenId, 46 | }); 47 | 48 | const adapters: MessageAdapters = { 49 | adapterId: adapterIds[0], 50 | returnAdapterId: returnAdapterIds[0], 51 | }; 52 | 53 | FolksCore.setFolksSigner({ signer, folksChainId: chain }); 54 | 55 | const accountId = "0x7d6...b66" as AccountId; // Your xChainApp account id 56 | const loanId = "0x166...c12" as LoanId; // Your loan id 57 | const amountToBorrow = parseUnits("0.0005", 18); // 0.0005 BNB (BNB has 18 decimals) 58 | const isStableRate = false; 59 | 60 | let maxStableRate; 61 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 62 | if (isStableRate) { 63 | const poolInfo = await FolksPool.read.poolInfo(tokenId); 64 | const interestRate = poolInfo.stableBorrowData.interestRate[0]; 65 | const [rateWithSlippage] = dn.mul(interestRate, 1.05); // 5% max deviation from current rate 66 | maxStableRate = rateWithSlippage; 67 | } else { 68 | maxStableRate = BigInt(0); 69 | } 70 | 71 | const prepareBorrowCall = await FolksLoan.prepare.borrow( 72 | accountId, 73 | loanId, 74 | tokenId, 75 | amountToBorrow, 76 | maxStableRate, 77 | chain, 78 | adapters, 79 | ); 80 | const createBorrowCallRes = await FolksLoan.write.borrow( 81 | accountId, 82 | loanId, 83 | tokenId, 84 | amountToBorrow, 85 | maxStableRate, 86 | chain, 87 | prepareBorrowCall, 88 | ); 89 | console.log(`Transaction ID: ${createBorrowCallRes}`); 90 | } 91 | 92 | main() 93 | .then(() => { 94 | console.log("done"); 95 | }) 96 | .catch((error: unknown) => { 97 | console.error(error); 98 | }); 99 | -------------------------------------------------------------------------------- /examples/create-loan.ts: -------------------------------------------------------------------------------- 1 | import { createWalletClient, http } from "viem"; 2 | import { mnemonicToAccount } from "viem/accounts"; 3 | 4 | import { convertStringToLoanName } from "../src/common/utils/lending.js"; 5 | import { 6 | NetworkType, 7 | FolksCore, 8 | getRandomBytes, 9 | FolksLoan, 10 | FOLKS_CHAIN_ID, 11 | BYTES4_LENGTH, 12 | getSupportedMessageAdapters, 13 | Action, 14 | MessageAdapterParamsType, 15 | CHAIN_VIEM, 16 | TESTNET_LOAN_TYPE_ID, 17 | } from "../src/index.js"; 18 | 19 | import type { FolksCoreConfig, MessageAdapters, Nonce, AccountId } from "../src/index.js"; 20 | 21 | async function main() { 22 | const network = NetworkType.TESTNET; 23 | const chain = FOLKS_CHAIN_ID.AVALANCHE_FUJI; 24 | 25 | const folksConfig: FolksCoreConfig = { network, provider: { evm: {} } }; 26 | 27 | FolksCore.init(folksConfig); 28 | FolksCore.setNetwork(network); 29 | 30 | const nonce: Nonce = getRandomBytes(BYTES4_LENGTH) as Nonce; 31 | 32 | const MNEMONIC = "your mnemonic here"; 33 | const account = mnemonicToAccount(MNEMONIC); 34 | 35 | const signer = createWalletClient({ 36 | account, 37 | chain: CHAIN_VIEM[chain], 38 | transport: http(), 39 | }); 40 | 41 | const { adapterIds, returnAdapterIds } = getSupportedMessageAdapters({ 42 | action: Action.CreateLoan, 43 | messageAdapterParamType: MessageAdapterParamsType.Data, 44 | network, 45 | sourceFolksChainId: chain, 46 | }); 47 | 48 | const adapters: MessageAdapters = { 49 | adapterId: adapterIds[0], 50 | returnAdapterId: returnAdapterIds[0], 51 | }; 52 | 53 | FolksCore.setFolksSigner({ signer, folksChainId: chain }); 54 | 55 | const accountId = "0x7d6...b66" as AccountId; // Your xChainApp account id 56 | const loanType = TESTNET_LOAN_TYPE_ID.GENERAL; // TESTNET_LOAN_TYPE_ID.DEPOSIT for deposits 57 | const loanName = convertStringToLoanName("Test Loan"); 58 | 59 | const prepareCreateLoanCall = await FolksLoan.prepare.createLoan(accountId, nonce, loanType, loanName, adapters); 60 | const createLoanCallRes = await FolksLoan.write.createLoan( 61 | accountId, 62 | nonce, 63 | loanType, 64 | loanName, 65 | prepareCreateLoanCall, 66 | ); 67 | console.log(`Transaction ID: ${createLoanCallRes}`); 68 | } 69 | 70 | main() 71 | .then(() => { 72 | console.log("done"); 73 | }) 74 | .catch((error: unknown) => { 75 | console.error(error); 76 | }); 77 | -------------------------------------------------------------------------------- /examples/deposit-or-collateralise.ts: -------------------------------------------------------------------------------- 1 | import { createWalletClient, http, parseUnits } from "viem"; 2 | import { mnemonicToAccount } from "viem/accounts"; 3 | 4 | import { 5 | NetworkType, 6 | FolksCore, 7 | FolksLoan, 8 | FOLKS_CHAIN_ID, 9 | getSupportedMessageAdapters, 10 | Action, 11 | MessageAdapterParamsType, 12 | CHAIN_VIEM, 13 | TESTNET_FOLKS_TOKEN_ID, 14 | TESTNET_LOAN_TYPE_ID, 15 | } from "../src/index.js"; 16 | 17 | import type { FolksCoreConfig, MessageAdapters, AccountId, LoanId } from "../src/index.js"; 18 | 19 | async function main() { 20 | const network = NetworkType.TESTNET; 21 | const chain = FOLKS_CHAIN_ID.AVALANCHE_FUJI; 22 | const tokenId = TESTNET_FOLKS_TOKEN_ID.AVAX; 23 | 24 | const folksConfig: FolksCoreConfig = { network, provider: { evm: {} } }; 25 | 26 | FolksCore.init(folksConfig); 27 | FolksCore.setNetwork(network); 28 | 29 | const MNEMONIC = "your mnemonic here"; 30 | const account = mnemonicToAccount(MNEMONIC); 31 | 32 | const signer = createWalletClient({ 33 | account, 34 | chain: CHAIN_VIEM[chain], 35 | transport: http(), 36 | }); 37 | 38 | const { adapterIds, returnAdapterIds } = getSupportedMessageAdapters({ 39 | action: Action.Deposit, 40 | messageAdapterParamType: MessageAdapterParamsType.SendToken, 41 | network, 42 | sourceFolksChainId: chain, 43 | folksTokenId: tokenId, 44 | }); 45 | 46 | const adapters: MessageAdapters = { 47 | adapterId: adapterIds[0], 48 | returnAdapterId: returnAdapterIds[0], 49 | }; 50 | 51 | FolksCore.setFolksSigner({ signer, folksChainId: chain }); 52 | 53 | const accountId = "0x7d6...b66" as AccountId; // Your xChainApp account id 54 | const loanId = "0x166...c12" as LoanId; // Your loan id 55 | const loanType = TESTNET_LOAN_TYPE_ID.GENERAL; // TESTNET_LOAN_TYPE_ID.DEPOSIT for deposits 56 | const amountToDeposit = parseUnits("0.1", 18); // 0.1 AVAX (AVAX has 18 decimals) 57 | 58 | const prepareDepositCall = await FolksLoan.prepare.deposit( 59 | accountId, 60 | loanId, 61 | loanType, 62 | tokenId, 63 | amountToDeposit, 64 | adapters, 65 | ); 66 | const createDepositCallRes = await FolksLoan.write.deposit( 67 | accountId, 68 | loanId, 69 | amountToDeposit, 70 | true, 71 | prepareDepositCall, 72 | ); 73 | console.log(`Transaction ID: ${createDepositCallRes}`); 74 | } 75 | 76 | main() 77 | .then(() => { 78 | console.log("done"); 79 | }) 80 | .catch((error: unknown) => { 81 | console.error(error); 82 | }); 83 | -------------------------------------------------------------------------------- /examples/link-address.ts: -------------------------------------------------------------------------------- 1 | import { createWalletClient, http } from "viem"; 2 | import { mnemonicToAccount } from "viem/accounts"; 3 | 4 | import { 5 | Action, 6 | CHAIN_VIEM, 7 | ChainType, 8 | convertToGenericAddress, 9 | FOLKS_CHAIN_ID, 10 | FolksAccount, 11 | FolksCore, 12 | getSupportedMessageAdapters, 13 | MessageAdapterParamsType, 14 | NetworkType, 15 | } from "../src/index.js"; 16 | 17 | import type { AccountId, EvmAddress, FolksCoreConfig, MessageAdapters } from "../src/index.js"; 18 | 19 | async function main() { 20 | const network = NetworkType.TESTNET; 21 | const chain = FOLKS_CHAIN_ID.AVALANCHE_FUJI; 22 | const chainToLink = FOLKS_CHAIN_ID.BSC_TESTNET; 23 | 24 | const folksConfig: FolksCoreConfig = { network, provider: { evm: {} } }; 25 | 26 | FolksCore.init(folksConfig); 27 | FolksCore.setNetwork(network); 28 | 29 | // invite 30 | const MNEMONIC = "your mnemonic here"; 31 | const account = mnemonicToAccount(MNEMONIC); 32 | 33 | const signer = createWalletClient({ 34 | account, 35 | chain: CHAIN_VIEM[chain], 36 | transport: http(), 37 | }); 38 | 39 | const chainAdapters = getSupportedMessageAdapters({ 40 | action: Action.InviteAddress, 41 | messageAdapterParamType: MessageAdapterParamsType.Data, 42 | network, 43 | sourceFolksChainId: chain, 44 | }); 45 | 46 | const adapters: MessageAdapters = { 47 | adapterId: chainAdapters.adapterIds[0], 48 | returnAdapterId: chainAdapters.returnAdapterIds[0], 49 | }; 50 | 51 | FolksCore.setFolksSigner({ 52 | signer, 53 | folksChainId: chain, 54 | }); 55 | 56 | const accountId = "0x7d6...b66" as AccountId; // Your xChainApp account id 57 | const addressToLink = convertToGenericAddress("0x322...b78" as EvmAddress, ChainType.EVM); 58 | 59 | const prepareInviteCall = await FolksAccount.prepare.inviteAddress(accountId, chainToLink, addressToLink, adapters); 60 | const inviteRes = await FolksAccount.write.inviteAddress(accountId, chainToLink, addressToLink, prepareInviteCall); 61 | console.log(`Invitation transaction ID: ${inviteRes}`); 62 | 63 | // accept invitation 64 | const MNEMONIC_TO_LINK = "your mnemonic here"; 65 | const accountToLink = mnemonicToAccount(MNEMONIC_TO_LINK); 66 | 67 | const signerToLink = createWalletClient({ 68 | account: accountToLink, 69 | chain: CHAIN_VIEM[chainToLink], 70 | transport: http(), 71 | }); 72 | 73 | const chainAdaptersToLink = getSupportedMessageAdapters({ 74 | action: Action.AcceptInviteAddress, 75 | messageAdapterParamType: MessageAdapterParamsType.Data, 76 | network, 77 | sourceFolksChainId: chainToLink, 78 | }); 79 | 80 | const adaptersToLink: MessageAdapters = { 81 | adapterId: chainAdaptersToLink.adapterIds[0], 82 | returnAdapterId: chainAdaptersToLink.returnAdapterIds[0], 83 | }; 84 | 85 | FolksCore.setFolksSigner({ 86 | signer: signerToLink, 87 | folksChainId: chainToLink, 88 | }); 89 | 90 | const prepareAcceptInviteCall = await FolksAccount.prepare.acceptInvite(accountId, adaptersToLink); 91 | 92 | const acceptInviteRes = await FolksAccount.write.acceptInvite(accountId, prepareAcceptInviteCall); 93 | console.log(`Accept transaction ID: ${acceptInviteRes}`); 94 | } 95 | 96 | main() 97 | .then(() => { 98 | console.log("done"); 99 | }) 100 | .catch((error: unknown) => { 101 | console.error(error); 102 | }); 103 | -------------------------------------------------------------------------------- /examples/liquidate.ts: -------------------------------------------------------------------------------- 1 | import * as dn from "dnum"; 2 | import { createWalletClient, http, parseUnits } from "viem"; 3 | import { mnemonicToAccount } from "viem/accounts"; 4 | 5 | import { 6 | NetworkType, 7 | FolksCore, 8 | FolksLoan, 9 | FOLKS_CHAIN_ID, 10 | CHAIN_VIEM, 11 | TESTNET_FOLKS_TOKEN_ID, 12 | FolksPool, 13 | FolksOracle, 14 | TESTNET_LOAN_TYPE_ID, 15 | } from "../src/index.js"; 16 | 17 | import type { FolksCoreConfig, AccountId, LoanId, FolksTokenId, PoolInfo } from "../src/index.js"; 18 | 19 | async function main() { 20 | const network = NetworkType.TESTNET; 21 | const chain = FOLKS_CHAIN_ID.AVALANCHE_FUJI; 22 | 23 | const folksConfig: FolksCoreConfig = { network, provider: { evm: {} } }; 24 | 25 | FolksCore.init(folksConfig); 26 | FolksCore.setNetwork(network); 27 | 28 | const MNEMONIC = "your mnemonic here"; 29 | const account = mnemonicToAccount(MNEMONIC); 30 | 31 | const signer = createWalletClient({ 32 | account, 33 | chain: CHAIN_VIEM[chain], 34 | transport: http(), 35 | }); 36 | 37 | FolksCore.setFolksSigner({ signer, folksChainId: chain }); 38 | 39 | const accountId = "0x7d6...b66" as AccountId; // Your xChainApp account id 40 | const liquidatorLoanId = "0x4c6...824" as LoanId; // Your loan id to transfer debt and collateral into 41 | const violatorLoanId = "0xcd1...632" as LoanId; // Loan to liquidate 42 | 43 | // check if loan can be liquidated 44 | const poolsInfo: Partial> = {}; 45 | await Promise.all( 46 | Object.values(TESTNET_FOLKS_TOKEN_ID).map(async (folksTokenId) => { 47 | const poolInfo = await FolksPool.read.poolInfo(folksTokenId); 48 | poolsInfo[folksTokenId] = poolInfo; 49 | }), 50 | ); 51 | const loanTypeInfo = { 52 | [TESTNET_LOAN_TYPE_ID.GENERAL]: await FolksLoan.read.loanTypeInfo(TESTNET_LOAN_TYPE_ID.GENERAL), 53 | }; 54 | const oraclePrices = await FolksOracle.read.oraclePrices(); 55 | 56 | const userGeneralLoans = await FolksLoan.read.userLoans([violatorLoanId]); 57 | const userGeneralLoansInfo = FolksLoan.util.userLoansInfo(userGeneralLoans, poolsInfo, loanTypeInfo, oraclePrices); 58 | const violatorLoanInfo = userGeneralLoansInfo[violatorLoanId]; 59 | 60 | if (dn.lt(violatorLoanInfo.totalEffectiveBorrowBalanceValue, violatorLoanInfo.totalEffectiveCollateralBalanceValue)) { 61 | console.log("Loan can't be liquidated."); 62 | return; 63 | } 64 | 65 | // liquidate 66 | const amountToRepay = parseUnits("0.01", 18); 67 | const minAmountToSeize = parseUnits("0.5", 18); 68 | const prepareLiquidationCall = await FolksLoan.prepare.liquidate( 69 | accountId, 70 | liquidatorLoanId, 71 | violatorLoanId, 72 | TESTNET_FOLKS_TOKEN_ID.BNB, 73 | TESTNET_FOLKS_TOKEN_ID.AVAX, 74 | amountToRepay, 75 | minAmountToSeize, 76 | ); 77 | const liquidateRes = await FolksLoan.write.liquidate(accountId, prepareLiquidationCall); 78 | console.log(`Transaction ID: ${liquidateRes}`); 79 | } 80 | 81 | main() 82 | .then(() => { 83 | console.log("done"); 84 | }) 85 | .catch((error: unknown) => { 86 | console.error(error); 87 | }); 88 | -------------------------------------------------------------------------------- /examples/read-write-account.ts: -------------------------------------------------------------------------------- 1 | import { createWalletClient, http } from "viem"; 2 | import { mnemonicToAccount } from "viem/accounts"; 3 | 4 | import { 5 | NetworkType, 6 | FolksCore, 7 | getRandomBytes, 8 | FolksAccount, 9 | FOLKS_CHAIN_ID, 10 | BYTES4_LENGTH, 11 | getSupportedMessageAdapters, 12 | Action, 13 | MessageAdapterParamsType, 14 | buildAccountId, 15 | convertToGenericAddress, 16 | ChainType, 17 | CHAIN_VIEM, 18 | } from "../src/index.js"; 19 | 20 | import type { EvmAddress, FolksCoreConfig, MessageAdapters, Nonce } from "../src/index.js"; 21 | 22 | async function main() { 23 | const network = NetworkType.TESTNET; 24 | const chain = FOLKS_CHAIN_ID.AVALANCHE_FUJI; 25 | 26 | const folksConfig: FolksCoreConfig = { network, provider: { evm: {} } }; 27 | 28 | FolksCore.init(folksConfig); 29 | FolksCore.setNetwork(network); 30 | 31 | const nonce: Nonce = getRandomBytes(BYTES4_LENGTH) as Nonce; 32 | 33 | // write 34 | const MNEMONIC = "your mnemonic here"; 35 | const account = mnemonicToAccount(MNEMONIC); 36 | 37 | const signer = createWalletClient({ 38 | account, 39 | chain: CHAIN_VIEM[chain], 40 | transport: http(), 41 | }); 42 | 43 | const { adapterIds, returnAdapterIds } = getSupportedMessageAdapters({ 44 | action: Action.CreateAccount, 45 | messageAdapterParamType: MessageAdapterParamsType.Data, 46 | network, 47 | sourceFolksChainId: chain, 48 | }); 49 | 50 | const adapters: MessageAdapters = { 51 | adapterId: adapterIds[0], 52 | returnAdapterId: returnAdapterIds[0], 53 | }; 54 | 55 | FolksCore.setFolksSigner({ signer, folksChainId: chain }); 56 | 57 | // read 58 | const folksChain = FolksCore.getSelectedFolksChain(); 59 | const userAddress = convertToGenericAddress(account.address as EvmAddress, ChainType.EVM); 60 | const accountId = buildAccountId(userAddress, folksChain.folksChainId, nonce); 61 | const accountInfo = await FolksAccount.read.accountInfo(accountId); 62 | console.log(accountInfo); 63 | 64 | // write 65 | const prepareCreateAccountCall = await FolksAccount.prepare.createAccount(nonce, adapters); 66 | const createAccountCallRes = await FolksAccount.write.createAccount(nonce, prepareCreateAccountCall); 67 | 68 | console.log(createAccountCallRes); 69 | } 70 | 71 | main() 72 | .then(() => { 73 | console.log("done"); 74 | }) 75 | .catch((error: unknown) => { 76 | console.error(error); 77 | }); 78 | -------------------------------------------------------------------------------- /examples/user-loan-info.ts: -------------------------------------------------------------------------------- 1 | import { createClient, http } from "viem"; 2 | 3 | import { 4 | CHAIN_VIEM, 5 | FOLKS_CHAIN_ID, 6 | FolksCore, 7 | FolksLoan, 8 | FolksOracle, 9 | FolksPool, 10 | NetworkType, 11 | TESTNET_FOLKS_TOKEN_ID, 12 | TESTNET_LOAN_TYPE_ID, 13 | } from "../src/index.js"; 14 | 15 | import type { AccountId, FolksCoreConfig, PoolInfo, FolksTokenId } from "../src/index.js"; 16 | 17 | async function main() { 18 | const network = NetworkType.TESTNET; 19 | 20 | const folksConfig: FolksCoreConfig = { 21 | network, 22 | provider: { 23 | evm: { 24 | [FOLKS_CHAIN_ID.AVALANCHE_FUJI]: createClient({ 25 | chain: CHAIN_VIEM[FOLKS_CHAIN_ID.AVALANCHE_FUJI], 26 | transport: http("https://my-rpc.avax-testnet.network/"), 27 | }), 28 | }, 29 | }, 30 | }; 31 | 32 | FolksCore.init(folksConfig); 33 | FolksCore.setNetwork(network); 34 | 35 | const poolsInfo: Partial> = {}; 36 | await Promise.all( 37 | Object.values(TESTNET_FOLKS_TOKEN_ID).map(async (folksTokenId) => { 38 | const poolInfo = await FolksPool.read.poolInfo(folksTokenId); 39 | poolsInfo[folksTokenId] = poolInfo; 40 | }), 41 | ); 42 | const loanTypeInfo = { 43 | [TESTNET_LOAN_TYPE_ID.GENERAL]: await FolksLoan.read.loanTypeInfo(TESTNET_LOAN_TYPE_ID.GENERAL), 44 | }; 45 | const oraclePrices = await FolksOracle.read.oraclePrices(); 46 | 47 | const accountId: AccountId = "0x7d6...b66" as AccountId; 48 | 49 | const loanIds = await FolksLoan.read.userLoansIds(accountId, [TESTNET_LOAN_TYPE_ID.GENERAL]); 50 | const generalLoansIds = loanIds.get(TESTNET_LOAN_TYPE_ID.GENERAL); 51 | 52 | if (!generalLoansIds) { 53 | console.log("No general loans found"); 54 | return; 55 | } 56 | 57 | const userGeneralLoans = await FolksLoan.read.userLoans(generalLoansIds); 58 | const userGeneralLoansInfo = FolksLoan.util.userLoansInfo(userGeneralLoans, poolsInfo, loanTypeInfo, oraclePrices); 59 | console.log(userGeneralLoansInfo); 60 | } 61 | 62 | main() 63 | .then(() => { 64 | console.log("done"); 65 | }) 66 | .catch((error: unknown) => { 67 | console.error(error); 68 | }); 69 | -------------------------------------------------------------------------------- /media/repo-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Folks-Finance/xchain-js-sdk/f1946c2cec7f63be0e2b760cbe273b6839d10fff/media/repo-header.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@folks-finance/xchain-sdk", 3 | "description": "The official JavaScript SDK for the Folks Finance Cross-Chain Lending Protocol", 4 | "version": "0.0.82", 5 | "license": "MIT", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Folks-Finance/xchain-js-sdk.git" 12 | }, 13 | "type": "module", 14 | "engines": { 15 | "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 16 | }, 17 | "sideEffects": false, 18 | "files": [ 19 | "dist", 20 | "src" 21 | ], 22 | "main": "dist/index.js", 23 | "types": "dist/index.d.ts", 24 | "exports": { 25 | ".": { 26 | "types": "./dist/index.d.ts", 27 | "default": "./dist/index.js" 28 | }, 29 | "./package.json": "./package.json" 30 | }, 31 | "scripts": { 32 | "prepare": "husky", 33 | "commit": "cz", 34 | "build": "tsc -p tsconfig.build.json", 35 | "lint": "eslint .", 36 | "format": "prettier --write .", 37 | "typecheck": "tsc --noEmit", 38 | "release": "pnpm build && changeset publish" 39 | }, 40 | "dependencies": { 41 | "dnum": "^2.13.1", 42 | "viem": "^2.23.2" 43 | }, 44 | "devDependencies": { 45 | "@changesets/changelog-github": "^0.5.0", 46 | "@changesets/cli": "^2.27.7", 47 | "@commitlint/cli": "^19.3.0", 48 | "@commitlint/config-conventional": "^19.2.2", 49 | "@commitlint/cz-commitlint": "^19.2.0", 50 | "@eslint/js": "^9.2.0", 51 | "@types/node": "^20.12.12", 52 | "@typescript-eslint/parser": "^7.10.0", 53 | "commitizen": "^4.3.0", 54 | "eslint": "^9.2.0", 55 | "eslint-config-prettier": "^9.1.0", 56 | "eslint-import-resolver-typescript": "^3.6.1", 57 | "eslint-plugin-import-x": "^0.5.0", 58 | "eslint-plugin-unicorn": "^53.0.0", 59 | "globals": "^15.2.0", 60 | "husky": "^9.0.11", 61 | "lint-staged": "^15.2.2", 62 | "prettier": "^3.2.5", 63 | "typescript": "^5.4.5", 64 | "typescript-eslint": "^7.9.0" 65 | }, 66 | "config": { 67 | "commitizen": { 68 | "path": "@commitlint/cz-commitlint" 69 | } 70 | }, 71 | "packageManager": "pnpm@9.1.1+sha256.9551e803dcb7a1839fdf5416153a844060c7bce013218ce823410532504ac10b", 72 | "keywords": [ 73 | "folks-finance", 74 | "cross-chain", 75 | "xlending", 76 | "blockchain", 77 | "ethereum", 78 | "evm" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | export default { 5 | printWidth: 120, 6 | tabWidth: 2, 7 | useTabs: false, 8 | semi: true, 9 | singleQuote: false, 10 | trailingComma: "all", 11 | }; 12 | -------------------------------------------------------------------------------- /src/chains/evm/common/constants/abi/arbitrum-node-interface-abi.ts: -------------------------------------------------------------------------------- 1 | export const ArbitrumNodeInterfaceAbi = [ 2 | { 3 | name: "gasEstimateL1Component", 4 | type: "function", 5 | inputs: [ 6 | { name: "to", type: "address" }, 7 | { name: "contractCreation", type: "bool" }, 8 | { name: "data", type: "bytes" }, 9 | ], 10 | outputs: [ 11 | { name: "gasEstimateForL1", type: "uint64" }, 12 | { name: "baseFee", type: "uint256" }, 13 | { name: "l1BaseFeeEstimate", type: "uint256" }, 14 | ], 15 | stateMutability: "payable", 16 | }, 17 | ] as const; 18 | -------------------------------------------------------------------------------- /src/chains/evm/common/constants/abi/erc-20-abi.ts: -------------------------------------------------------------------------------- 1 | export const ERC20Abi = [ 2 | { 3 | inputs: [ 4 | { internalType: "address", name: "spender", type: "address" }, 5 | { internalType: "uint256", name: "allowance", type: "uint256" }, 6 | { internalType: "uint256", name: "needed", type: "uint256" }, 7 | ], 8 | name: "ERC20InsufficientAllowance", 9 | type: "error", 10 | }, 11 | { 12 | inputs: [ 13 | { internalType: "address", name: "sender", type: "address" }, 14 | { internalType: "uint256", name: "balance", type: "uint256" }, 15 | { internalType: "uint256", name: "needed", type: "uint256" }, 16 | ], 17 | name: "ERC20InsufficientBalance", 18 | type: "error", 19 | }, 20 | { 21 | inputs: [{ internalType: "address", name: "approver", type: "address" }], 22 | name: "ERC20InvalidApprover", 23 | type: "error", 24 | }, 25 | { 26 | inputs: [{ internalType: "address", name: "receiver", type: "address" }], 27 | name: "ERC20InvalidReceiver", 28 | type: "error", 29 | }, 30 | { 31 | inputs: [{ internalType: "address", name: "sender", type: "address" }], 32 | name: "ERC20InvalidSender", 33 | type: "error", 34 | }, 35 | { 36 | inputs: [{ internalType: "address", name: "spender", type: "address" }], 37 | name: "ERC20InvalidSpender", 38 | type: "error", 39 | }, 40 | { 41 | anonymous: false, 42 | inputs: [ 43 | { 44 | indexed: true, 45 | internalType: "address", 46 | name: "owner", 47 | type: "address", 48 | }, 49 | { 50 | indexed: true, 51 | internalType: "address", 52 | name: "spender", 53 | type: "address", 54 | }, 55 | { 56 | indexed: false, 57 | internalType: "uint256", 58 | name: "value", 59 | type: "uint256", 60 | }, 61 | ], 62 | name: "Approval", 63 | type: "event", 64 | }, 65 | { 66 | anonymous: false, 67 | inputs: [ 68 | { indexed: true, internalType: "address", name: "from", type: "address" }, 69 | { indexed: true, internalType: "address", name: "to", type: "address" }, 70 | { 71 | indexed: false, 72 | internalType: "uint256", 73 | name: "value", 74 | type: "uint256", 75 | }, 76 | ], 77 | name: "Transfer", 78 | type: "event", 79 | }, 80 | { 81 | inputs: [ 82 | { internalType: "address", name: "owner", type: "address" }, 83 | { internalType: "address", name: "spender", type: "address" }, 84 | ], 85 | name: "allowance", 86 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 87 | stateMutability: "view", 88 | type: "function", 89 | }, 90 | { 91 | inputs: [ 92 | { internalType: "address", name: "spender", type: "address" }, 93 | { internalType: "uint256", name: "value", type: "uint256" }, 94 | ], 95 | name: "approve", 96 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 97 | stateMutability: "nonpayable", 98 | type: "function", 99 | }, 100 | { 101 | inputs: [{ internalType: "address", name: "account", type: "address" }], 102 | name: "balanceOf", 103 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 104 | stateMutability: "view", 105 | type: "function", 106 | }, 107 | { 108 | inputs: [], 109 | name: "decimals", 110 | outputs: [{ internalType: "uint8", name: "", type: "uint8" }], 111 | stateMutability: "view", 112 | type: "function", 113 | }, 114 | { 115 | inputs: [], 116 | name: "name", 117 | outputs: [{ internalType: "string", name: "", type: "string" }], 118 | stateMutability: "view", 119 | type: "function", 120 | }, 121 | { 122 | inputs: [], 123 | name: "symbol", 124 | outputs: [{ internalType: "string", name: "", type: "string" }], 125 | stateMutability: "view", 126 | type: "function", 127 | }, 128 | { 129 | inputs: [], 130 | name: "totalSupply", 131 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 132 | stateMutability: "view", 133 | type: "function", 134 | }, 135 | { 136 | inputs: [ 137 | { internalType: "address", name: "to", type: "address" }, 138 | { internalType: "uint256", name: "value", type: "uint256" }, 139 | ], 140 | name: "transfer", 141 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 142 | stateMutability: "nonpayable", 143 | type: "function", 144 | }, 145 | { 146 | inputs: [ 147 | { internalType: "address", name: "from", type: "address" }, 148 | { internalType: "address", name: "to", type: "address" }, 149 | { internalType: "uint256", name: "value", type: "uint256" }, 150 | ], 151 | name: "transferFrom", 152 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 153 | stateMutability: "nonpayable", 154 | type: "function", 155 | }, 156 | ] as const; 157 | -------------------------------------------------------------------------------- /src/chains/evm/common/constants/address.ts: -------------------------------------------------------------------------------- 1 | export const ARBITRUM_NODE_INTERFACE = "0x00000000000000000000000000000000000000C8"; 2 | -------------------------------------------------------------------------------- /src/chains/evm/common/constants/chain.ts: -------------------------------------------------------------------------------- 1 | import { 2 | arbitrum, 3 | arbitrumSepolia, 4 | avalanche, 5 | avalancheFuji, 6 | base, 7 | baseSepolia, 8 | bsc, 9 | bscTestnet, 10 | mainnet, 11 | monadTestnet, 12 | polygon, 13 | sepolia, 14 | } from "viem/chains"; 15 | 16 | import type { EvmChainName, EvmFolksChainId } from "../types/chain.js"; 17 | import type { Chain } from "viem"; 18 | 19 | export const MAINNET_EVM_CHAIN_NAMES = ["AVALANCHE", "ETHEREUM", "BASE", "BSC", "ARBITRUM", "POLYGON"] as const; 20 | export const TESTNET_EVM_CHAIN_NAMES = [ 21 | "AVALANCHE_FUJI", 22 | "ETHEREUM_SEPOLIA", 23 | "BASE_SEPOLIA", 24 | "BSC_TESTNET", 25 | "ARBITRUM_SEPOLIA", 26 | "MONAD_TESTNET", 27 | ] as const; 28 | export const EVM_CHAIN_NAMES = [...MAINNET_EVM_CHAIN_NAMES, ...TESTNET_EVM_CHAIN_NAMES] as const; 29 | 30 | export const MAINNET_EVM_CHAIN_ID = { 31 | AVALANCHE: avalanche.id, 32 | ETHEREUM: mainnet.id, 33 | BASE: base.id, 34 | BSC: bsc.id, 35 | ARBITRUM: arbitrum.id, 36 | POLYGON: polygon.id, 37 | } as const; 38 | 39 | export const TESTNET_EVM_CHAIN_ID = { 40 | AVALANCHE_FUJI: avalancheFuji.id, 41 | ETHEREUM_SEPOLIA: sepolia.id, 42 | BASE_SEPOLIA: baseSepolia.id, 43 | BSC_TESTNET: bscTestnet.id, 44 | ARBITRUM_SEPOLIA: arbitrumSepolia.id, 45 | MONAD_TESTNET: monadTestnet.id, 46 | } as const; 47 | 48 | export const EVM_CHAIN_ID = { 49 | ...MAINNET_EVM_CHAIN_ID, 50 | ...TESTNET_EVM_CHAIN_ID, 51 | } as const satisfies Record; 52 | 53 | export const MAINNET_EVM_FOLKS_CHAIN_ID = { 54 | AVALANCHE: 100, 55 | ETHEREUM: 101, 56 | BASE: 102, 57 | BSC: 103, 58 | ARBITRUM: 104, 59 | POLYGON: 106, 60 | } as const; 61 | 62 | export const TESTNET_EVM_FOLKS_CHAIN_ID = { 63 | AVALANCHE_FUJI: 1, 64 | ETHEREUM_SEPOLIA: 6, 65 | BASE_SEPOLIA: 7, 66 | BSC_TESTNET: 3, 67 | ARBITRUM_SEPOLIA: 8, 68 | MONAD_TESTNET: 10, 69 | } as const; 70 | 71 | export const EVM_FOLKS_CHAIN_ID = { 72 | ...MAINNET_EVM_FOLKS_CHAIN_ID, 73 | ...TESTNET_EVM_FOLKS_CHAIN_ID, 74 | } as const satisfies Record; 75 | 76 | export const MAINNET_CHAIN_VIEM = { 77 | [EVM_FOLKS_CHAIN_ID.AVALANCHE]: avalanche, 78 | [EVM_FOLKS_CHAIN_ID.ETHEREUM]: mainnet, 79 | [EVM_FOLKS_CHAIN_ID.BASE]: base, 80 | [EVM_FOLKS_CHAIN_ID.BSC]: bsc, 81 | [EVM_FOLKS_CHAIN_ID.ARBITRUM]: arbitrum, 82 | [EVM_FOLKS_CHAIN_ID.POLYGON]: polygon, 83 | } as const; 84 | export const TESTNET_CHAIN_VIEM = { 85 | [EVM_FOLKS_CHAIN_ID.AVALANCHE_FUJI]: avalancheFuji, 86 | [EVM_FOLKS_CHAIN_ID.ETHEREUM_SEPOLIA]: sepolia, 87 | [EVM_FOLKS_CHAIN_ID.BASE_SEPOLIA]: baseSepolia, 88 | [EVM_FOLKS_CHAIN_ID.BSC_TESTNET]: bscTestnet, 89 | [EVM_FOLKS_CHAIN_ID.ARBITRUM_SEPOLIA]: arbitrumSepolia, 90 | [EVM_FOLKS_CHAIN_ID.MONAD_TESTNET]: monadTestnet, 91 | } as const; 92 | export const CHAIN_VIEM = { 93 | ...MAINNET_CHAIN_VIEM, 94 | ...TESTNET_CHAIN_VIEM, 95 | } as const satisfies Record; 96 | 97 | export const MAINNET_CHAIN_NODE = { 98 | [EVM_FOLKS_CHAIN_ID.AVALANCHE]: [...avalanche.rpcUrls.default.http], 99 | [EVM_FOLKS_CHAIN_ID.ETHEREUM]: [...mainnet.rpcUrls.default.http], 100 | [EVM_FOLKS_CHAIN_ID.BASE]: [...base.rpcUrls.default.http], 101 | [EVM_FOLKS_CHAIN_ID.BSC]: [...bsc.rpcUrls.default.http], 102 | [EVM_FOLKS_CHAIN_ID.ARBITRUM]: [...arbitrum.rpcUrls.default.http], 103 | [EVM_FOLKS_CHAIN_ID.POLYGON]: [...polygon.rpcUrls.default.http], 104 | }; 105 | export const TESTNET_CHAIN_NODE = { 106 | [EVM_FOLKS_CHAIN_ID.AVALANCHE_FUJI]: [...avalancheFuji.rpcUrls.default.http], 107 | [EVM_FOLKS_CHAIN_ID.ETHEREUM_SEPOLIA]: [...sepolia.rpcUrls.default.http], 108 | [EVM_FOLKS_CHAIN_ID.BASE_SEPOLIA]: [...baseSepolia.rpcUrls.default.http], 109 | [EVM_FOLKS_CHAIN_ID.BSC_TESTNET]: [...bscTestnet.rpcUrls.default.http], 110 | [EVM_FOLKS_CHAIN_ID.ARBITRUM_SEPOLIA]: [...arbitrumSepolia.rpcUrls.default.http], 111 | [EVM_FOLKS_CHAIN_ID.MONAD_TESTNET]: [...monadTestnet.rpcUrls.default.http], 112 | }; 113 | export const CHAIN_NODE = { 114 | ...MAINNET_CHAIN_NODE, 115 | ...TESTNET_CHAIN_NODE, 116 | } as const satisfies Record>; 117 | -------------------------------------------------------------------------------- /src/chains/evm/common/constants/contract.ts: -------------------------------------------------------------------------------- 1 | import type { EventParams } from "../types/contract.js"; 2 | 3 | export const defaultEventParams: EventParams = { 4 | fromBlock: "earliest", 5 | toBlock: "latest", 6 | strict: true, 7 | }; 8 | 9 | export const GAS_LIMIT_ESTIMATE_INCREASE = 50_000n; 10 | export const SEND_TOKEN_ACTION_RETURN_GAS_LIMIT = 500_000n; 11 | export const RECEIVER_VALUE_SLIPPAGE = 0.01; 12 | export const RETRY_REVERSE_GAS_LIMIT_SLIPPAGE = 0.05; 13 | export const UPDATE_USER_POINTS_IN_LOANS_GAS_LIMIT_SLIPPAGE = 0.1; 14 | export const UPDATE_ACCOUNT_POINTS_FOR_REWARDS_GAS_LIMIT_SLIPPAGE = 0.01; 15 | export const CLAIM_REWARDS_GAS_LIMIT_SLIPPAGE = 0; 16 | -------------------------------------------------------------------------------- /src/chains/evm/common/types/chain.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | EVM_CHAIN_ID, 3 | MAINNET_EVM_CHAIN_ID, 4 | TESTNET_EVM_CHAIN_ID, 5 | EVM_CHAIN_NAMES, 6 | MAINNET_EVM_CHAIN_NAMES, 7 | TESTNET_EVM_CHAIN_NAMES, 8 | EVM_FOLKS_CHAIN_ID, 9 | MAINNET_EVM_FOLKS_CHAIN_ID, 10 | TESTNET_EVM_FOLKS_CHAIN_ID, 11 | } from "../constants/chain.js"; 12 | 13 | export type MainnetEvmChainName = (typeof MAINNET_EVM_CHAIN_NAMES)[number]; 14 | export type TestnetEvmChainName = (typeof TESTNET_EVM_CHAIN_NAMES)[number]; 15 | export type EvmChainName = (typeof EVM_CHAIN_NAMES)[number]; 16 | 17 | export type MainnetEvmChainId = (typeof MAINNET_EVM_CHAIN_ID)[keyof typeof MAINNET_EVM_CHAIN_ID]; 18 | export type TestnetEvmChainId = (typeof TESTNET_EVM_CHAIN_ID)[keyof typeof TESTNET_EVM_CHAIN_ID]; 19 | export type EvmChainId = (typeof EVM_CHAIN_ID)[keyof typeof EVM_CHAIN_ID]; 20 | 21 | export type MainnetEvmFolksChainId = (typeof MAINNET_EVM_FOLKS_CHAIN_ID)[keyof typeof MAINNET_EVM_FOLKS_CHAIN_ID]; 22 | export type TestnetEvmFolksChainId = (typeof TESTNET_EVM_FOLKS_CHAIN_ID)[keyof typeof TESTNET_EVM_FOLKS_CHAIN_ID]; 23 | export type EvmFolksChainId = (typeof EVM_FOLKS_CHAIN_ID)[keyof typeof EVM_FOLKS_CHAIN_ID]; 24 | -------------------------------------------------------------------------------- /src/chains/evm/common/types/contract.ts: -------------------------------------------------------------------------------- 1 | import type { Abi, GetContractReturnType, Client, GetContractEventsParameters } from "viem"; 2 | 3 | type OmitWrite = Omit; 4 | export type GetReadContractReturnType = OmitWrite>; 5 | 6 | export type EventParams = Pick; 7 | export type GetEventParams = { eventParams: EventParams }; 8 | -------------------------------------------------------------------------------- /src/chains/evm/common/types/gmp.ts: -------------------------------------------------------------------------------- 1 | import type { EvmAddress, GenericAddress } from "../../../../common/types/address.js"; 2 | import type { FolksChainId } from "../../../../common/types/chain.js"; 3 | import type { AccountId } from "../../../../common/types/lending.js"; 4 | import type { AdapterType } from "../../../../common/types/message.js"; 5 | import type { Hex } from "viem"; 6 | 7 | type CCIPTokenAmount = { 8 | token: EvmAddress; 9 | amount: bigint; 10 | }; 11 | 12 | export type CCIPAny2EvmMessage = { 13 | messageId: Hex; 14 | sourceChainSelector: bigint; 15 | sender: GenericAddress; 16 | data: Hex; 17 | destTokenAmounts: Array; 18 | }; 19 | 20 | export type RetryMessageExtraArgs = { 21 | returnAdapterId: AdapterType; 22 | returnGasLimit: bigint; 23 | }; 24 | 25 | export type RetryMessageExtraArgsParams = Partial> | undefined; 26 | 27 | export type ReverseMessageExtraArgs = { 28 | accountId: AccountId; 29 | returnAdapterId: AdapterType; 30 | returnGasLimit: bigint; 31 | }; 32 | 33 | export type ReverseMessageExtraArgsParams = Partial> | undefined; 34 | 35 | export type MessageReceived = { 36 | messageId: Hex; 37 | sourceChainId: FolksChainId; 38 | sourceAddress: GenericAddress; 39 | handler: GenericAddress; 40 | payload: Hex; 41 | returnAdapterId: AdapterType; 42 | returnGasLimit: bigint; 43 | }; 44 | -------------------------------------------------------------------------------- /src/chains/evm/common/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./chain.js"; 2 | export * from "./contract.js"; 3 | export * from "./module.js"; 4 | -------------------------------------------------------------------------------- /src/chains/evm/common/types/module.ts: -------------------------------------------------------------------------------- 1 | import type { MessageReceived } from "./gmp.js"; 2 | import type { EvmAddress, GenericAddress } from "../../../../common/types/address.js"; 3 | import type { AccountId } from "../../../../common/types/lending.js"; 4 | import type { MessageParams } from "../../../../common/types/message.js"; 5 | import type { SpokeTokenData } from "../../../../common/types/token.js"; 6 | import type { PoolEpoch as PoolEpochV1 } from "../../hub/types/rewards-v1.js"; 7 | import type { PoolEpoch as PoolEpochV2, ReceiveRewardToken } from "../../hub/types/rewards-v2.js"; 8 | import type { Hex } from "viem"; 9 | 10 | export type PrepareCall = { 11 | msgValue: bigint; 12 | gasLimit: bigint; 13 | maxFeePerGas?: bigint; 14 | maxPriorityFeePerGas?: bigint; 15 | messageParams: MessageParams; 16 | }; 17 | 18 | export type PrepareCreateAccountCall = { 19 | accountId: AccountId; 20 | spokeCommonAddress: GenericAddress; 21 | } & PrepareCall; 22 | 23 | export type PrepareInviteAddressCall = { 24 | spokeCommonAddress: GenericAddress; 25 | } & PrepareCall; 26 | 27 | export type PrepareAcceptInviteAddressCall = { 28 | spokeCommonAddress: GenericAddress; 29 | } & PrepareCall; 30 | 31 | export type PrepareUnregisterAddressCall = { 32 | spokeCommonAddress: GenericAddress; 33 | } & PrepareCall; 34 | 35 | export type PrepareAddDelegateCall = { 36 | spokeCommonAddress: GenericAddress; 37 | } & PrepareCall; 38 | 39 | export type PrepareRemoveDelegateCall = { 40 | spokeCommonAddress: GenericAddress; 41 | } & PrepareCall; 42 | 43 | export type PrepareCreateLoanCall = { 44 | spokeCommonAddress: GenericAddress; 45 | } & PrepareCall; 46 | 47 | export type PrepareDeleteLoanCall = { 48 | spokeCommonAddress: GenericAddress; 49 | } & PrepareCall; 50 | 51 | export type PrepareCreateLoanAndDepositCall = { 52 | spokeTokenData: SpokeTokenData; 53 | } & PrepareCall; 54 | 55 | export type PrepareDepositCall = { 56 | spokeTokenData: SpokeTokenData; 57 | } & PrepareCall; 58 | 59 | export type PrepareWithdrawCall = { 60 | spokeCommonAddress: GenericAddress; 61 | } & PrepareCall; 62 | 63 | export type PrepareBorrowCall = { 64 | spokeCommonAddress: GenericAddress; 65 | } & PrepareCall; 66 | 67 | export type PrepareRepayCall = { 68 | spokeTokenData: SpokeTokenData; 69 | } & PrepareCall; 70 | 71 | export type PrepareRepayWithCollateralCall = { 72 | spokeCommonAddress: GenericAddress; 73 | } & PrepareCall; 74 | 75 | export type PrepareSwitchBorrowTypeCall = { 76 | spokeCommonAddress: GenericAddress; 77 | } & PrepareCall; 78 | 79 | export type PrepareLiquidateCall = { 80 | messageData: Hex; 81 | hubAddress: GenericAddress; 82 | } & Omit; 83 | 84 | export type PrepareUpdateUserPointsInLoansCall = { 85 | loanManagerAddress: GenericAddress; 86 | } & Omit; 87 | 88 | export type PrepareRetryMessageCall = { 89 | isHub: boolean; 90 | message: MessageReceived; 91 | extraArgs: Hex; 92 | bridgeRouterAddress: GenericAddress; 93 | } & Omit; 94 | 95 | export type PrepareReverseMessageCall = { 96 | isHub: boolean; 97 | message: MessageReceived; 98 | extraArgs: Hex; 99 | bridgeRouterAddress: GenericAddress; 100 | } & Omit; 101 | 102 | export type PrepareResendWormholeMessageCall = { 103 | vaaKey: { 104 | chainId: number; 105 | emitterAddress: GenericAddress; 106 | sequence: bigint; 107 | }; 108 | targetWormholeChainId: number; 109 | receiverValue: bigint; 110 | receiverGasLimit: bigint; 111 | deliveryProviderAddress: GenericAddress; 112 | wormholeRelayerAddress: GenericAddress; 113 | } & Omit; 114 | 115 | export type PrepareUpdateAccountsPointsForRewardsV1Call = { 116 | poolEpochs: Array; 117 | rewardsV1Address: GenericAddress; 118 | } & Omit; 119 | 120 | export type PrepareUpdateAccountsPointsForRewardsV2Call = { 121 | poolEpochs: Array; 122 | rewardsV2Address: GenericAddress; 123 | } & Omit; 124 | 125 | export type PrepareClaimRewardsV1Call = { 126 | poolEpochs: Array; 127 | receiver: EvmAddress; 128 | rewardsV1Address: GenericAddress; 129 | } & Omit; 130 | 131 | export type PrepareClaimRewardsV2Call = { 132 | poolEpochs: Array; 133 | rewardTokens: Array; 134 | spokeRewardsV2CommonAddress: GenericAddress; 135 | } & PrepareCall; 136 | -------------------------------------------------------------------------------- /src/chains/evm/common/types/tokens.ts: -------------------------------------------------------------------------------- 1 | import type { EvmFolksChainId } from "./chain.js"; 2 | import type { EvmAddress } from "../../../../common/types/address.js"; 3 | import type { RewardsTokenId } from "../../../../common/types/rewards.js"; 4 | import type { FolksTokenId as LendingTokenId, TokenType } from "../../../../common/types/token.js"; 5 | 6 | export type Erc20ContractSlot = { 7 | balanceOf: bigint; 8 | allowance: bigint; 9 | }; 10 | 11 | export type AllowanceStateOverride = { 12 | erc20Address: EvmAddress; 13 | stateDiff: Array<{ 14 | owner: EvmAddress; 15 | spender: EvmAddress; 16 | folksChainId: EvmFolksChainId; 17 | folksTokenId: LendingTokenId; 18 | tokenType: TokenType; 19 | amount: bigint; 20 | }>; 21 | }; 22 | 23 | export type BalanceOfStateOverride = { 24 | erc20Address: EvmAddress; 25 | stateDiff: Array<{ 26 | owner: EvmAddress; 27 | folksChainId: EvmFolksChainId; 28 | folksTokenId: LendingTokenId | RewardsTokenId; 29 | tokenType: TokenType; 30 | amount: bigint; 31 | }>; 32 | }; 33 | -------------------------------------------------------------------------------- /src/chains/evm/common/utils/chain.ts: -------------------------------------------------------------------------------- 1 | import { getBlock } from "viem/actions"; 2 | 3 | import { EVM_FOLKS_CHAIN_ID } from "../constants/chain.js"; 4 | 5 | import type { EvmAddress } from "../../../../common/types/address.js"; 6 | import type { EvmChainId } from "../types/chain.js"; 7 | import type { Account, Client, WalletClient } from "viem"; 8 | 9 | export function getEvmSignerAddress(signer: WalletClient): EvmAddress { 10 | if (signer.account?.address) return signer.account.address as EvmAddress; 11 | throw new Error("EVM Signer address is not set"); 12 | } 13 | 14 | export function getEvmSignerAccount(signer: WalletClient): Account { 15 | if (signer.account) return signer.account; 16 | throw new Error("EVM Signer account is not set"); 17 | } 18 | 19 | export const isEvmChainId = (chainId: number): chainId is EvmChainId => { 20 | // @ts-expect-error -- this is made on purpose to have the type predicate 21 | return Object.values(EVM_FOLKS_CHAIN_ID).includes(chainId); 22 | }; 23 | 24 | export async function getBlockTimestamp(provider: Client, blockNumber?: bigint): Promise { 25 | const block = await getBlock(provider, { blockNumber, includeTransactions: false }); 26 | return block.timestamp; 27 | } 28 | -------------------------------------------------------------------------------- /src/chains/evm/common/utils/contract.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseError, 3 | ContractFunctionRevertedError, 4 | encodeAbiParameters, 5 | getContract, 6 | keccak256, 7 | padHex, 8 | toHex, 9 | } from "viem"; 10 | 11 | import { ChainType } from "../../../../common/types/chain.js"; 12 | import { convertFromGenericAddress } from "../../../../common/utils/address.js"; 13 | import { CCIPDataAdapterAbi } from "../constants/abi/ccip-data-adapter-abi.js"; 14 | import { ERC20Abi } from "../constants/abi/erc-20-abi.js"; 15 | import { WormholeDataAdapterAbi } from "../constants/abi/wormhole-data-adapter-abi.js"; 16 | import { IWormholeRelayerAbi } from "../constants/abi/wormhole-relayer-abi.js"; 17 | 18 | import { getEvmSignerAccount, getEvmSignerAddress } from "./chain.js"; 19 | 20 | import type { EvmAddress, GenericAddress } from "../../../../common/types/address.js"; 21 | import type { GetReadContractReturnType } from "../types/contract.js"; 22 | import type { Client, GetContractReturnType, Hex, WalletClient } from "viem"; 23 | 24 | export function getERC20Contract(provider: Client, address: GenericAddress, signer: WalletClient) { 25 | return getContract({ 26 | abi: ERC20Abi, 27 | address: convertFromGenericAddress(address, ChainType.EVM), 28 | client: { wallet: signer, public: provider }, 29 | }); 30 | } 31 | 32 | export async function sendERC20Approve( 33 | provider: Client, 34 | address: GenericAddress, 35 | signer: WalletClient, 36 | spender: EvmAddress, 37 | amount: bigint, 38 | ): Promise { 39 | const erc20 = getERC20Contract(provider, address, signer); 40 | const allowance = await erc20.read.allowance([getEvmSignerAddress(signer), spender]); 41 | 42 | // approve if not enough 43 | if (allowance < amount) 44 | return await erc20.write.approve([spender, BigInt(amount)], { 45 | account: getEvmSignerAccount(signer), 46 | chain: signer.chain, 47 | }); 48 | return null; 49 | } 50 | 51 | export function getWormholeRelayerContract( 52 | provider: Client, 53 | address: GenericAddress, 54 | ): GetReadContractReturnType; 55 | export function getWormholeRelayerContract( 56 | provider: Client, 57 | address: GenericAddress, 58 | signer: WalletClient, 59 | ): GetContractReturnType; 60 | export function getWormholeRelayerContract( 61 | provider: Client, 62 | address: GenericAddress, 63 | signer?: WalletClient, 64 | ): GetReadContractReturnType | GetContractReturnType { 65 | return getContract({ 66 | abi: IWormholeRelayerAbi, 67 | address: convertFromGenericAddress(address, ChainType.EVM), 68 | client: { wallet: signer, public: provider }, 69 | }); 70 | } 71 | 72 | export function getWormholeDataAdapterContract( 73 | provider: Client, 74 | address: GenericAddress, 75 | ): GetReadContractReturnType { 76 | return getContract({ 77 | abi: WormholeDataAdapterAbi, 78 | address: convertFromGenericAddress(address, ChainType.EVM), 79 | client: { public: provider }, 80 | }); 81 | } 82 | 83 | export function getCCIPDataAdapterContract( 84 | provider: Client, 85 | address: GenericAddress, 86 | ): GetReadContractReturnType { 87 | return getContract({ 88 | abi: CCIPDataAdapterAbi, 89 | address: convertFromGenericAddress(address, ChainType.EVM), 90 | client: { public: provider }, 91 | }); 92 | } 93 | 94 | export function extractRevertErrorName(err: unknown): string | undefined { 95 | if (err instanceof BaseError) { 96 | const revertError = err.walk((err) => err instanceof ContractFunctionRevertedError); 97 | if (revertError instanceof ContractFunctionRevertedError && revertError.data?.errorName) { 98 | return revertError.data.errorName; 99 | } 100 | } 101 | } 102 | 103 | export function encodeErc20AccountData(data: { isFrozen: boolean; amount: bigint }) { 104 | const bitFlag = data.isFrozen ? 1n : 0n; 105 | const packed = (data.amount << 8n) | bitFlag; 106 | return padHex(toHex(packed), { size: 32 }); 107 | } 108 | 109 | export function getBalanceOfSlotHash(address: EvmAddress, slot: bigint) { 110 | return keccak256(encodeAbiParameters([{ type: "address" }, { type: "uint256" }], [address, slot])); 111 | } 112 | 113 | export function getAllowanceSlotHash(owner: EvmAddress, spender: EvmAddress, slot: bigint) { 114 | return keccak256( 115 | encodeAbiParameters( 116 | [{ type: "address" }, { type: "bytes32" }], 117 | [spender, keccak256(encodeAbiParameters([{ type: "address" }, { type: "uint256" }], [owner, slot]))], 118 | ), 119 | ); 120 | } 121 | -------------------------------------------------------------------------------- /src/chains/evm/common/utils/gmp.ts: -------------------------------------------------------------------------------- 1 | import { concat } from "viem"; 2 | 3 | import { UINT16_LENGTH, UINT256_LENGTH } from "../../../../common/constants/bytes.js"; 4 | import { convertNumberToBytes } from "../../../../common/utils/bytes.js"; 5 | 6 | import type { GenericAddress } from "../../../../common/types/address.js"; 7 | import type { AdapterType } from "../../../../common/types/message.js"; 8 | import type { RetryMessageExtraArgs, ReverseMessageExtraArgs } from "../types/gmp.js"; 9 | import type { Hex } from "viem"; 10 | 11 | export function encodeEvmPayloadWithMetadata( 12 | returnAdapterId: AdapterType, 13 | returnGasLimit: bigint, 14 | sender: GenericAddress, 15 | handler: GenericAddress, 16 | payload: Hex, 17 | ): Hex { 18 | return concat([ 19 | convertNumberToBytes(returnAdapterId, UINT16_LENGTH), 20 | convertNumberToBytes(returnGasLimit, UINT256_LENGTH), 21 | sender, 22 | handler, 23 | payload, 24 | ]); 25 | } 26 | 27 | export function encodeRetryMessageExtraArgs(extraArgs?: RetryMessageExtraArgs): Hex { 28 | if (extraArgs === undefined) return "0x"; 29 | const { returnAdapterId, returnGasLimit } = extraArgs; 30 | return concat([ 31 | convertNumberToBytes(returnAdapterId, UINT16_LENGTH), 32 | convertNumberToBytes(returnGasLimit, UINT256_LENGTH), 33 | ]); 34 | } 35 | 36 | export function encodeReverseMessageExtraArgs(extraArgs?: ReverseMessageExtraArgs): Hex { 37 | if (extraArgs === undefined) return "0x"; 38 | const { accountId, returnAdapterId, returnGasLimit } = extraArgs; 39 | return concat([ 40 | accountId, 41 | convertNumberToBytes(returnAdapterId, UINT16_LENGTH), 42 | convertNumberToBytes(returnGasLimit, UINT256_LENGTH), 43 | ]); 44 | } 45 | -------------------------------------------------------------------------------- /src/chains/evm/common/utils/provider.ts: -------------------------------------------------------------------------------- 1 | import { createClient, fallback, http } from "viem"; 2 | 3 | import { CHAIN_VIEM, CHAIN_NODE, EVM_FOLKS_CHAIN_ID } from "../constants/chain.js"; 4 | 5 | import { isEvmChainId } from "./chain.js"; 6 | 7 | import type { FolksChainId } from "../../../../common/types/chain.js"; 8 | import type { EvmChainId } from "../types/chain.js"; 9 | import type { Client } from "viem"; 10 | 11 | export function initProviders(customProvider: Partial>): Record { 12 | return Object.fromEntries( 13 | Object.values(EVM_FOLKS_CHAIN_ID).map((evmFolksChainId) => { 14 | return [ 15 | evmFolksChainId, 16 | customProvider[evmFolksChainId] ?? 17 | createClient({ 18 | chain: CHAIN_VIEM[evmFolksChainId], 19 | transport: fallback(CHAIN_NODE[evmFolksChainId].map((url: string) => http(url))), 20 | }), 21 | ]; 22 | }), 23 | ) as Record; 24 | } 25 | 26 | export function getChainId(provider: Client): EvmChainId { 27 | const chainId = provider.chain?.id; 28 | if (chainId === undefined) throw new Error("EVM provider chain id is undefined"); 29 | if (!isEvmChainId(chainId)) throw new Error(`EVM provider chain id is not supported: ${chainId}`); 30 | 31 | return chainId; 32 | } 33 | -------------------------------------------------------------------------------- /src/chains/evm/common/utils/tokens.ts: -------------------------------------------------------------------------------- 1 | import { encodeAbiParameters } from "viem"; 2 | 3 | import { FOLKS_CHAIN_ID } from "../../../../common/constants/chain.js"; 4 | import { NetworkType } from "../../../../common/types/chain.js"; 5 | import { MAINNET_FOLKS_TOKEN_ID } from "../../../../common/types/token.js"; 6 | import { getSpokeEvmTokenAddress } from "../../spoke/utils/contract.js"; 7 | import { CONTRACT_SLOT } from "../constants/tokens.js"; 8 | 9 | import { encodeErc20AccountData, getAllowanceSlotHash, getBalanceOfSlotHash } from "./contract.js"; 10 | 11 | import type { EvmAddress } from "../../../../common/types/address.js"; 12 | import type { RewardsTokenId } from "../../../../common/types/rewards.js"; 13 | import type { FolksTokenId as LendingTokenId } from "../../../../common/types/token.js"; 14 | import type { EvmFolksChainId } from "../types/chain.js"; 15 | import type { AllowanceStateOverride, BalanceOfStateOverride } from "../types/tokens.js"; 16 | import type { StateOverride } from "viem"; 17 | 18 | export function getContractSlot(folksChainId: EvmFolksChainId) { 19 | const contractSlot = CONTRACT_SLOT[folksChainId]; 20 | if (!contractSlot) { 21 | throw new Error(`Contract slot not found for folksChainId: ${folksChainId}`); 22 | } 23 | return contractSlot; 24 | } 25 | 26 | export function getFolksTokenContractSlot( 27 | folksChainId: EvmFolksChainId, 28 | folksTokenId: LendingTokenId | RewardsTokenId, 29 | ) { 30 | const contractSlot = getContractSlot(folksChainId); 31 | 32 | const folksTokenContractSlot = contractSlot.erc20[folksTokenId]; 33 | if (!folksTokenContractSlot) { 34 | throw new Error(`Contract slot not found for folksTokenId: ${folksTokenId}`); 35 | } 36 | return folksTokenContractSlot; 37 | } 38 | 39 | export function getAllowanceStateOverride(allowanceStatesOverride: Array): StateOverride { 40 | return allowanceStatesOverride.map((aso) => ({ 41 | address: aso.erc20Address, 42 | stateDiff: aso.stateDiff.map((sd) => ({ 43 | slot: getAllowanceSlotHash( 44 | sd.owner, 45 | sd.spender, 46 | getFolksTokenContractSlot(sd.folksChainId, sd.folksTokenId).allowance, 47 | ), 48 | value: encodeAbiParameters([{ type: "uint256" }], [sd.amount]), 49 | })), 50 | })); 51 | } 52 | 53 | function encodeBalanceOfValue(amount: bigint, erc20Address?: EvmAddress) { 54 | // aUSD_ava or aUSD_pol 55 | if ( 56 | erc20Address && 57 | (erc20Address === 58 | getSpokeEvmTokenAddress(NetworkType.MAINNET, FOLKS_CHAIN_ID.AVALANCHE, MAINNET_FOLKS_TOKEN_ID.aUSD_ava) || 59 | erc20Address === 60 | getSpokeEvmTokenAddress(NetworkType.MAINNET, FOLKS_CHAIN_ID.POLYGON, MAINNET_FOLKS_TOKEN_ID.aUSD_pol)) 61 | ) 62 | return encodeErc20AccountData({ isFrozen: false, amount }); 63 | return encodeAbiParameters([{ type: "uint256" }], [amount]); 64 | } 65 | 66 | export function getBalanceOfStateOverride(balanceOfStatesOverride: Array): StateOverride { 67 | return balanceOfStatesOverride.map((bso) => ({ 68 | address: bso.erc20Address, 69 | stateDiff: bso.stateDiff.map((sd) => ({ 70 | slot: getBalanceOfSlotHash(sd.owner, getFolksTokenContractSlot(sd.folksChainId, sd.folksTokenId).balanceOf), 71 | value: encodeBalanceOfValue(sd.amount, bso.erc20Address), 72 | })), 73 | })); 74 | } 75 | -------------------------------------------------------------------------------- /src/chains/evm/hub/constants/abi/hub-abi.ts: -------------------------------------------------------------------------------- 1 | export const HubAbi = [ 2 | { 3 | inputs: [ 4 | { 5 | internalType: "contract IBridgeRouter", 6 | name: "bridgeRouter", 7 | type: "address", 8 | }, 9 | { 10 | internalType: "contract ISpokeManager", 11 | name: "spokeManager_", 12 | type: "address", 13 | }, 14 | { 15 | internalType: "contract IAccountManager", 16 | name: "accountManager_", 17 | type: "address", 18 | }, 19 | { 20 | internalType: "contract ILoanManager", 21 | name: "loanManager_", 22 | type: "address", 23 | }, 24 | { internalType: "uint16", name: "hubChainId_", type: "uint16" }, 25 | ], 26 | stateMutability: "nonpayable", 27 | type: "constructor", 28 | }, 29 | { 30 | inputs: [{ internalType: "bytes32", name: "messageId", type: "bytes32" }], 31 | name: "CannotReceiveMessage", 32 | type: "error", 33 | }, 34 | { 35 | inputs: [{ internalType: "bytes32", name: "messageId", type: "bytes32" }], 36 | name: "CannotRetryMessage", 37 | type: "error", 38 | }, 39 | { 40 | inputs: [{ internalType: "bytes32", name: "messageId", type: "bytes32" }], 41 | name: "CannotReverseMessage", 42 | type: "error", 43 | }, 44 | { 45 | inputs: [{ internalType: "address", name: "router", type: "address" }], 46 | name: "InvalidBridgeRouter", 47 | type: "error", 48 | }, 49 | { 50 | inputs: [ 51 | { internalType: "address", name: "expected", type: "address" }, 52 | { internalType: "address", name: "actual", type: "address" }, 53 | ], 54 | name: "InvalidTokenFeeClaimer", 55 | type: "error", 56 | }, 57 | { 58 | inputs: [ 59 | { internalType: "bytes32", name: "accountId", type: "bytes32" }, 60 | { internalType: "address", name: "addr", type: "address" }, 61 | ], 62 | name: "NoPermissionOnHub", 63 | type: "error", 64 | }, 65 | { 66 | inputs: [ 67 | { internalType: "bytes32", name: "accountId", type: "bytes32" }, 68 | { internalType: "uint16", name: "chainId", type: "uint16" }, 69 | { internalType: "bytes32", name: "addr", type: "bytes32" }, 70 | ], 71 | name: "NotRegisteredToAccount", 72 | type: "error", 73 | }, 74 | { inputs: [], name: "ReentrancyGuardReentrantCall", type: "error" }, 75 | { 76 | inputs: [ 77 | { internalType: "uint16", name: "chainId", type: "uint16" }, 78 | { internalType: "bytes32", name: "addr", type: "bytes32" }, 79 | ], 80 | name: "SpokeUnknown", 81 | type: "error", 82 | }, 83 | { 84 | inputs: [{ internalType: "enum Messages.Action", name: "action", type: "uint8" }], 85 | name: "UnsupportedDirectOperation", 86 | type: "error", 87 | }, 88 | { 89 | inputs: [], 90 | name: "accountManager", 91 | outputs: [{ internalType: "contract IAccountManager", name: "", type: "address" }], 92 | stateMutability: "view", 93 | type: "function", 94 | }, 95 | { 96 | inputs: [ 97 | { internalType: "uint8", name: "poolId", type: "uint8" }, 98 | { internalType: "uint16", name: "chainId", type: "uint16" }, 99 | { internalType: "uint16", name: "returnAdapterId", type: "uint16" }, 100 | { internalType: "uint256", name: "returnGasLimit", type: "uint256" }, 101 | ], 102 | name: "claimTokenFees", 103 | outputs: [], 104 | stateMutability: "nonpayable", 105 | type: "function", 106 | }, 107 | { 108 | inputs: [ 109 | { internalType: "enum Messages.Action", name: "action", type: "uint8" }, 110 | { internalType: "bytes32", name: "accountId", type: "bytes32" }, 111 | { internalType: "bytes", name: "data", type: "bytes" }, 112 | ], 113 | name: "directOperation", 114 | outputs: [], 115 | stateMutability: "nonpayable", 116 | type: "function", 117 | }, 118 | { 119 | inputs: [], 120 | name: "getBridgeRouter", 121 | outputs: [{ internalType: "address", name: "", type: "address" }], 122 | stateMutability: "view", 123 | type: "function", 124 | }, 125 | { 126 | inputs: [], 127 | name: "hubChainId", 128 | outputs: [{ internalType: "uint16", name: "", type: "uint16" }], 129 | stateMutability: "view", 130 | type: "function", 131 | }, 132 | { 133 | inputs: [], 134 | name: "loanManager", 135 | outputs: [{ internalType: "contract ILoanManager", name: "", type: "address" }], 136 | stateMutability: "view", 137 | type: "function", 138 | }, 139 | { 140 | inputs: [ 141 | { 142 | components: [ 143 | { internalType: "bytes32", name: "messageId", type: "bytes32" }, 144 | { internalType: "uint16", name: "sourceChainId", type: "uint16" }, 145 | { internalType: "bytes32", name: "sourceAddress", type: "bytes32" }, 146 | { internalType: "bytes32", name: "handler", type: "bytes32" }, 147 | { internalType: "bytes", name: "payload", type: "bytes" }, 148 | { internalType: "uint16", name: "returnAdapterId", type: "uint16" }, 149 | { internalType: "uint256", name: "returnGasLimit", type: "uint256" }, 150 | ], 151 | internalType: "struct Messages.MessageReceived", 152 | name: "message", 153 | type: "tuple", 154 | }, 155 | ], 156 | name: "receiveMessage", 157 | outputs: [], 158 | stateMutability: "nonpayable", 159 | type: "function", 160 | }, 161 | { 162 | inputs: [ 163 | { 164 | components: [ 165 | { internalType: "bytes32", name: "messageId", type: "bytes32" }, 166 | { internalType: "uint16", name: "sourceChainId", type: "uint16" }, 167 | { internalType: "bytes32", name: "sourceAddress", type: "bytes32" }, 168 | { internalType: "bytes32", name: "handler", type: "bytes32" }, 169 | { internalType: "bytes", name: "payload", type: "bytes" }, 170 | { internalType: "uint16", name: "returnAdapterId", type: "uint16" }, 171 | { internalType: "uint256", name: "returnGasLimit", type: "uint256" }, 172 | ], 173 | internalType: "struct Messages.MessageReceived", 174 | name: "message", 175 | type: "tuple", 176 | }, 177 | { internalType: "address", name: "caller", type: "address" }, 178 | { internalType: "bytes", name: "extraArgs", type: "bytes" }, 179 | ], 180 | name: "retryMessage", 181 | outputs: [], 182 | stateMutability: "nonpayable", 183 | type: "function", 184 | }, 185 | { 186 | inputs: [ 187 | { 188 | components: [ 189 | { internalType: "bytes32", name: "messageId", type: "bytes32" }, 190 | { internalType: "uint16", name: "sourceChainId", type: "uint16" }, 191 | { internalType: "bytes32", name: "sourceAddress", type: "bytes32" }, 192 | { internalType: "bytes32", name: "handler", type: "bytes32" }, 193 | { internalType: "bytes", name: "payload", type: "bytes" }, 194 | { internalType: "uint16", name: "returnAdapterId", type: "uint16" }, 195 | { internalType: "uint256", name: "returnGasLimit", type: "uint256" }, 196 | ], 197 | internalType: "struct Messages.MessageReceived", 198 | name: "message", 199 | type: "tuple", 200 | }, 201 | { internalType: "address", name: "caller", type: "address" }, 202 | { internalType: "bytes", name: "extraArgs", type: "bytes" }, 203 | ], 204 | name: "reverseMessage", 205 | outputs: [], 206 | stateMutability: "nonpayable", 207 | type: "function", 208 | }, 209 | { 210 | inputs: [], 211 | name: "spokeManager", 212 | outputs: [{ internalType: "contract ISpokeManager", name: "", type: "address" }], 213 | stateMutability: "view", 214 | type: "function", 215 | }, 216 | ] as const; 217 | -------------------------------------------------------------------------------- /src/chains/evm/hub/constants/abi/node-manager-abi.ts: -------------------------------------------------------------------------------- 1 | export const NodeManagerAbi = [ 2 | { 3 | inputs: [{ internalType: "uint256", name: "deviation", type: "uint256" }], 4 | name: "DeviationToleranceExceeded", 5 | type: "error", 6 | }, 7 | { 8 | inputs: [{ internalType: "uint256", name: "deviation", type: "uint256" }], 9 | name: "DeviationToleranceExceeded", 10 | type: "error", 11 | }, 12 | { 13 | inputs: [ 14 | { 15 | components: [ 16 | { 17 | internalType: "enum NodeDefinition.NodeType", 18 | name: "nodeType", 19 | type: "uint8", 20 | }, 21 | { internalType: "bytes", name: "parameters", type: "bytes" }, 22 | { internalType: "bytes32[]", name: "parents", type: "bytes32[]" }, 23 | ], 24 | internalType: "struct NodeDefinition.Data", 25 | name: "nodeDefinition", 26 | type: "tuple", 27 | }, 28 | ], 29 | name: "InvalidNodeDefinition", 30 | type: "error", 31 | }, 32 | { inputs: [], name: "MathOverflowedMulDiv", type: "error" }, 33 | { 34 | inputs: [{ internalType: "bytes32", name: "nodeId", type: "bytes32" }], 35 | name: "NodeAlreadyRegistered", 36 | type: "error", 37 | }, 38 | { 39 | inputs: [{ internalType: "bytes32", name: "nodeId", type: "bytes32" }], 40 | name: "NodeNotRegistered", 41 | type: "error", 42 | }, 43 | { 44 | inputs: [{ internalType: "int256", name: "value", type: "int256" }], 45 | name: "SafeCastOverflowedIntToUint", 46 | type: "error", 47 | }, 48 | { 49 | inputs: [ 50 | { 51 | internalType: "enum NodeDefinition.NodeType", 52 | name: "nodeType", 53 | type: "uint8", 54 | }, 55 | ], 56 | name: "SameOracle", 57 | type: "error", 58 | }, 59 | { inputs: [], name: "StalenessToleranceExceeded", type: "error" }, 60 | { 61 | inputs: [{ internalType: "bytes32", name: "nodeId", type: "bytes32" }], 62 | name: "UnprocessableNode", 63 | type: "error", 64 | }, 65 | { 66 | inputs: [ 67 | { 68 | internalType: "enum ReducerNode.Operation", 69 | name: "operation", 70 | type: "uint8", 71 | }, 72 | ], 73 | name: "UnsupportedOperation", 74 | type: "error", 75 | }, 76 | { 77 | inputs: [ 78 | { 79 | internalType: "enum NodeDefinition.NodeType", 80 | name: "nodeType", 81 | type: "uint8", 82 | }, 83 | ], 84 | name: "ZeroPrice", 85 | type: "error", 86 | }, 87 | { 88 | inputs: [ 89 | { 90 | internalType: "enum NodeDefinition.NodeType", 91 | name: "nodeType", 92 | type: "uint8", 93 | }, 94 | ], 95 | name: "ZeroPrice", 96 | type: "error", 97 | }, 98 | { 99 | anonymous: false, 100 | inputs: [ 101 | { 102 | indexed: false, 103 | internalType: "bytes32", 104 | name: "nodeId", 105 | type: "bytes32", 106 | }, 107 | { 108 | indexed: false, 109 | internalType: "enum NodeDefinition.NodeType", 110 | name: "nodeType", 111 | type: "uint8", 112 | }, 113 | { 114 | indexed: false, 115 | internalType: "bytes", 116 | name: "parameters", 117 | type: "bytes", 118 | }, 119 | { 120 | indexed: false, 121 | internalType: "bytes32[]", 122 | name: "parents", 123 | type: "bytes32[]", 124 | }, 125 | ], 126 | name: "NodeRegistered", 127 | type: "event", 128 | }, 129 | { 130 | inputs: [{ internalType: "bytes32", name: "nodeId", type: "bytes32" }], 131 | name: "getNode", 132 | outputs: [ 133 | { 134 | components: [ 135 | { 136 | internalType: "enum NodeDefinition.NodeType", 137 | name: "nodeType", 138 | type: "uint8", 139 | }, 140 | { internalType: "bytes", name: "parameters", type: "bytes" }, 141 | { internalType: "bytes32[]", name: "parents", type: "bytes32[]" }, 142 | ], 143 | internalType: "struct NodeDefinition.Data", 144 | name: "node", 145 | type: "tuple", 146 | }, 147 | ], 148 | stateMutability: "pure", 149 | type: "function", 150 | }, 151 | { 152 | inputs: [ 153 | { 154 | internalType: "enum NodeDefinition.NodeType", 155 | name: "nodeType", 156 | type: "uint8", 157 | }, 158 | { internalType: "bytes", name: "parameters", type: "bytes" }, 159 | { internalType: "bytes32[]", name: "parents", type: "bytes32[]" }, 160 | ], 161 | name: "getNodeId", 162 | outputs: [{ internalType: "bytes32", name: "nodeId", type: "bytes32" }], 163 | stateMutability: "pure", 164 | type: "function", 165 | }, 166 | { 167 | inputs: [{ internalType: "bytes32", name: "nodeId", type: "bytes32" }], 168 | name: "isNodeRegistered", 169 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 170 | stateMutability: "view", 171 | type: "function", 172 | }, 173 | { 174 | inputs: [{ internalType: "bytes32", name: "nodeId", type: "bytes32" }], 175 | name: "process", 176 | outputs: [ 177 | { 178 | components: [ 179 | { internalType: "uint256", name: "price", type: "uint256" }, 180 | { internalType: "uint256", name: "timestamp", type: "uint256" }, 181 | { 182 | internalType: "enum NodeDefinition.NodeType", 183 | name: "nodeType", 184 | type: "uint8", 185 | }, 186 | { 187 | internalType: "uint256", 188 | name: "additionalParam1", 189 | type: "uint256", 190 | }, 191 | { 192 | internalType: "uint256", 193 | name: "additionalParam2", 194 | type: "uint256", 195 | }, 196 | ], 197 | internalType: "struct NodeOutput.Data", 198 | name: "node", 199 | type: "tuple", 200 | }, 201 | ], 202 | stateMutability: "view", 203 | type: "function", 204 | }, 205 | { 206 | inputs: [ 207 | { 208 | internalType: "enum NodeDefinition.NodeType", 209 | name: "nodeType", 210 | type: "uint8", 211 | }, 212 | { internalType: "bytes", name: "parameters", type: "bytes" }, 213 | { internalType: "bytes32[]", name: "parents", type: "bytes32[]" }, 214 | ], 215 | name: "registerNode", 216 | outputs: [{ internalType: "bytes32", name: "nodeId", type: "bytes32" }], 217 | stateMutability: "nonpayable", 218 | type: "function", 219 | }, 220 | { 221 | inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], 222 | name: "supportsInterface", 223 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 224 | stateMutability: "pure", 225 | type: "function", 226 | }, 227 | ] as const; 228 | -------------------------------------------------------------------------------- /src/chains/evm/hub/modules/folks-hub-account.ts: -------------------------------------------------------------------------------- 1 | import { multicall } from "viem/actions"; 2 | 3 | import { getFolksChainIdsByNetwork } from "../../../../common/utils/chain.js"; 4 | import { defaultEventParams } from "../../common/constants/contract.js"; 5 | import { extractRevertErrorName } from "../../common/utils/contract.js"; 6 | import { getHubChain } from "../utils/chain.js"; 7 | import { getAccountManagerContract } from "../utils/contract.js"; 8 | import { fetchInvitationByAddress } from "../utils/events.js"; 9 | 10 | import type { GenericAddress } from "../../../../common/types/address.js"; 11 | import type { FolksChainId, NetworkType } from "../../../../common/types/chain.js"; 12 | import type { AccountId } from "../../../../common/types/lending.js"; 13 | import type { AccountIdByAddress, AccountInfo } from "../types/account.js"; 14 | import type { Client } from "viem"; 15 | 16 | export async function getAccountInfo( 17 | provider: Client, 18 | network: NetworkType, 19 | accountId: AccountId, 20 | folksChainIds?: Array, 21 | ): Promise { 22 | const hubChain = getHubChain(network); 23 | const accountManager = getAccountManagerContract(provider, hubChain.accountManagerAddress); 24 | 25 | // get chain ids to check 26 | folksChainIds = folksChainIds ? folksChainIds : getFolksChainIdsByNetwork(network); 27 | 28 | // define return variable 29 | const accountInfo: AccountInfo = { 30 | registered: new Map(), 31 | invited: new Map(), 32 | }; 33 | 34 | // query for registered and invited addresses on each respective chain 35 | const registeredAddresses = await multicall(provider, { 36 | contracts: folksChainIds.map((folksChainId) => ({ 37 | address: accountManager.address, 38 | abi: accountManager.abi, 39 | functionName: "getAddressRegisteredToAccountOnChain", 40 | args: [accountId, folksChainId], 41 | })), 42 | allowFailure: true, 43 | }); 44 | 45 | const invitedAddresses = await multicall(provider, { 46 | contracts: folksChainIds.map((folksChainId) => ({ 47 | address: accountManager.address, 48 | abi: accountManager.abi, 49 | functionName: "getAddressInvitedToAccountOnChain", 50 | args: [accountId, folksChainId], 51 | })), 52 | allowFailure: true, 53 | }); 54 | 55 | for (const [index, result] of registeredAddresses.entries()) { 56 | const chainId = folksChainIds[index]; 57 | if (result.status === "success") accountInfo.registered.set(chainId, result.result as GenericAddress); 58 | } 59 | for (const [index, result] of invitedAddresses.entries()) { 60 | const chainId = folksChainIds[index]; 61 | if (result.status === "success") accountInfo.invited.set(chainId, result.result as GenericAddress); 62 | } 63 | 64 | return accountInfo; 65 | } 66 | 67 | export async function getAccountIdByAddress( 68 | provider: Client, 69 | network: NetworkType, 70 | address: GenericAddress, 71 | folksChainIds?: Array, 72 | ): Promise { 73 | const hubChain = getHubChain(network); 74 | const accountManager = getAccountManagerContract(provider, hubChain.accountManagerAddress); 75 | 76 | // get chain ids to check 77 | folksChainIds = folksChainIds ? folksChainIds : getFolksChainIdsByNetwork(network); 78 | 79 | const accountIds = await multicall(provider, { 80 | contracts: folksChainIds.map((folksChainId) => ({ 81 | address: accountManager.address, 82 | abi: accountManager.abi, 83 | functionName: "getAccountIdOfAddressOnChain", 84 | args: [address, folksChainId], 85 | })), 86 | allowFailure: true, 87 | }); 88 | 89 | const accountIdByAddress: AccountIdByAddress = []; 90 | 91 | for (const [index, result] of accountIds.entries()) { 92 | const folksChainId = folksChainIds[index]; 93 | if (result.status === "success") 94 | accountIdByAddress.push({ 95 | accountId: result.result as AccountId, 96 | folksChainId, 97 | }); 98 | } 99 | 100 | return accountIdByAddress; 101 | } 102 | 103 | export async function getAccountIdByAddressOnChain( 104 | provider: Client, 105 | network: NetworkType, 106 | address: GenericAddress, 107 | folksChainId: FolksChainId, 108 | ): Promise { 109 | const hubChain = getHubChain(network); 110 | const accountManager = getAccountManagerContract(provider, hubChain.accountManagerAddress); 111 | 112 | try { 113 | const accountId = await accountManager.read.getAccountIdOfAddressOnChain([address, folksChainId]); 114 | return accountId as AccountId; 115 | } catch (err: unknown) { 116 | const errorName = extractRevertErrorName(err); 117 | if (errorName === "NoAccountRegisteredTo") return null; 118 | throw err; 119 | } 120 | } 121 | 122 | export async function getInvitationByAddress( 123 | provider: Client, 124 | network: NetworkType, 125 | address: GenericAddress, 126 | folksChainId?: FolksChainId, 127 | ) { 128 | const hubChain = getHubChain(network); 129 | const accountManager = getAccountManagerContract(provider, hubChain.accountManagerAddress); 130 | 131 | return fetchInvitationByAddress({ 132 | accountManager, 133 | address, 134 | folksChainId, 135 | eventParams: defaultEventParams, 136 | }); 137 | } 138 | 139 | export async function isAccountCreated(provider: Client, network: NetworkType, accountId: AccountId): Promise { 140 | const hubChain = getHubChain(network); 141 | const accountManager = getAccountManagerContract(provider, hubChain.accountManagerAddress); 142 | 143 | return await accountManager.read.isAccountCreated([accountId]); 144 | } 145 | -------------------------------------------------------------------------------- /src/chains/evm/hub/modules/folks-hub-gmp.ts: -------------------------------------------------------------------------------- 1 | import { FINALITY } from "../../../../common/constants/message.js"; 2 | import { getRandomGenericAddress } from "../../../../common/utils/address.js"; 3 | import { getRandomBytes } from "../../../../common/utils/bytes.js"; 4 | import { increaseByPercent } from "../../../../common/utils/math-lib.js"; 5 | import { RETRY_REVERSE_GAS_LIMIT_SLIPPAGE } from "../../common/constants/contract.js"; 6 | import { getEvmSignerAccount } from "../../common/utils/chain.js"; 7 | import { buildMessageParams, buildSendTokenExtraArgsWhenRemoving } from "../../common/utils/message.js"; 8 | import { getHubChainBridgeRouterAddress } from "../utils/chain.js"; 9 | import { getBridgeRouterHubContract } from "../utils/contract.js"; 10 | 11 | import type { EvmAddress } from "../../../../common/types/address.js"; 12 | import type { FolksChainId } from "../../../../common/types/chain.js"; 13 | import type { MessageId } from "../../../../common/types/gmp.js"; 14 | import type { AdapterType, MessageToSend } from "../../../../common/types/message.js"; 15 | import type { MessageReceived } from "../../common/types/gmp.js"; 16 | import type { PrepareRetryMessageCall, PrepareReverseMessageCall } from "../../common/types/module.js"; 17 | import type { HubChain } from "../types/chain.js"; 18 | import type { HubTokenData } from "../types/token.js"; 19 | import type { Client, EstimateGasParameters, Hex, WalletClient } from "viem"; 20 | 21 | export const prepare = { 22 | async retryMessage( 23 | provider: Client, 24 | sender: EvmAddress, 25 | adapterId: AdapterType, 26 | messageId: MessageId, 27 | message: MessageReceived, 28 | extraArgs: Hex, 29 | value: bigint, 30 | hubChain: HubChain, 31 | isRewards = false, 32 | transactionOptions: EstimateGasParameters = { 33 | account: sender, 34 | }, 35 | ): Promise { 36 | const bridgeRouterAddress = getHubChainBridgeRouterAddress(hubChain, isRewards); 37 | const bridgeRouter = getBridgeRouterHubContract(provider, bridgeRouterAddress); 38 | 39 | const gasLimit = await bridgeRouter.estimateGas.retryMessage([adapterId, messageId, message, extraArgs], { 40 | ...transactionOptions, 41 | value, 42 | }); 43 | 44 | return { 45 | gasLimit: increaseByPercent(gasLimit, RETRY_REVERSE_GAS_LIMIT_SLIPPAGE), 46 | msgValue: value, 47 | isHub: true, 48 | message, 49 | extraArgs, 50 | bridgeRouterAddress: hubChain.bridgeRouterAddress, 51 | }; 52 | }, 53 | 54 | async reverseMessage( 55 | provider: Client, 56 | sender: EvmAddress, 57 | adapterId: AdapterType, 58 | messageId: MessageId, 59 | message: MessageReceived, 60 | extraArgs: Hex, 61 | value: bigint, 62 | hubChain: HubChain, 63 | isRewards = false, 64 | transactionOptions: EstimateGasParameters = { 65 | account: sender, 66 | }, 67 | ): Promise { 68 | const bridgeRouterAddress = getHubChainBridgeRouterAddress(hubChain, isRewards); 69 | const bridgeRouter = getBridgeRouterHubContract(provider, bridgeRouterAddress); 70 | 71 | const gasLimit = await bridgeRouter.estimateGas.reverseMessage([adapterId, messageId, message, extraArgs], { 72 | ...transactionOptions, 73 | value, 74 | }); 75 | 76 | return { 77 | gasLimit: increaseByPercent(gasLimit, RETRY_REVERSE_GAS_LIMIT_SLIPPAGE), 78 | msgValue: value, 79 | isHub: true, 80 | message, 81 | extraArgs, 82 | bridgeRouterAddress: hubChain.bridgeRouterAddress, 83 | }; 84 | }, 85 | }; 86 | 87 | export const write = { 88 | async retryMessage( 89 | provider: Client, 90 | signer: WalletClient, 91 | adapterId: AdapterType, 92 | messageId: MessageId, 93 | prepareCall: PrepareRetryMessageCall, 94 | ) { 95 | const { gasLimit, maxFeePerGas, maxPriorityFeePerGas, msgValue, message, extraArgs, bridgeRouterAddress } = 96 | prepareCall; 97 | 98 | const bridgeRouter = getBridgeRouterHubContract(provider, bridgeRouterAddress, signer); 99 | 100 | return await bridgeRouter.write.retryMessage([adapterId, messageId, message, extraArgs], { 101 | account: getEvmSignerAccount(signer), 102 | chain: signer.chain, 103 | gas: gasLimit, 104 | maxFeePerGas, 105 | maxPriorityFeePerGas, 106 | value: msgValue, 107 | }); 108 | }, 109 | 110 | async reverseMessage( 111 | provider: Client, 112 | signer: WalletClient, 113 | adapterId: AdapterType, 114 | messageId: MessageId, 115 | prepareCall: PrepareReverseMessageCall, 116 | ) { 117 | const { gasLimit, maxFeePerGas, maxPriorityFeePerGas, msgValue, message, extraArgs, bridgeRouterAddress } = 118 | prepareCall; 119 | 120 | const bridgeRouter = getBridgeRouterHubContract(provider, bridgeRouterAddress, signer); 121 | 122 | return await bridgeRouter.write.reverseMessage([adapterId, messageId, message, extraArgs], { 123 | account: getEvmSignerAccount(signer), 124 | chain: signer.chain, 125 | gas: gasLimit, 126 | maxFeePerGas, 127 | maxPriorityFeePerGas, 128 | value: msgValue, 129 | }); 130 | }, 131 | }; 132 | 133 | export async function getSendMessageFee( 134 | provider: Client, 135 | toFolksChainId: FolksChainId, 136 | adapterId: AdapterType, 137 | gasLimit: bigint, 138 | hubChain: HubChain, 139 | hubTokenData?: HubTokenData, 140 | isRewards = false, 141 | ): Promise { 142 | const bridgeRouterAddress = getHubChainBridgeRouterAddress(hubChain, isRewards); 143 | const hubBridgeRouter = getBridgeRouterHubContract(provider, bridgeRouterAddress); 144 | 145 | // construct return message 146 | const message: MessageToSend = { 147 | params: buildMessageParams({ 148 | adapters: { 149 | adapterId, 150 | returnAdapterId: 0 as AdapterType, 151 | }, 152 | gasLimit, 153 | }), 154 | sender: hubChain.hubAddress, 155 | destinationChainId: toFolksChainId, 156 | handler: getRandomGenericAddress(), 157 | payload: getRandomBytes(256), 158 | finalityLevel: FINALITY.FINALISED, 159 | extraArgs: hubTokenData 160 | ? buildSendTokenExtraArgsWhenRemoving(getRandomGenericAddress(), hubTokenData.token, 1n) 161 | : "0x", 162 | }; 163 | 164 | // get return adapter fee 165 | return await hubBridgeRouter.read.getSendFee([message]); 166 | } 167 | -------------------------------------------------------------------------------- /src/chains/evm/hub/modules/folks-hub-oracle.ts: -------------------------------------------------------------------------------- 1 | import { multicall } from "viem/actions"; 2 | 3 | import { getHubChain } from "../utils/chain.js"; 4 | import { getNodeManagerContract, getOracleManagerContract } from "../utils/contract.js"; 5 | 6 | import type { NetworkType } from "../../../../common/types/chain.js"; 7 | import type { NodeManagerAbi } from "../constants/abi/node-manager-abi.js"; 8 | import type { OracleManagerAbi } from "../constants/abi/oracle-manager-abi.js"; 9 | import type { OracleNode, OracleNodePrice, OracleNodePrices, OraclePrice, OraclePrices } from "../types/oracle.js"; 10 | import type { HubTokenData } from "../types/token.js"; 11 | import type { Client, ContractFunctionParameters, ReadContractReturnType } from "viem"; 12 | 13 | export async function getOraclePrice( 14 | provider: Client, 15 | network: NetworkType, 16 | poolId: number, 17 | blockNumber?: bigint, 18 | ): Promise { 19 | const hubChain = getHubChain(network); 20 | 21 | const oracleManager = getOracleManagerContract(provider, hubChain.oracleManagerAddress); 22 | 23 | const { price, decimals } = await oracleManager.read.processPriceFeed([poolId], { blockNumber }); 24 | return { price: [price, 18], decimals }; 25 | } 26 | 27 | export async function getOraclePrices( 28 | provider: Client, 29 | network: NetworkType, 30 | tokens: Array, 31 | blockNumber?: bigint, 32 | ): Promise { 33 | const hubChain = getHubChain(network); 34 | const oracleManager = getOracleManagerContract(provider, hubChain.oracleManagerAddress); 35 | 36 | const processPriceFeeds: Array = tokens.map(({ poolId }) => ({ 37 | address: oracleManager.address, 38 | abi: oracleManager.abi, 39 | functionName: "processPriceFeed", 40 | args: [poolId], 41 | })); 42 | 43 | const priceFeeds = (await multicall(provider, { 44 | contracts: processPriceFeeds, 45 | allowFailure: false, 46 | blockNumber, 47 | })) as Array>; 48 | 49 | const oraclePrices: OraclePrices = {}; 50 | for (const [i, { price, decimals }] of priceFeeds.entries()) { 51 | const { folksTokenId } = tokens[i]; 52 | oraclePrices[folksTokenId] = { price: [price, 18], decimals }; 53 | } 54 | return oraclePrices; 55 | } 56 | 57 | export async function getNodePrice( 58 | provider: Client, 59 | network: NetworkType, 60 | oracleNode: OracleNode, 61 | ): Promise { 62 | const hubChain = getHubChain(network); 63 | 64 | const nodeManager = getNodeManagerContract(provider, hubChain.nodeManagerAddress); 65 | 66 | const { nodeId, decimals } = oracleNode; 67 | const { price, timestamp } = await nodeManager.read.process([nodeId]); 68 | return { price: [price, 18], decimals, timestamp }; 69 | } 70 | 71 | export async function getNodePrices( 72 | provider: Client, 73 | network: NetworkType, 74 | oracleNodes: Array, 75 | ): Promise { 76 | const hubChain = getHubChain(network); 77 | const nodeManager = getNodeManagerContract(provider, hubChain.nodeManagerAddress); 78 | 79 | const processPriceFeeds: Array = oracleNodes.map(({ nodeId }) => ({ 80 | address: nodeManager.address, 81 | abi: nodeManager.abi, 82 | functionName: "process", 83 | args: [nodeId], 84 | })); 85 | 86 | const priceFeeds = (await multicall(provider, { 87 | contracts: processPriceFeeds, 88 | allowFailure: false, 89 | })) as Array>; 90 | 91 | const oracleNodePrices: OracleNodePrices = {}; 92 | for (const [i, { price, timestamp }] of priceFeeds.entries()) { 93 | const { nodeId, decimals } = oracleNodes[i]; 94 | oracleNodePrices[nodeId] = { price: [price, 18], decimals, timestamp }; 95 | } 96 | return oracleNodePrices; 97 | } 98 | -------------------------------------------------------------------------------- /src/chains/evm/hub/modules/folks-hub-pool.ts: -------------------------------------------------------------------------------- 1 | import { multicall } from "viem/actions"; 2 | 3 | import { 4 | calcBorrowInterestIndex, 5 | calcDepositInterestIndex, 6 | calcOverallBorrowInterestRate, 7 | calcRetention, 8 | } from "../../../../common/utils/formulae.js"; 9 | import { compoundEveryHour, compoundEverySecond, unixTime } from "../../../../common/utils/math-lib.js"; 10 | import { getBlockTimestamp } from "../../common/utils/chain.js"; 11 | import { getHubTokenData } from "../utils/chain.js"; 12 | import { getHubPoolContract } from "../utils/contract.js"; 13 | 14 | import type { EvmAddress, GenericAddress } from "../../../../common/types/address.js"; 15 | import type { NetworkType } from "../../../../common/types/chain.js"; 16 | import type { FolksTokenId } from "../../../../common/types/token.js"; 17 | import type { PoolInfo } from "../types/pool.js"; 18 | import type { Client } from "viem"; 19 | 20 | export async function getPoolInfo( 21 | provider: Client, 22 | network: NetworkType, 23 | folksTokenId: FolksTokenId, 24 | blockNumber?: bigint, 25 | ): Promise { 26 | const { 27 | poolAddress, 28 | token: { decimals: tokenDecimals }, 29 | } = getHubTokenData(folksTokenId, network); 30 | const hubPool = getHubPoolContract(provider, poolAddress); 31 | 32 | // get pool data 33 | const [ 34 | poolId, 35 | lastUpdateTimestamp, 36 | feeData, 37 | depositData, 38 | variableBorrowData, 39 | stableBorrowData, 40 | capsData, 41 | configData, 42 | fTokenCirculatingSupply, 43 | ] = await multicall(provider, { 44 | contracts: [ 45 | { 46 | address: hubPool.address, 47 | abi: hubPool.abi, 48 | functionName: "getPoolId", 49 | }, 50 | { 51 | address: hubPool.address, 52 | abi: hubPool.abi, 53 | functionName: "getLastUpdateTimestamp", 54 | }, 55 | { 56 | address: hubPool.address, 57 | abi: hubPool.abi, 58 | functionName: "getFeeData", 59 | }, 60 | { 61 | address: hubPool.address, 62 | abi: hubPool.abi, 63 | functionName: "getDepositData", 64 | }, 65 | { 66 | address: hubPool.address, 67 | abi: hubPool.abi, 68 | functionName: "getVariableBorrowData", 69 | }, 70 | { 71 | address: hubPool.address, 72 | abi: hubPool.abi, 73 | functionName: "getStableBorrowData", 74 | }, 75 | { 76 | address: hubPool.address, 77 | abi: hubPool.abi, 78 | functionName: "getCapsData", 79 | }, 80 | { 81 | address: hubPool.address, 82 | abi: hubPool.abi, 83 | functionName: "getConfigData", 84 | }, 85 | { 86 | address: hubPool.address, 87 | abi: hubPool.abi, 88 | functionName: "totalSupply", 89 | }, 90 | ], 91 | allowFailure: false, 92 | blockNumber, 93 | }); 94 | 95 | const lastTimestamp = blockNumber ? await getBlockTimestamp(provider, blockNumber) : BigInt(unixTime()); 96 | 97 | const { 98 | flashLoanFee, 99 | retentionRate, 100 | fTokenFeeRecipient, 101 | tokenFeeClaimer, 102 | totalRetainedAmount: actualRetained, 103 | tokenFeeRecipient, 104 | } = feeData; 105 | const { 106 | optimalUtilisationRatio, 107 | totalAmount: depositTotalAmount, 108 | interestRate: depositInterestRate, 109 | interestIndex: oldDepositInterestIndex, 110 | } = depositData; 111 | const { 112 | vr0, 113 | vr1, 114 | vr2, 115 | totalAmount: variableBorrowTotalAmount, 116 | interestRate: variableBorrowInterestRate, 117 | interestIndex: oldVariableBorrowInterestsIndex, 118 | } = variableBorrowData; 119 | const { 120 | sr0, 121 | sr1, 122 | sr2, 123 | sr3, 124 | optimalStableToTotalDebtRatio, 125 | rebalanceUpUtilisationRatio, 126 | rebalanceUpDepositInterestRate, 127 | rebalanceDownDelta, 128 | totalAmount: stableBorrowTotalAmount, 129 | interestRate: stableBorrowInterestRate, 130 | averageInterestRate: stableBorrowAverageInterestRate, 131 | } = stableBorrowData; 132 | const { deposit: depositCap, borrow: borrowCap, stableBorrowPercentage: stableBorrowPercentageCap } = capsData; 133 | const { deprecated, stableBorrowSupported, canMintFToken, flashLoanSupported } = configData; 134 | 135 | // build pool info 136 | return { 137 | folksTokenId, 138 | poolId, 139 | tokenDecimals, 140 | feeData: { 141 | flashLoanFee: [BigInt(flashLoanFee), 6], 142 | retentionRate: [BigInt(retentionRate), 6], 143 | totalRetainedAmount: calcRetention( 144 | actualRetained, 145 | variableBorrowTotalAmount + stableBorrowTotalAmount, 146 | calcOverallBorrowInterestRate( 147 | variableBorrowTotalAmount, 148 | stableBorrowTotalAmount, 149 | [variableBorrowInterestRate, 18], 150 | [stableBorrowAverageInterestRate, 18], 151 | ), 152 | [BigInt(retentionRate), 6], 153 | lastUpdateTimestamp, 154 | lastTimestamp, 155 | ), 156 | fTokenFeeRecipient: fTokenFeeRecipient as EvmAddress, 157 | tokenFeeClaimer: tokenFeeClaimer as EvmAddress, 158 | tokenFeeRecipient: tokenFeeRecipient as GenericAddress, 159 | }, 160 | depositData: { 161 | optimalUtilisationRatio: [BigInt(optimalUtilisationRatio), 4], 162 | totalAmount: depositTotalAmount, 163 | interestRate: [depositInterestRate, 18], 164 | interestYield: compoundEveryHour([depositInterestRate, 18]), 165 | interestIndex: calcDepositInterestIndex( 166 | [depositInterestRate, 18], 167 | [oldDepositInterestIndex, 18], 168 | lastUpdateTimestamp, 169 | lastTimestamp, 170 | ), 171 | }, 172 | variableBorrowData: { 173 | vr0: [BigInt(vr0), 6], 174 | vr1: [BigInt(vr1), 6], 175 | vr2: [BigInt(vr2), 6], 176 | totalAmount: variableBorrowTotalAmount, 177 | interestRate: [variableBorrowInterestRate, 18], 178 | interestYield: compoundEverySecond([variableBorrowInterestRate, 18]), 179 | interestIndex: calcBorrowInterestIndex( 180 | [variableBorrowInterestRate, 18], 181 | [oldVariableBorrowInterestsIndex, 18], 182 | lastUpdateTimestamp, 183 | lastTimestamp, 184 | ), 185 | }, 186 | stableBorrowData: { 187 | sr0: [BigInt(sr0), 6], 188 | sr1: [BigInt(sr1), 6], 189 | sr2: [BigInt(sr2), 6], 190 | sr3: [BigInt(sr3), 6], 191 | optimalStableToTotalDebtRatio: [BigInt(optimalStableToTotalDebtRatio), 4], 192 | rebalanceUpUtilisationRatio: [BigInt(rebalanceUpUtilisationRatio), 4], 193 | rebalanceUpDepositInterestRate: [BigInt(rebalanceUpDepositInterestRate), 4], 194 | rebalanceDownDelta: [BigInt(rebalanceDownDelta), 4], 195 | totalAmount: stableBorrowTotalAmount, 196 | interestRate: [stableBorrowInterestRate, 18], 197 | interestYield: compoundEverySecond([stableBorrowInterestRate, 18]), 198 | averageInterestRate: [stableBorrowAverageInterestRate, 18], 199 | averageInterestYield: compoundEverySecond([stableBorrowAverageInterestRate, 18]), 200 | }, 201 | capsData: { 202 | deposit: BigInt(depositCap), 203 | borrow: BigInt(borrowCap), 204 | stableBorrowPercentage: [BigInt(stableBorrowPercentageCap), 18], 205 | }, 206 | configData: { 207 | deprecated, 208 | stableBorrowSupported, 209 | canMintFToken, 210 | flashLoanSupported, 211 | }, 212 | fTokenCirculatingSupply, 213 | }; 214 | } 215 | -------------------------------------------------------------------------------- /src/chains/evm/hub/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * as FolksHubAccount from "./folks-hub-account.js"; 2 | export * as FolksHubLoan from "./folks-hub-loan.js"; 3 | export * as FolksHubOracle from "./folks-hub-oracle.js"; 4 | export * as FolksHubPool from "./folks-hub-pool.js"; 5 | export * as FolksHubRewardsV1 from "./folks-hub-rewards-v1.js"; 6 | export * as FolksHubRewardsV2 from "./folks-hub-rewards-v2.js"; 7 | export * as FolksHubGmp from "./folks-hub-gmp.js"; 8 | -------------------------------------------------------------------------------- /src/chains/evm/hub/types/account.ts: -------------------------------------------------------------------------------- 1 | import type { GenericAddress } from "../../../../common/types/address.js"; 2 | import type { FolksChainId } from "../../../../common/types/chain.js"; 3 | import type { AccountId } from "../../../../common/types/lending.js"; 4 | import type { GetEventParams, GetReadContractReturnType } from "../../common/types/contract.js"; 5 | import type { AccountManagerAbi } from "../constants/abi/account-manager-abi.js"; 6 | 7 | export type AccountInfo = { 8 | registered: Map; 9 | invited: Map; 10 | }; 11 | 12 | export type AccountIdByAddress = Array<{ 13 | accountId: AccountId; 14 | folksChainId: FolksChainId; 15 | }>; 16 | 17 | export type InviteAddressEventParams = GetEventParams & { 18 | accountManager: GetReadContractReturnType; 19 | address: GenericAddress; 20 | folksChainId?: FolksChainId; 21 | }; 22 | 23 | export type AcceptInviteAddressEventParams = GetEventParams & { 24 | accountManager: GetReadContractReturnType; 25 | address: GenericAddress; 26 | folksChainId?: FolksChainId; 27 | }; 28 | -------------------------------------------------------------------------------- /src/chains/evm/hub/types/chain.ts: -------------------------------------------------------------------------------- 1 | import type { HubRewardTokenData as RewardTokenDataV2 } from "./rewards-v2.js"; 2 | import type { HubTokenData } from "./token.js"; 3 | import type { REWARDS_TYPE } from "../../../../common/constants/reward.js"; 4 | import type { GenericAddress } from "../../../../common/types/address.js"; 5 | import type { IFolksChain } from "../../../../common/types/chain.js"; 6 | import type { AdapterType } from "../../../../common/types/message.js"; 7 | import type { RewardsTokenId } from "../../../../common/types/rewards.js"; 8 | import type { FolksTokenId } from "../../../../common/types/token.js"; 9 | 10 | type HubRewardsV1 = { 11 | hubAddress: GenericAddress; 12 | }; 13 | 14 | type HubRewardsV2 = { 15 | hubAddress: GenericAddress; 16 | spokeManagerAddress: GenericAddress; 17 | tokens: Partial>; 18 | }; 19 | 20 | export type HubRewardsMap = { 21 | bridgeRouterAddress: GenericAddress; 22 | adapters: Partial>; 23 | [REWARDS_TYPE.V1]: HubRewardsV1; 24 | [REWARDS_TYPE.V2]: HubRewardsV2; 25 | }; 26 | 27 | export type HubChain = { 28 | hubAddress: GenericAddress; 29 | bridgeRouterAddress: GenericAddress; 30 | adapters: Record; 31 | nodeManagerAddress: GenericAddress; 32 | oracleManagerAddress: GenericAddress; 33 | spokeManagerAddress: GenericAddress; 34 | accountManagerAddress: GenericAddress; 35 | loanManagerAddress: GenericAddress; 36 | tokens: Partial>; 37 | rewards: HubRewardsMap; 38 | } & IFolksChain; 39 | -------------------------------------------------------------------------------- /src/chains/evm/hub/types/loan.ts: -------------------------------------------------------------------------------- 1 | import type { OraclePrice } from "./oracle.js"; 2 | import type { PoolInfo } from "./pool.js"; 3 | import type { AccountId, LoanId, LoanTypeId } from "../../../../common/types/lending.js"; 4 | import type { FolksTokenId } from "../../../../common/types/token.js"; 5 | import type { GetEventParams, GetReadContractReturnType } from "../../common/types/contract.js"; 6 | import type { LoanManagerAbi } from "../constants/abi/loan-manager-abi.js"; 7 | import type { Dnum } from "dnum"; 8 | import type { ReadContractReturnType } from "viem"; 9 | 10 | export type LoanPoolInfo = { 11 | folksTokenId: FolksTokenId; 12 | poolId: number; 13 | collateralUsed: bigint; // in f token 14 | borrowUsed: bigint; // in token 15 | collateralCap: bigint; // $ amount 16 | borrowCap: bigint; // $ amount 17 | collateralFactor: Dnum; 18 | borrowFactor: Dnum; 19 | liquidationBonus: Dnum; 20 | liquidationFee: Dnum; 21 | isDeprecated: boolean; 22 | reward: { 23 | minimumAmount: bigint; // in token 24 | collateralSpeed: Dnum; 25 | borrowSpeed: Dnum; 26 | collateralRewardIndex: Dnum; 27 | borrowRewardIndex: Dnum; 28 | }; 29 | }; 30 | 31 | export type LoanTypeInfo = { 32 | loanTypeId: LoanTypeId; 33 | deprecated: boolean; 34 | loanTargetHealth: Dnum; 35 | pools: Partial>; 36 | }; 37 | 38 | export type PoolsPoints = { 39 | collateral: bigint; 40 | borrow: bigint; 41 | interestPaid: bigint; 42 | }; 43 | 44 | export type UserPoints = { 45 | accountId: AccountId; 46 | poolsPoints: Partial>; 47 | }; 48 | 49 | export type UserLoanInfoCollateral = { 50 | folksTokenId: FolksTokenId; 51 | poolId: number; 52 | tokenDecimals: number; 53 | oraclePrice: OraclePrice; 54 | collateralFactor: Dnum; 55 | fTokenBalance: bigint; 56 | tokenBalance: bigint; 57 | balanceValue: Dnum; 58 | effectiveBalanceValue: Dnum; 59 | interestRate: Dnum; 60 | interestYield: Dnum; 61 | }; 62 | 63 | export type UserLoanInfoBorrow = { 64 | folksTokenId: FolksTokenId; 65 | poolId: number; 66 | tokenDecimals: number; 67 | oraclePrice: OraclePrice; 68 | isStable: boolean; 69 | borrowFactor: Dnum; 70 | borrowedAmount: bigint; 71 | borrowedAmountValue: Dnum; 72 | borrowBalance: bigint; 73 | borrowBalanceValue: Dnum; 74 | effectiveBorrowBalanceValue: Dnum; 75 | accruedInterest: bigint; 76 | accruedInterestValue: Dnum; 77 | interestRate: Dnum; 78 | interestYield: Dnum; 79 | }; 80 | 81 | export type UserLoanInfo = { 82 | loanId: LoanId; 83 | loanTypeId: LoanTypeId; 84 | accountId: AccountId; 85 | collaterals: Partial>; 86 | borrows: Partial>; 87 | netRate: Dnum; 88 | netYield: Dnum; 89 | totalCollateralBalanceValue: Dnum; 90 | totalBorrowedAmountValue: Dnum; 91 | totalBorrowBalanceValue: Dnum; 92 | totalEffectiveCollateralBalanceValue: Dnum; 93 | totalEffectiveBorrowBalanceValue: Dnum; 94 | loanToValueRatio: Dnum; 95 | borrowUtilisationRatio: Dnum; 96 | liquidationMargin: Dnum; 97 | }; 98 | 99 | export type AssetsAdditionalInterest = Partial< 100 | Record< 101 | FolksTokenId, 102 | { 103 | additionalRate: Dnum; 104 | additionalYield: Dnum; 105 | } 106 | > 107 | >; 108 | 109 | export type CreateUserLoanEventParams = GetEventParams & { 110 | loanManager: GetReadContractReturnType; 111 | accountId: AccountId; 112 | loanTypeIds?: Array; 113 | }; 114 | 115 | export type DeleteUserLoanEventParams = GetEventParams & { 116 | loanManager: GetReadContractReturnType; 117 | accountId: AccountId; 118 | }; 119 | 120 | export type LoanManagerUserLoanAbi = ReadContractReturnType; 121 | 122 | export type LoanManagerUserLoanCollateral = LoanManagerUserLoanAbi[4][0]; 123 | export type LoanManagerUserLoanBorrow = LoanManagerUserLoanAbi[5][0]; 124 | 125 | export type LoanManagerUserLoan = { 126 | accountId: AccountId; 127 | loanTypeId: LoanTypeId; 128 | colPools: Array; 129 | borPools: Array; 130 | userLoanCollateral: Array; 131 | userLoanBorrow: Array; 132 | }; 133 | 134 | export enum LoanChangeType { 135 | AddCollateral, 136 | ReduceCollateral, 137 | Borrow, 138 | Repay, 139 | SwitchBorrowType, 140 | } 141 | 142 | type LoanBaseChange = { 143 | type: LoanChangeType; 144 | poolInfo: PoolInfo; 145 | }; 146 | 147 | type LoanAddCollateralChange = LoanBaseChange & { 148 | type: LoanChangeType.AddCollateral; 149 | fTokenAmount: bigint; 150 | }; 151 | 152 | type LoanReduceCollateralChange = LoanBaseChange & { 153 | type: LoanChangeType.ReduceCollateral; 154 | fTokenAmount: bigint; 155 | }; 156 | 157 | type LoanBorrowChange = LoanBaseChange & { 158 | type: LoanChangeType.Borrow; 159 | tokenAmount: bigint; 160 | isStable: boolean; 161 | }; 162 | 163 | type LoanRepayChange = LoanBaseChange & { 164 | type: LoanChangeType.Repay; 165 | tokenAmount: bigint; 166 | }; 167 | 168 | type LoanSwitchBorrowTypeChange = LoanBaseChange & { 169 | type: LoanChangeType.SwitchBorrowType; 170 | isSwitchingToStable: boolean; 171 | }; 172 | 173 | export type LoanChange = 174 | | LoanAddCollateralChange 175 | | LoanReduceCollateralChange 176 | | LoanBorrowChange 177 | | LoanRepayChange 178 | | LoanSwitchBorrowTypeChange; 179 | -------------------------------------------------------------------------------- /src/chains/evm/hub/types/oracle.ts: -------------------------------------------------------------------------------- 1 | import type { Branded } from "../../../../common/types/brand.js"; 2 | import type { FolksTokenId } from "../../../../common/types/token.js"; 3 | import type { Dnum } from "dnum"; 4 | 5 | export type NodeId = Branded<`0x${string}`, "NodeId">; 6 | 7 | export type OracleNode = { 8 | nodeId: NodeId; 9 | decimals: number; 10 | }; 11 | 12 | export type OracleNodePrice = { 13 | price: Dnum; 14 | decimals: number; 15 | timestamp: bigint; 16 | }; 17 | 18 | export type OracleNodePrices = Partial>; 19 | 20 | export type OraclePrice = { 21 | price: Dnum; 22 | decimals: number; 23 | }; 24 | 25 | export type OraclePrices = Partial>; 26 | -------------------------------------------------------------------------------- /src/chains/evm/hub/types/pool.ts: -------------------------------------------------------------------------------- 1 | import type { EvmAddress, GenericAddress } from "../../../../common/types/address.js"; 2 | import type { FolksTokenId } from "../../../../common/types/token.js"; 3 | import type { Dnum } from "dnum"; 4 | 5 | type FeeData = { 6 | flashLoanFee: Dnum; 7 | retentionRate: Dnum; 8 | fTokenFeeRecipient: EvmAddress; 9 | tokenFeeClaimer: EvmAddress; 10 | totalRetainedAmount: bigint; 11 | tokenFeeRecipient: GenericAddress; 12 | }; 13 | 14 | type DepositData = { 15 | optimalUtilisationRatio: Dnum; 16 | totalAmount: bigint; 17 | interestRate: Dnum; 18 | interestYield: Dnum; 19 | interestIndex: Dnum; 20 | }; 21 | 22 | type VariableBorrowData = { 23 | vr0: Dnum; 24 | vr1: Dnum; 25 | vr2: Dnum; 26 | totalAmount: bigint; 27 | interestRate: Dnum; 28 | interestYield: Dnum; 29 | interestIndex: Dnum; 30 | }; 31 | 32 | type StableBorrowData = { 33 | sr0: Dnum; 34 | sr1: Dnum; 35 | sr2: Dnum; 36 | sr3: Dnum; 37 | optimalStableToTotalDebtRatio: Dnum; 38 | rebalanceUpUtilisationRatio: Dnum; 39 | rebalanceUpDepositInterestRate: Dnum; 40 | rebalanceDownDelta: Dnum; 41 | totalAmount: bigint; 42 | interestRate: Dnum; 43 | interestYield: Dnum; 44 | averageInterestRate: Dnum; 45 | averageInterestYield: Dnum; 46 | }; 47 | 48 | type CapsData = { 49 | deposit: bigint; // $ amount 50 | borrow: bigint; // $ amount 51 | stableBorrowPercentage: Dnum; 52 | }; 53 | 54 | type ConfigData = { 55 | deprecated: boolean; 56 | stableBorrowSupported: boolean; 57 | canMintFToken: boolean; 58 | flashLoanSupported: boolean; 59 | }; 60 | 61 | export type PoolInfo = { 62 | folksTokenId: FolksTokenId; 63 | poolId: number; 64 | tokenDecimals: number; 65 | feeData: FeeData; 66 | depositData: DepositData; 67 | variableBorrowData: VariableBorrowData; 68 | stableBorrowData: StableBorrowData; 69 | capsData: CapsData; 70 | configData: ConfigData; 71 | fTokenCirculatingSupply: bigint; 72 | }; 73 | -------------------------------------------------------------------------------- /src/chains/evm/hub/types/rewards-v1.ts: -------------------------------------------------------------------------------- 1 | import type { FolksTokenId } from "../../../../common/types/token.js"; 2 | import type { Dnum } from "dnum"; 3 | 4 | export type Epoch = { 5 | poolId: number; 6 | epochIndex: number; 7 | startTimestamp: bigint; 8 | endTimestamp: bigint; 9 | totalRewards: bigint; 10 | }; 11 | 12 | export type PoolEpoch = { 13 | poolId: number; 14 | epochIndex: number; 15 | }; 16 | 17 | export type Epochs = Partial>>; 18 | 19 | export type ActiveEpochs = Partial>; 20 | 21 | export type ActiveEpochInfo = { 22 | remainingRewards: bigint; 23 | totalRewardsApr: Dnum; 24 | } & Epoch; 25 | 26 | export type ActiveEpochsInfo = Partial>; 27 | 28 | export type PendingRewards = Partial>; 29 | 30 | export type LastUpdatedPointsForRewards = Partial< 31 | Record< 32 | FolksTokenId, 33 | { 34 | lastWrittenPoints: bigint; 35 | writtenEpochPoints: bigint; 36 | } 37 | > 38 | >; 39 | -------------------------------------------------------------------------------- /src/chains/evm/hub/types/rewards-v2.ts: -------------------------------------------------------------------------------- 1 | import type { NodeId } from "./oracle.js"; 2 | import type { AdapterType } from "../../../../common/types/message.js"; 3 | import type { RewardsTokenId } from "../../../../common/types/rewards.js"; 4 | import type { Erc20HubTokenType, FolksTokenId, NativeTokenType } from "../../../../common/types/token.js"; 5 | import type { Dnum } from "dnum"; 6 | 7 | export type FolksHubRewardTokenType = Erc20HubTokenType | NativeTokenType; 8 | 9 | export type HubRewardTokenData = { 10 | rewardTokenId: RewardsTokenId; 11 | nodeId: NodeId; 12 | token: FolksHubRewardTokenType; 13 | }; 14 | 15 | export type EpochReward = { 16 | rewardTokenId: RewardsTokenId; 17 | totalRewards: bigint; 18 | }; 19 | 20 | export type Epoch = { 21 | poolId: number; 22 | epochIndex: number; 23 | startTimestamp: bigint; 24 | endTimestamp: bigint; 25 | rewards: Array; 26 | }; 27 | 28 | export type PoolEpoch = { 29 | poolId: number; 30 | epochIndex: number; 31 | }; 32 | 33 | export type ReceiveRewardToken = { 34 | rewardTokenId: RewardsTokenId; 35 | returnAdapterId: AdapterType; 36 | returnGasLimit: bigint; 37 | }; 38 | 39 | export type Epochs = Partial>>; 40 | 41 | export type ActiveEpochs = Partial>; 42 | 43 | export type ActiveEpochReward = { 44 | remainingRewards: bigint; 45 | rewardsApr: Dnum; 46 | }; 47 | 48 | export type ActiveEpochInfo = { 49 | rewardsInfo: Partial>; 50 | totalRewardsApr: Dnum; 51 | } & Epoch; 52 | 53 | export type ActiveEpochsInfo = Partial>; 54 | 55 | export type UnclaimedRewards = Partial>; 56 | 57 | export type PendingRewards = Partial>>>; 58 | 59 | export type LastUpdatedPointsForRewards = Partial< 60 | Record< 61 | FolksTokenId, 62 | { 63 | lastWrittenPoints: bigint; 64 | writtenEpochPoints: bigint; 65 | } 66 | > 67 | >; 68 | -------------------------------------------------------------------------------- /src/chains/evm/hub/types/token.ts: -------------------------------------------------------------------------------- 1 | import type { GenericAddress } from "../../../../common/types/address.js"; 2 | import type { LoanTypeId } from "../../../../common/types/lending.js"; 3 | import type { FolksHubTokenType, ITokenData } from "../../../../common/types/token.js"; 4 | 5 | export type HubTokenData = { 6 | poolId: number; 7 | poolAddress: GenericAddress; 8 | token: FolksHubTokenType; 9 | supportedLoanTypes: Set; 10 | } & ITokenData; 11 | -------------------------------------------------------------------------------- /src/chains/evm/hub/utils/chain.ts: -------------------------------------------------------------------------------- 1 | import { REWARDS_TYPE } from "../../../../common/constants/reward.js"; 2 | import { HUB_CHAIN } from "../constants/chain.js"; 3 | 4 | import type { GenericAddress } from "../../../../common/types/address.js"; 5 | import type { FolksChainId, NetworkType } from "../../../../common/types/chain.js"; 6 | import type { LoanTypeId } from "../../../../common/types/lending.js"; 7 | import type { AdapterType } from "../../../../common/types/message.js"; 8 | import type { RewardsTokenId, RewardsType } from "../../../../common/types/rewards.js"; 9 | import type { FolksTokenId } from "../../../../common/types/token.js"; 10 | import type { HubChain } from "../types/chain.js"; 11 | import type { HubRewardTokenData } from "../types/rewards-v2.js"; 12 | import type { HubTokenData } from "../types/token.js"; 13 | 14 | export function isHubChain(folksChainId: FolksChainId, network: NetworkType): boolean { 15 | return HUB_CHAIN[network].folksChainId === folksChainId; 16 | } 17 | 18 | export function getHubChain(network: NetworkType): HubChain { 19 | return HUB_CHAIN[network]; 20 | } 21 | 22 | export function getHubTokensData(network: NetworkType): Partial> { 23 | return HUB_CHAIN[network].tokens; 24 | } 25 | 26 | export function getHubTokenData(folksTokenId: FolksTokenId, network: NetworkType): HubTokenData { 27 | const token = HUB_CHAIN[network].tokens[folksTokenId]; 28 | if (!token) throw new Error(`Hub token not found for folksTokenId: ${folksTokenId}`); 29 | return token; 30 | } 31 | 32 | export function getHubRewardAddress(network: NetworkType, rewardsType: RewardsType): GenericAddress { 33 | return HUB_CHAIN[network].rewards[rewardsType].hubAddress; 34 | } 35 | 36 | export function getHubRewardsV2TokensData(network: NetworkType): Partial> { 37 | return HUB_CHAIN[network].rewards[REWARDS_TYPE.V2].tokens; 38 | } 39 | 40 | export function getHubRewardsV2TokenData(rewardTokenId: RewardsTokenId, network: NetworkType): HubRewardTokenData { 41 | const token = HUB_CHAIN[network].rewards[REWARDS_TYPE.V2].tokens[rewardTokenId]; 42 | if (!token) throw new Error(`RewardsV2 token not found for rewardTokenId: ${rewardTokenId}`); 43 | return token; 44 | } 45 | 46 | export function isLoanTypeSupported(loanType: LoanTypeId, folksTokenId: FolksTokenId, network: NetworkType): boolean { 47 | const token = getHubTokenData(folksTokenId, network); 48 | return token.supportedLoanTypes.has(loanType); 49 | } 50 | 51 | export function assertLoanTypeSupported(loanType: LoanTypeId, folksTokenId: FolksTokenId, network: NetworkType): void { 52 | if (!isLoanTypeSupported(loanType, folksTokenId, network)) 53 | throw new Error(`Loan type ${loanType} is not supported for folksTokenId: ${folksTokenId}`); 54 | } 55 | 56 | export function getHubChainAdapterAddress(network: NetworkType, adapterType: AdapterType, isRewards = false) { 57 | const hubChain = getHubChain(network); 58 | const { adapters } = isRewards ? hubChain.rewards : hubChain; 59 | const adapterAddress = adapters[adapterType]; 60 | if (adapterAddress) return adapterAddress; 61 | throw new Error(`Adapter ${adapterType} not found for hub chain ${hubChain.folksChainId}`); 62 | } 63 | 64 | export function getHubChainBridgeRouterAddress(hubChain: HubChain, isRewards = false) { 65 | const { bridgeRouterAddress } = isRewards ? hubChain.rewards : hubChain; 66 | return bridgeRouterAddress; 67 | } 68 | -------------------------------------------------------------------------------- /src/chains/evm/hub/utils/contract.ts: -------------------------------------------------------------------------------- 1 | import { getContract } from "viem"; 2 | 3 | import { ChainType } from "../../../../common/types/chain.js"; 4 | import { convertFromGenericAddress } from "../../../../common/utils/address.js"; 5 | import { AccountManagerAbi } from "../constants/abi/account-manager-abi.js"; 6 | import { BridgeRouterHubAbi } from "../constants/abi/bridge-router-hub-abi.js"; 7 | import { HubAbi } from "../constants/abi/hub-abi.js"; 8 | import { HubPoolAbi } from "../constants/abi/hub-pool-abi.js"; 9 | import { HubRewardsV1Abi } from "../constants/abi/hub-rewards-v1-abi.js"; 10 | import { HubRewardsV2Abi } from "../constants/abi/hub-rewards-v2-abi.js"; 11 | import { LoanManagerAbi } from "../constants/abi/loan-manager-abi.js"; 12 | import { NodeManagerAbi } from "../constants/abi/node-manager-abi.js"; 13 | import { OracleManagerAbi } from "../constants/abi/oracle-manager-abi.js"; 14 | 15 | import type { GenericAddress } from "../../../../common/types/address.js"; 16 | import type { GetReadContractReturnType } from "../../common/types/contract.js"; 17 | import type { Client, GetContractReturnType, WalletClient } from "viem"; 18 | 19 | export function getAccountManagerContract( 20 | provider: Client, 21 | address: GenericAddress, 22 | signer?: WalletClient, 23 | ): GetReadContractReturnType { 24 | return getContract({ 25 | abi: AccountManagerAbi, 26 | address: convertFromGenericAddress(address, ChainType.EVM), 27 | client: { wallet: signer, public: provider }, 28 | }); 29 | } 30 | 31 | export function getBridgeRouterHubContract( 32 | provider: Client, 33 | address: GenericAddress, 34 | ): GetReadContractReturnType; 35 | export function getBridgeRouterHubContract( 36 | provider: Client, 37 | address: GenericAddress, 38 | signer: WalletClient, 39 | ): GetContractReturnType; 40 | export function getBridgeRouterHubContract( 41 | provider: Client, 42 | address: GenericAddress, 43 | signer?: WalletClient, 44 | ): GetReadContractReturnType | GetContractReturnType { 45 | return getContract({ 46 | abi: BridgeRouterHubAbi, 47 | address: convertFromGenericAddress(address, ChainType.EVM), 48 | client: { wallet: signer, public: provider }, 49 | }); 50 | } 51 | 52 | export function getHubPoolContract( 53 | provider: Client, 54 | address: GenericAddress, 55 | ): GetReadContractReturnType { 56 | return getContract({ 57 | abi: HubPoolAbi, 58 | address: convertFromGenericAddress(address, ChainType.EVM), 59 | client: { public: provider }, 60 | }); 61 | } 62 | 63 | export function getLoanManagerContract( 64 | provider: Client, 65 | address: GenericAddress, 66 | ): GetReadContractReturnType; 67 | export function getLoanManagerContract( 68 | provider: Client, 69 | address: GenericAddress, 70 | signer: WalletClient, 71 | ): GetContractReturnType; 72 | export function getLoanManagerContract( 73 | provider: Client, 74 | address: GenericAddress, 75 | signer?: WalletClient, 76 | ): GetReadContractReturnType | GetContractReturnType { 77 | return getContract({ 78 | abi: LoanManagerAbi, 79 | address: convertFromGenericAddress(address, ChainType.EVM), 80 | client: { wallet: signer, public: provider }, 81 | }); 82 | } 83 | 84 | export function getOracleManagerContract( 85 | provider: Client, 86 | address: GenericAddress, 87 | ): GetReadContractReturnType { 88 | return getContract({ 89 | abi: OracleManagerAbi, 90 | address: convertFromGenericAddress(address, ChainType.EVM), 91 | client: { public: provider }, 92 | }); 93 | } 94 | 95 | export function getNodeManagerContract( 96 | provider: Client, 97 | address: GenericAddress, 98 | ): GetReadContractReturnType { 99 | return getContract({ 100 | abi: NodeManagerAbi, 101 | address: convertFromGenericAddress(address, ChainType.EVM), 102 | client: { public: provider }, 103 | }); 104 | } 105 | 106 | export function getHubRewardsV1Contract( 107 | provider: Client, 108 | address: GenericAddress, 109 | ): GetReadContractReturnType; 110 | export function getHubRewardsV1Contract( 111 | provider: Client, 112 | address: GenericAddress, 113 | signer: WalletClient, 114 | ): GetContractReturnType; 115 | export function getHubRewardsV1Contract( 116 | provider: Client, 117 | address: GenericAddress, 118 | signer?: WalletClient, 119 | ): GetReadContractReturnType | GetContractReturnType { 120 | return getContract({ 121 | abi: HubRewardsV1Abi, 122 | address: convertFromGenericAddress(address, ChainType.EVM), 123 | client: { wallet: signer, public: provider }, 124 | }); 125 | } 126 | 127 | export function getHubRewardsV2Contract( 128 | provider: Client, 129 | address: GenericAddress, 130 | ): GetReadContractReturnType; 131 | export function getHubRewardsV2Contract( 132 | provider: Client, 133 | address: GenericAddress, 134 | signer: WalletClient, 135 | ): GetContractReturnType; 136 | export function getHubRewardsV2Contract( 137 | provider: Client, 138 | address: GenericAddress, 139 | signer?: WalletClient, 140 | ): GetReadContractReturnType | GetContractReturnType { 141 | return getContract({ 142 | abi: HubRewardsV2Abi, 143 | address: convertFromGenericAddress(address, ChainType.EVM), 144 | client: { wallet: signer, public: provider }, 145 | }); 146 | } 147 | 148 | export function getHubContract(provider: Client, address: GenericAddress): GetReadContractReturnType; 149 | export function getHubContract( 150 | provider: Client, 151 | address: GenericAddress, 152 | signer: WalletClient, 153 | ): GetContractReturnType; 154 | export function getHubContract( 155 | provider: Client, 156 | address: GenericAddress, 157 | signer?: WalletClient, 158 | ): GetReadContractReturnType | GetContractReturnType { 159 | return getContract({ 160 | abi: HubAbi, 161 | address: convertFromGenericAddress(address, ChainType.EVM), 162 | client: { wallet: signer, public: provider }, 163 | }); 164 | } 165 | -------------------------------------------------------------------------------- /src/chains/evm/hub/utils/events.ts: -------------------------------------------------------------------------------- 1 | import type { FolksChainId } from "../../../../common/types/chain.js"; 2 | import type { AccountId, LoanId, LoanTypeId } from "../../../../common/types/lending.js"; 3 | import type { AcceptInviteAddressEventParams, InviteAddressEventParams } from "../types/account.js"; 4 | import type { CreateUserLoanEventParams, DeleteUserLoanEventParams } from "../types/loan.js"; 5 | 6 | export async function fetchCreateUserLoanEvents(params: CreateUserLoanEventParams) { 7 | const { loanManager, accountId, loanTypeIds, eventParams } = params; 8 | const logs = await loanManager.getEvents.CreateUserLoan({ accountId }, eventParams); 9 | return logs 10 | .filter( 11 | (log) => 12 | loanTypeIds === undefined || (log.args.loanTypeId && loanTypeIds.includes(log.args.loanTypeId as LoanTypeId)), 13 | ) 14 | .map((log) => ({ 15 | blockNumber: log.blockNumber, 16 | loanId: log.args.loanId as LoanId, 17 | accountId, 18 | loanTypeId: log.args.loanTypeId as LoanTypeId, 19 | })); 20 | } 21 | 22 | export async function fetchDeleteUserLoanEvents(params: DeleteUserLoanEventParams) { 23 | const { loanManager, accountId, eventParams } = params; 24 | const logs = await loanManager.getEvents.DeleteUserLoan({ accountId }, eventParams); 25 | return logs.map((log) => ({ 26 | blockNumber: log.blockNumber, 27 | loanId: log.args.loanId, 28 | accountId, 29 | })); 30 | } 31 | 32 | export async function fetchUserLoanIds(params: CreateUserLoanEventParams): Promise>> { 33 | const createdUserLoans = await fetchCreateUserLoanEvents(params); 34 | const deletedUserLoans = await fetchDeleteUserLoanEvents(params); 35 | 36 | // add created and remove deleted through counting 37 | const loanIdsMap = new Map(); 38 | for (const userLoan of createdUserLoans) { 39 | const loanId = userLoan.loanId; 40 | const loanIdCount = loanIdsMap.get(loanId); 41 | if (!loanIdCount) loanIdsMap.set(loanId, { loanTypeId: userLoan.loanTypeId, count: 1 }); 42 | else loanIdCount.count++; 43 | } 44 | for (const userLoan of deletedUserLoans) { 45 | const loanId = userLoan.loanId as LoanId; 46 | const loanIdCount = loanIdsMap.get(loanId); 47 | if (loanIdCount) 48 | if (loanIdCount.count === 1) loanIdsMap.delete(loanId); 49 | else loanIdCount.count--; 50 | } 51 | 52 | const loanTypeMap = new Map>(); 53 | 54 | for (const [loanId, { loanTypeId }] of loanIdsMap.entries()) { 55 | const loanTypeIds = loanTypeMap.get(loanTypeId); 56 | if (!loanTypeIds) loanTypeMap.set(loanTypeId, [loanId]); 57 | else loanTypeIds.push(loanId); 58 | } 59 | 60 | return loanTypeMap; 61 | } 62 | 63 | async function fetchReceivedInvitationEventByAddress(params: InviteAddressEventParams) { 64 | const { eventParams, accountManager, address, folksChainId } = params; 65 | 66 | const logs = await accountManager.getEvents.InviteAddress( 67 | { inviteeAddr: address, inviteeChainId: folksChainId }, 68 | eventParams, 69 | ); 70 | return logs.map((log) => ({ 71 | blockNumber: log.blockNumber, 72 | accountId: log.args.accountId, 73 | folksChainId: log.args.inviteeChainId, 74 | id: `${log.args.accountId ?? ""}${log.args.inviteeChainId ?? ""}`, 75 | })); 76 | } 77 | 78 | async function fetchAcceptedInvitationEventByAddress(params: AcceptInviteAddressEventParams) { 79 | const { eventParams, accountManager, address, folksChainId } = params; 80 | 81 | const logs = await accountManager.getEvents.AcceptInviteAddress(eventParams); 82 | return logs 83 | .filter((log) => log.args.addr === address && (folksChainId ? log.args.chainId === folksChainId : true)) 84 | .map((log) => ({ 85 | blockNumber: log.blockNumber, 86 | accountId: log.args.accountId, 87 | folksChainId: log.args.chainId, 88 | id: `${log.args.accountId ?? ""}${log.args.chainId ?? ""}`, 89 | })); 90 | } 91 | 92 | export async function fetchInvitationByAddress(params: InviteAddressEventParams) { 93 | const receivedInvitations = await fetchReceivedInvitationEventByAddress(params); 94 | const acceptedInvitations = await fetchAcceptedInvitationEventByAddress(params); 95 | 96 | const allEvents = [...receivedInvitations, ...acceptedInvitations]; 97 | allEvents.sort((a, b) => Number(a.blockNumber) - Number(b.blockNumber)); 98 | 99 | const accountStatus = new Map(); 100 | 101 | for (const event of allEvents) 102 | if (receivedInvitations.includes(event)) accountStatus.set(event.id, true); 103 | else if (acceptedInvitations.includes(event)) accountStatus.set(event.id, false); 104 | 105 | return { 106 | address: params.address, 107 | invitations: allEvents 108 | .filter((log) => accountStatus.get(log.id)) 109 | .map((log) => ({ 110 | accountId: log.accountId as AccountId, 111 | folksChainId: log.folksChainId as FolksChainId, 112 | })), 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /src/chains/evm/hub/utils/loan.ts: -------------------------------------------------------------------------------- 1 | import { 2 | calcBorrowBalance, 3 | calcBorrowInterestIndex, 4 | calcStableInterestRate, 5 | } from "../../../../common/utils/formulae.js"; 6 | import { unixTime } from "../../../../common/utils/math-lib.js"; 7 | 8 | import type { LoanManagerUserLoanBorrow } from "../types/loan.js"; 9 | import type { PoolInfo } from "../types/pool.js"; 10 | 11 | export function initLoanBorrowInterests(isStable: boolean, poolInfo: PoolInfo): LoanManagerUserLoanBorrow { 12 | const lastInterestIndex = isStable ? BigInt(1e18) : poolInfo.variableBorrowData.interestIndex[0]; 13 | const stableInterestRate = isStable ? poolInfo.stableBorrowData.interestRate[0] : 0n; 14 | const lastStableUpdateTimestamp = isStable ? BigInt(unixTime()) : 0n; 15 | return { 16 | amount: 0n, 17 | balance: 0n, 18 | lastInterestIndex, 19 | stableInterestRate, 20 | lastStableUpdateTimestamp, 21 | rewardIndex: 0n, 22 | }; 23 | } 24 | 25 | export function updateLoanBorrowInterests( 26 | borrow: LoanManagerUserLoanBorrow, 27 | amount: bigint, 28 | poolInfo: PoolInfo, 29 | isStableInterestRateToUpdate: boolean, 30 | ): LoanManagerUserLoanBorrow { 31 | if (borrow.lastStableUpdateTimestamp > 0) { 32 | const oldInterestIndex = borrow.lastInterestIndex; 33 | const oldStableInterestRate = borrow.stableInterestRate; 34 | borrow.lastInterestIndex = calcBorrowInterestIndex( 35 | [oldStableInterestRate, 18], 36 | [oldInterestIndex, 18], 37 | borrow.lastStableUpdateTimestamp, 38 | )[0]; 39 | borrow.lastStableUpdateTimestamp = BigInt(unixTime()); 40 | 41 | borrow.balance = calcBorrowBalance(borrow.balance, [borrow.lastInterestIndex, 18], [oldInterestIndex, 18]); 42 | 43 | if (isStableInterestRateToUpdate) { 44 | borrow.stableInterestRate = calcStableInterestRate( 45 | borrow.balance, 46 | amount, 47 | [oldStableInterestRate, 18], 48 | poolInfo.stableBorrowData.interestRate, 49 | )[0]; 50 | } 51 | } else { 52 | borrow.balance = calcBorrowBalance(borrow.balance, poolInfo.variableBorrowData.interestIndex, [ 53 | borrow.lastInterestIndex, 54 | 18, 55 | ]); 56 | borrow.lastInterestIndex = poolInfo.variableBorrowData.interestIndex[0]; 57 | } 58 | 59 | return borrow; 60 | } 61 | -------------------------------------------------------------------------------- /src/chains/evm/hub/utils/message.ts: -------------------------------------------------------------------------------- 1 | import { RECEIVE_TOKEN_ACTIONS } from "../../../../common/constants/message.js"; 2 | import { ChainType } from "../../../../common/types/chain.js"; 3 | import { MessageDirection } from "../../../../common/types/gmp.js"; 4 | import { Action } from "../../../../common/types/message.js"; 5 | import { TokenType } from "../../../../common/types/token.js"; 6 | import { 7 | assertAdapterSupportsCrossChainToken, 8 | assertAdapterSupportsDataMessage, 9 | } from "../../../../common/utils/adapter.js"; 10 | import { getSpokeChain, getSpokeTokenData } from "../../../../common/utils/chain.js"; 11 | import { 12 | buildMessageToSend, 13 | decodeMessagePayloadData, 14 | estimateAdapterReceiveGasLimit, 15 | } from "../../../../common/utils/messages.js"; 16 | import { getFolksTokenIdFromPool } from "../../../../common/utils/token.js"; 17 | import { FolksCore } from "../../../../xchain/core/folks-core.js"; 18 | import { buildSendTokenExtraArgsWhenRemoving } from "../../common/utils/message.js"; 19 | 20 | import { getHubTokenData } from "./chain.js"; 21 | import { getBridgeRouterHubContract } from "./contract.js"; 22 | 23 | import type { GenericAddress } from "../../../../common/types/address.js"; 24 | import type { NetworkType } from "../../../../common/types/chain.js"; 25 | import type { 26 | MessageBuilderParams, 27 | OverrideTokenData, 28 | Payload, 29 | ReceiveTokenAction, 30 | ReversibleHubAction, 31 | SendTokenExtraArgs, 32 | SendTokenMessageData, 33 | } from "../../../../common/types/message.js"; 34 | import type { 35 | MessageReceived, 36 | RetryMessageExtraArgs, 37 | RetryMessageExtraArgsParams, 38 | ReverseMessageExtraArgs, 39 | ReverseMessageExtraArgsParams, 40 | } from "../../common/types/gmp.js"; 41 | import type { HubChain } from "../types/chain.js"; 42 | import type { Client as EVMProvider } from "viem"; 43 | 44 | export async function getHubRetryMessageExtraArgsAndAdapterFees( 45 | provider: EVMProvider, 46 | hubChain: HubChain, 47 | network: NetworkType, 48 | userAddress: GenericAddress, 49 | message: MessageReceived, 50 | extraArgsParams: RetryMessageExtraArgsParams, 51 | payload: Payload, 52 | ): Promise<{ 53 | adapterFees: bigint; 54 | extraArgs: RetryMessageExtraArgs; 55 | }> { 56 | const returnAdapterId = extraArgsParams?.returnAdapterId ?? message.returnAdapterId; 57 | const { accountId, action, data } = payload; 58 | 59 | // @ts-expect-error: ts(2345) 60 | if (!RECEIVE_TOKEN_ACTIONS.includes(action)) 61 | return { adapterFees: 0n, extraArgs: { returnAdapterId, returnGasLimit: 0n } }; 62 | const payloadData = decodeMessagePayloadData(action as ReceiveTokenAction, data); 63 | 64 | const folksTokenId = getFolksTokenIdFromPool(payloadData.poolId, network); 65 | 66 | const spokeChain = getSpokeChain(payloadData.receiverFolksChainId, network); 67 | const spokeTokenData = getSpokeTokenData(spokeChain, folksTokenId); 68 | const hubTokenData = getHubTokenData(folksTokenId, network); 69 | 70 | if (hubTokenData.token.type === TokenType.CROSS_CHAIN) 71 | assertAdapterSupportsCrossChainToken(payloadData.receiverFolksChainId, hubTokenData.token, returnAdapterId); 72 | else assertAdapterSupportsDataMessage(payloadData.receiverFolksChainId, returnAdapterId); 73 | 74 | const returnData: SendTokenMessageData = { 75 | amount: payloadData.amount, 76 | }; 77 | const returnExtraArgs: SendTokenExtraArgs = { 78 | folksTokenId, 79 | token: hubTokenData.token, 80 | recipient: spokeTokenData.spokeAddress, 81 | amount: payloadData.amount, 82 | }; 83 | const overrideTokenData: OverrideTokenData = { 84 | folksTokenId, 85 | token: spokeTokenData.token, 86 | address: spokeTokenData.spokeAddress, 87 | amount: payloadData.amount, 88 | }; 89 | const returnMessageBuilderParams: MessageBuilderParams = { 90 | userAddress, 91 | accountId, 92 | adapters: { 93 | adapterId: returnAdapterId, 94 | returnAdapterId, 95 | }, 96 | action: Action.SendToken, 97 | sender: hubChain.hubAddress, 98 | destinationChainId: payloadData.receiverFolksChainId, 99 | handler: spokeTokenData.spokeAddress, 100 | data: returnData, 101 | extraArgs: returnExtraArgs, 102 | overrideData: overrideTokenData, 103 | }; 104 | const returnGasLimit = await estimateAdapterReceiveGasLimit( 105 | hubChain.folksChainId, 106 | payloadData.receiverFolksChainId, 107 | FolksCore.getEVMProvider(payloadData.receiverFolksChainId), 108 | network, 109 | MessageDirection.HubToSpoke, 110 | returnMessageBuilderParams, 111 | ); 112 | 113 | const bridgeRouter = getBridgeRouterHubContract(provider, hubChain.bridgeRouterAddress); 114 | const messageToSend = buildMessageToSend(ChainType.EVM, returnMessageBuilderParams, { 115 | gasLimit: returnGasLimit, 116 | }); 117 | 118 | const adapterFees = await bridgeRouter.read.getSendFee([messageToSend]); 119 | 120 | return { 121 | adapterFees, 122 | extraArgs: { returnAdapterId, returnGasLimit }, 123 | }; 124 | } 125 | 126 | export async function getHubReverseMessageExtraArgsAndAdapterFees( 127 | provider: EVMProvider, 128 | hubChain: HubChain, 129 | network: NetworkType, 130 | userAddress: GenericAddress, 131 | message: MessageReceived, 132 | extraArgsParams: ReverseMessageExtraArgsParams, 133 | payload: Payload, 134 | ): Promise<{ 135 | adapterFees: bigint; 136 | extraArgs: ReverseMessageExtraArgs; 137 | }> { 138 | const { action, data } = payload; 139 | const payloadData = decodeMessagePayloadData(action as ReversibleHubAction, data); 140 | const folksTokenId = getFolksTokenIdFromPool(payloadData.poolId, network); 141 | 142 | const returnAdapterId = extraArgsParams?.returnAdapterId ?? message.returnAdapterId; 143 | const accountId = extraArgsParams?.accountId ?? payload.accountId; 144 | 145 | const spokeChain = getSpokeChain(message.sourceChainId, network); 146 | const spokeTokenData = getSpokeTokenData(spokeChain, folksTokenId); 147 | const hubTokenData = getHubTokenData(folksTokenId, network); 148 | 149 | if (hubTokenData.token.type === TokenType.CROSS_CHAIN) 150 | assertAdapterSupportsCrossChainToken(message.sourceChainId, hubTokenData.token, returnAdapterId); 151 | else assertAdapterSupportsDataMessage(message.sourceChainId, returnAdapterId); 152 | 153 | const returnData: SendTokenMessageData = { 154 | amount: payloadData.amount, 155 | }; 156 | const returnExtraArgs: SendTokenExtraArgs = { 157 | folksTokenId, 158 | token: hubTokenData.token, 159 | recipient: spokeTokenData.spokeAddress, 160 | amount: payloadData.amount, 161 | }; 162 | const overrideTokenData: OverrideTokenData = { 163 | folksTokenId, 164 | token: spokeTokenData.token, 165 | address: spokeTokenData.spokeAddress, 166 | amount: payloadData.amount, 167 | }; 168 | const returnMessageBuilderParams: MessageBuilderParams = { 169 | userAddress, 170 | accountId, 171 | adapters: { 172 | adapterId: returnAdapterId, 173 | returnAdapterId, 174 | }, 175 | action: Action.SendToken, 176 | sender: hubChain.hubAddress, 177 | destinationChainId: message.sourceChainId, 178 | handler: spokeTokenData.spokeAddress, 179 | data: returnData, 180 | extraArgs: returnExtraArgs, 181 | overrideData: overrideTokenData, 182 | }; 183 | const returnGasLimit = await estimateAdapterReceiveGasLimit( 184 | hubChain.folksChainId, 185 | message.sourceChainId, 186 | FolksCore.getEVMProvider(message.sourceChainId), 187 | network, 188 | MessageDirection.HubToSpoke, 189 | returnMessageBuilderParams, 190 | ); 191 | 192 | const bridgeRouter = getBridgeRouterHubContract(provider, hubChain.bridgeRouterAddress); 193 | const messageToSend = buildMessageToSend(ChainType.EVM, returnMessageBuilderParams, { 194 | gasLimit: returnGasLimit, 195 | }); 196 | const adapterFees = await bridgeRouter.read.getSendFee([ 197 | { 198 | ...messageToSend, 199 | extraArgs: buildSendTokenExtraArgsWhenRemoving( 200 | spokeTokenData.spokeAddress, 201 | hubTokenData.token, 202 | payloadData.amount, 203 | ), 204 | }, 205 | ]); 206 | return { 207 | adapterFees, 208 | extraArgs: { 209 | accountId, 210 | returnAdapterId, 211 | returnGasLimit, 212 | }, 213 | }; 214 | } 215 | -------------------------------------------------------------------------------- /src/chains/evm/spoke/modules/folks-evm-rewards-v2.ts: -------------------------------------------------------------------------------- 1 | import { REWARDS_TYPE } from "../../../../common/constants/reward.js"; 2 | import { getSpokeRewardsCommonAddress } from "../../../../common/utils/chain.js"; 3 | import { GAS_LIMIT_ESTIMATE_INCREASE } from "../../common/constants/contract.js"; 4 | import { getEvmSignerAccount } from "../../common/utils/chain.js"; 5 | import { getBridgeRouterSpokeContract, getSpokeRewardsV2CommonContract } from "../utils/contract.js"; 6 | 7 | import type { EvmAddress } from "../../../../common/types/address.js"; 8 | import type { SpokeChain } from "../../../../common/types/chain.js"; 9 | import type { AccountId } from "../../../../common/types/lending.js"; 10 | import type { MessageToSend } from "../../../../common/types/message.js"; 11 | import type { PrepareClaimRewardsV2Call } from "../../../../common/types/module.js"; 12 | import type { PoolEpoch, ReceiveRewardToken } from "../../hub/types/rewards-v2.js"; 13 | import type { Client, EstimateGasParameters, WalletClient } from "viem"; 14 | 15 | export const prepare = { 16 | async claimRewards( 17 | provider: Client, 18 | sender: EvmAddress, 19 | messageToSend: MessageToSend, 20 | accountId: AccountId, 21 | poolEpochsToClaim: Array, 22 | rewardTokensToReceive: Array, 23 | spokeChain: SpokeChain, 24 | transactionOptions: EstimateGasParameters = { account: sender }, 25 | ): Promise { 26 | const spokeRewardsV2CommonAddress = getSpokeRewardsCommonAddress(spokeChain, REWARDS_TYPE.V2); 27 | const spokeRewardsCommon = getSpokeRewardsV2CommonContract(provider, spokeRewardsV2CommonAddress); 28 | const bridgeRouter = getBridgeRouterSpokeContract(provider, spokeChain.bridgeRouterAddress); 29 | 30 | // get adapter fees 31 | const msgValue = await bridgeRouter.read.getSendFee([messageToSend]); 32 | 33 | // get gas limits 34 | const gasLimit = await spokeRewardsCommon.estimateGas.claimRewards( 35 | [messageToSend.params, accountId, poolEpochsToClaim, rewardTokensToReceive], 36 | { 37 | value: msgValue, 38 | ...transactionOptions, 39 | }, 40 | ); 41 | 42 | return { 43 | msgValue, 44 | gasLimit: gasLimit + GAS_LIMIT_ESTIMATE_INCREASE, 45 | messageParams: messageToSend.params, 46 | poolEpochs: poolEpochsToClaim, 47 | rewardTokens: rewardTokensToReceive, 48 | spokeRewardsV2CommonAddress, 49 | }; 50 | }, 51 | }; 52 | 53 | export const write = { 54 | async claimRewards( 55 | provider: Client, 56 | signer: WalletClient, 57 | accountId: AccountId, 58 | prepareCall: PrepareClaimRewardsV2Call, 59 | ) { 60 | const { 61 | msgValue, 62 | gasLimit, 63 | maxFeePerGas, 64 | maxPriorityFeePerGas, 65 | messageParams, 66 | poolEpochs, 67 | rewardTokens, 68 | spokeRewardsV2CommonAddress, 69 | } = prepareCall; 70 | 71 | const spokeRewardsCommon = getSpokeRewardsV2CommonContract(provider, spokeRewardsV2CommonAddress, signer); 72 | 73 | return await spokeRewardsCommon.write.claimRewards([messageParams, accountId, poolEpochs, rewardTokens], { 74 | account: getEvmSignerAccount(signer), 75 | chain: signer.chain, 76 | gas: gasLimit, 77 | maxFeePerGas, 78 | maxPriorityFeePerGas, 79 | value: msgValue, 80 | }); 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /src/chains/evm/spoke/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * as FolksEvmAccount from "./folks-evm-account.js"; 2 | export * as FolksEvmGmp from "./folks-evm-gmp.js"; 3 | export * as FolksEvmLoan from "./folks-evm-loan.js"; 4 | export * as FolksEvmRewardsV2 from "./folks-evm-rewards-v2.js"; 5 | -------------------------------------------------------------------------------- /src/chains/evm/spoke/types/pool.ts: -------------------------------------------------------------------------------- 1 | export type TokenRateLimit = { 2 | periodLength: bigint; 3 | periodOffset: bigint; 4 | periodLimit: bigint; 5 | currentCapacity: bigint; 6 | nextPeriodReset: bigint; 7 | }; 8 | -------------------------------------------------------------------------------- /src/chains/evm/spoke/utils/contract.ts: -------------------------------------------------------------------------------- 1 | import { getContract } from "viem"; 2 | 3 | import { SPOKE_CHAIN } from "../../../../common/constants/chain.js"; 4 | import { ChainType } from "../../../../common/types/chain.js"; 5 | import { TokenType } from "../../../../common/types/token.js"; 6 | import { convertFromGenericAddress } from "../../../../common/utils/address.js"; 7 | import { BridgeRouterSpokeAbi } from "../constants/abi/bridge-router-spoke-abi.js"; 8 | import { SpokeCommonAbi } from "../constants/abi/spoke-common-abi.js"; 9 | import { SpokeRewardsV2CommonAbi } from "../constants/abi/spoke-rewards-v2-common-abi.js"; 10 | import { SpokeTokenAbi } from "../constants/abi/spoke-token-abi.js"; 11 | 12 | import type { GenericAddress } from "../../../../common/types/address.js"; 13 | import type { FolksChainId, NetworkType } from "../../../../common/types/chain.js"; 14 | import type { FolksTokenId } from "../../../../common/types/token.js"; 15 | import type { GetReadContractReturnType } from "../../common/types/contract.js"; 16 | import type { Client, GetContractReturnType, WalletClient } from "viem"; 17 | 18 | export function getSpokeCommonContract( 19 | provider: Client, 20 | address: GenericAddress, 21 | ): GetReadContractReturnType; 22 | export function getSpokeCommonContract( 23 | provider: Client, 24 | address: GenericAddress, 25 | signer: WalletClient, 26 | ): GetContractReturnType; 27 | export function getSpokeCommonContract( 28 | provider: Client, 29 | address: GenericAddress, 30 | signer?: WalletClient, 31 | ): GetReadContractReturnType | GetContractReturnType { 32 | return getContract({ 33 | abi: SpokeCommonAbi, 34 | address: convertFromGenericAddress(address, ChainType.EVM), 35 | client: { wallet: signer, public: provider }, 36 | }); 37 | } 38 | 39 | export function getBridgeRouterSpokeContract( 40 | provider: Client, 41 | address: GenericAddress, 42 | ): GetReadContractReturnType; 43 | export function getBridgeRouterSpokeContract( 44 | provider: Client, 45 | address: GenericAddress, 46 | signer: WalletClient, 47 | ): GetContractReturnType; 48 | export function getBridgeRouterSpokeContract( 49 | provider: Client, 50 | address: GenericAddress, 51 | signer?: WalletClient, 52 | ): GetReadContractReturnType { 53 | return getContract({ 54 | abi: BridgeRouterSpokeAbi, 55 | address: convertFromGenericAddress(address, ChainType.EVM), 56 | client: { wallet: signer, public: provider }, 57 | }); 58 | } 59 | 60 | export function getSpokeTokenContract( 61 | provider: Client, 62 | address: GenericAddress, 63 | ): GetReadContractReturnType; 64 | export function getSpokeTokenContract( 65 | provider: Client, 66 | address: GenericAddress, 67 | signer: WalletClient, 68 | ): GetContractReturnType; 69 | export function getSpokeTokenContract( 70 | provider: Client, 71 | address: GenericAddress, 72 | signer?: WalletClient, 73 | ): GetReadContractReturnType | GetContractReturnType { 74 | return getContract({ 75 | abi: SpokeTokenAbi, 76 | address: convertFromGenericAddress(address, ChainType.EVM), 77 | client: { wallet: signer, public: provider }, 78 | }); 79 | } 80 | 81 | export function getSpokeRewardsV2CommonContract( 82 | provider: Client, 83 | address: GenericAddress, 84 | ): GetReadContractReturnType; 85 | export function getSpokeRewardsV2CommonContract( 86 | provider: Client, 87 | address: GenericAddress, 88 | signer: WalletClient, 89 | ): GetContractReturnType; 90 | export function getSpokeRewardsV2CommonContract( 91 | provider: Client, 92 | address: GenericAddress, 93 | signer?: WalletClient, 94 | ): 95 | | GetReadContractReturnType 96 | | GetContractReturnType { 97 | return getContract({ 98 | abi: SpokeRewardsV2CommonAbi, 99 | address: convertFromGenericAddress(address, ChainType.EVM), 100 | client: { wallet: signer, public: provider }, 101 | }); 102 | } 103 | 104 | export function getSpokeEvmTokenAddress(network: NetworkType, folksChainId: FolksChainId, folksTokenId: FolksTokenId) { 105 | const spokeToken = SPOKE_CHAIN[network][folksChainId]?.tokens[folksTokenId]?.token; 106 | if (!spokeToken) 107 | throw new Error( 108 | `Spoke token not found for network: ${network}, folksChainId: ${folksChainId}, folksTokenId: ${folksTokenId}`, 109 | ); 110 | if (spokeToken.type === TokenType.NATIVE) 111 | throw new Error( 112 | `Spoke token is native for network: ${network}, folksChainId: ${folksChainId}, folksTokenId: ${folksTokenId}`, 113 | ); 114 | return convertFromGenericAddress(spokeToken.address, ChainType.EVM); 115 | } 116 | -------------------------------------------------------------------------------- /src/common/constants/adapter.ts: -------------------------------------------------------------------------------- 1 | import { AdapterType } from "../types/message.js"; 2 | 3 | export const DATA_ADAPTERS = [AdapterType.HUB, AdapterType.WORMHOLE_DATA, AdapterType.CCIP_DATA] as const; 4 | -------------------------------------------------------------------------------- /src/common/constants/bytes.ts: -------------------------------------------------------------------------------- 1 | export const UINT8_LENGTH = 1; 2 | export const UINT16_LENGTH = 2; 3 | export const UINT256_LENGTH = 32; 4 | export const BYTES4_LENGTH = 4; 5 | export const BYTES32_LENGTH = 32; 6 | 7 | export const EVM_ADDRESS_BYTES_LENGTH = 20; 8 | -------------------------------------------------------------------------------- /src/common/constants/gmp.ts: -------------------------------------------------------------------------------- 1 | import { ChainType } from "../types/chain.js"; 2 | import { convertToGenericAddress } from "../utils/address.js"; 3 | 4 | import { FOLKS_CHAIN_ID } from "./chain.js"; 5 | 6 | import type { EvmAddress } from "../types/address.js"; 7 | import type { FolksChainId } from "../types/chain.js"; 8 | import type { CCIPData, WormholeData } from "../types/gmp.js"; 9 | 10 | export const WORMHOLE_DATA: Record = { 11 | [FOLKS_CHAIN_ID.AVALANCHE]: { 12 | wormholeChainId: 6, 13 | wormholeRelayer: convertToGenericAddress("0x27428DD2d3DD32A4D7f7C497eAaa23130d894911" as EvmAddress, ChainType.EVM), 14 | }, 15 | [FOLKS_CHAIN_ID.ETHEREUM]: { 16 | wormholeChainId: 2, 17 | wormholeRelayer: convertToGenericAddress("0x27428DD2d3DD32A4D7f7C497eAaa23130d894911" as EvmAddress, ChainType.EVM), 18 | }, 19 | [FOLKS_CHAIN_ID.BASE]: { 20 | wormholeChainId: 30, 21 | wormholeRelayer: convertToGenericAddress("0x706f82e9bb5b0813501714ab5974216704980e31" as EvmAddress, ChainType.EVM), 22 | }, 23 | [FOLKS_CHAIN_ID.BSC]: { 24 | wormholeChainId: 4, 25 | wormholeRelayer: convertToGenericAddress("0x27428DD2d3DD32A4D7f7C497eAaa23130d894911" as EvmAddress, ChainType.EVM), 26 | }, 27 | [FOLKS_CHAIN_ID.ARBITRUM]: { 28 | wormholeChainId: 23, 29 | wormholeRelayer: convertToGenericAddress("0x27428DD2d3DD32A4D7f7C497eAaa23130d894911" as EvmAddress, ChainType.EVM), 30 | }, 31 | [FOLKS_CHAIN_ID.POLYGON]: { 32 | wormholeChainId: 5, 33 | wormholeRelayer: convertToGenericAddress("0x27428DD2d3DD32A4D7f7C497eAaa23130d894911" as EvmAddress, ChainType.EVM), 34 | }, 35 | [FOLKS_CHAIN_ID.AVALANCHE_FUJI]: { 36 | wormholeChainId: 6, 37 | wormholeRelayer: convertToGenericAddress("0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB" as EvmAddress, ChainType.EVM), 38 | }, 39 | [FOLKS_CHAIN_ID.ETHEREUM_SEPOLIA]: { 40 | wormholeChainId: 10002, 41 | wormholeRelayer: convertToGenericAddress("0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470" as EvmAddress, ChainType.EVM), 42 | }, 43 | [FOLKS_CHAIN_ID.BASE_SEPOLIA]: { 44 | wormholeChainId: 10004, 45 | wormholeRelayer: convertToGenericAddress("0x93BAD53DDfB6132b0aC8E37f6029163E63372cEE" as EvmAddress, ChainType.EVM), 46 | }, 47 | [FOLKS_CHAIN_ID.BSC_TESTNET]: { 48 | wormholeChainId: 4, 49 | wormholeRelayer: convertToGenericAddress("0x80aC94316391752A193C1c47E27D382b507c93F3" as EvmAddress, ChainType.EVM), 50 | }, 51 | [FOLKS_CHAIN_ID.ARBITRUM_SEPOLIA]: { 52 | wormholeChainId: 10003, 53 | wormholeRelayer: convertToGenericAddress("0x7B1bD7a6b4E61c2a123AC6BC2cbfC614437D0470" as EvmAddress, ChainType.EVM), 54 | }, 55 | [FOLKS_CHAIN_ID.MONAD_TESTNET]: { 56 | wormholeChainId: 48, 57 | wormholeRelayer: convertToGenericAddress("0x362fca37E45fe1096b42021b543f462D49a5C8df" as EvmAddress, ChainType.EVM), 58 | }, 59 | }; 60 | 61 | export const CCIP_DATA: Record = { 62 | [FOLKS_CHAIN_ID.AVALANCHE]: { 63 | ccipChainId: BigInt("6433500567565415381"), 64 | ccipRouter: convertToGenericAddress("0xF4c7E640EdA248ef95972845a62bdC74237805dB" as EvmAddress, ChainType.EVM), 65 | }, 66 | [FOLKS_CHAIN_ID.ETHEREUM]: { 67 | ccipChainId: BigInt("5009297550715157269"), 68 | ccipRouter: convertToGenericAddress("0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D" as EvmAddress, ChainType.EVM), 69 | }, 70 | [FOLKS_CHAIN_ID.BASE]: { 71 | ccipChainId: BigInt("15971525489660198786"), 72 | ccipRouter: convertToGenericAddress("0x881e3A65B4d4a04dD529061dd0071cf975F58bCD" as EvmAddress, ChainType.EVM), 73 | }, 74 | [FOLKS_CHAIN_ID.BSC]: { 75 | ccipChainId: BigInt("11344663589394136015"), 76 | ccipRouter: convertToGenericAddress("0x34B03Cb9086d7D758AC55af71584F81A598759FE" as EvmAddress, ChainType.EVM), 77 | }, 78 | [FOLKS_CHAIN_ID.ARBITRUM]: { 79 | ccipChainId: BigInt("4949039107694359620"), 80 | ccipRouter: convertToGenericAddress("0x141fa059441E0ca23ce184B6A78bafD2A517DdE8" as EvmAddress, ChainType.EVM), 81 | }, 82 | [FOLKS_CHAIN_ID.POLYGON]: { 83 | ccipChainId: BigInt("4051577828743386545"), 84 | ccipRouter: convertToGenericAddress("0x849c5ED5a80F5B408Dd4969b78c2C8fdf0565Bfe" as EvmAddress, ChainType.EVM), 85 | }, 86 | [FOLKS_CHAIN_ID.AVALANCHE_FUJI]: { 87 | ccipChainId: BigInt("14767482510784806043"), 88 | ccipRouter: convertToGenericAddress("0xF694E193200268f9a4868e4Aa017A0118C9a8177" as EvmAddress, ChainType.EVM), 89 | }, 90 | [FOLKS_CHAIN_ID.ETHEREUM_SEPOLIA]: { 91 | ccipChainId: BigInt("16015286601757825753"), 92 | ccipRouter: convertToGenericAddress("0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59" as EvmAddress, ChainType.EVM), 93 | }, 94 | [FOLKS_CHAIN_ID.BASE_SEPOLIA]: { 95 | ccipChainId: BigInt("10344971235874465080"), 96 | ccipRouter: convertToGenericAddress("0xD3b06cEbF099CE7DA4AcCf578aaebFDBd6e88a93" as EvmAddress, ChainType.EVM), 97 | }, 98 | [FOLKS_CHAIN_ID.BSC_TESTNET]: { 99 | ccipChainId: BigInt("13264668187771770619"), 100 | ccipRouter: convertToGenericAddress("0xE1053aE1857476f36A3C62580FF9b016E8EE8F6f" as EvmAddress, ChainType.EVM), 101 | }, 102 | [FOLKS_CHAIN_ID.ARBITRUM_SEPOLIA]: { 103 | ccipChainId: BigInt("3478487238524512106"), 104 | ccipRouter: convertToGenericAddress("0x2a9C5afB0d0e4BAb2BCdaE109EC4b0c4Be15a165" as EvmAddress, ChainType.EVM), 105 | }, 106 | [FOLKS_CHAIN_ID.MONAD_TESTNET]: { 107 | ccipChainId: BigInt("2183018362218727504"), 108 | ccipRouter: convertToGenericAddress("0x5f16e51e3Dcb255480F090157DD01bA962a53E54" as EvmAddress, ChainType.EVM), 109 | }, 110 | }; 111 | -------------------------------------------------------------------------------- /src/common/constants/lending.ts: -------------------------------------------------------------------------------- 1 | import { getEmptyBytes } from "../utils/bytes.js"; 2 | 3 | import { BYTES32_LENGTH } from "./bytes.js"; 4 | 5 | import type { AccountId } from "../types/lending.js"; 6 | 7 | export const NULL_ACCOUNT_ID: AccountId = getEmptyBytes(BYTES32_LENGTH) as AccountId; 8 | -------------------------------------------------------------------------------- /src/common/constants/message.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../types/message.js"; 2 | 3 | export const FINALITY = { 4 | IMMEDIATE: BigInt(0), 5 | FINALISED: BigInt(1), 6 | } as const; 7 | 8 | export const REVERSIBLE_HUB_ACTIONS = [Action.CreateLoanAndDeposit, Action.Deposit, Action.Repay] as const; 9 | 10 | export const SEND_TOKEN_ACTIONS = [Action.CreateLoanAndDeposit, Action.Deposit, Action.Repay] as const; 11 | export const RECEIVE_TOKEN_ACTIONS = [Action.Withdraw, Action.Borrow] as const; 12 | export const HUB_ACTIONS = [Action.DepositFToken, Action.WithdrawFToken, Action.Liquidate, Action.SendToken] as const; 13 | -------------------------------------------------------------------------------- /src/common/constants/pool.ts: -------------------------------------------------------------------------------- 1 | import { NetworkType } from "../types/chain.js"; 2 | import { MAINNET_FOLKS_TOKEN_ID, TESTNET_FOLKS_TOKEN_ID } from "../types/token.js"; 3 | 4 | import type { FolksTokenId, MainnetFolksTokenId, TestnetFolksTokenId } from "../types/token.js"; 5 | 6 | export const MAINNET_POOLS = { 7 | [MAINNET_FOLKS_TOKEN_ID.USDC]: 1, 8 | [MAINNET_FOLKS_TOKEN_ID.AVAX]: 2, 9 | [MAINNET_FOLKS_TOKEN_ID.sAVAX]: 3, 10 | [MAINNET_FOLKS_TOKEN_ID.ETH_eth]: 4, 11 | [MAINNET_FOLKS_TOKEN_ID.ETH_base]: 5, 12 | [MAINNET_FOLKS_TOKEN_ID.wETH_ava]: 6, 13 | [MAINNET_FOLKS_TOKEN_ID.wBTC_eth]: 7, 14 | [MAINNET_FOLKS_TOKEN_ID.BTCb_ava]: 8, 15 | [MAINNET_FOLKS_TOKEN_ID.cbBTC_base]: 9, 16 | [MAINNET_FOLKS_TOKEN_ID.BNB]: 10, 17 | [MAINNET_FOLKS_TOKEN_ID.ETHB_bsc]: 11, 18 | [MAINNET_FOLKS_TOKEN_ID.BTCB_bsc]: 12, 19 | [MAINNET_FOLKS_TOKEN_ID.ETH_arb]: 13, 20 | [MAINNET_FOLKS_TOKEN_ID.ARB]: 14, 21 | [MAINNET_FOLKS_TOKEN_ID.SolvBTC]: 15, 22 | [MAINNET_FOLKS_TOKEN_ID.JOE]: 16, 23 | [MAINNET_FOLKS_TOKEN_ID.ggAVAX]: 17, 24 | [MAINNET_FOLKS_TOKEN_ID.POL]: 19, 25 | [MAINNET_FOLKS_TOKEN_ID.wBTC_pol]: 20, 26 | [MAINNET_FOLKS_TOKEN_ID.wETH_pol]: 21, 27 | [MAINNET_FOLKS_TOKEN_ID.aUSD_ava]: 22, 28 | [MAINNET_FOLKS_TOKEN_ID.savUSD]: 23, 29 | [MAINNET_FOLKS_TOKEN_ID.wBTC_arb]: 24, 30 | [MAINNET_FOLKS_TOKEN_ID.tBTC_arb]: 25, 31 | [MAINNET_FOLKS_TOKEN_ID.wstETH_arb]: 26, 32 | [MAINNET_FOLKS_TOKEN_ID.weETH_arb]: 27, 33 | [MAINNET_FOLKS_TOKEN_ID.rsETH_arb]: 28, 34 | [MAINNET_FOLKS_TOKEN_ID.wstETH_pol]: 29, 35 | [MAINNET_FOLKS_TOKEN_ID.LINK_pol]: 30, 36 | [MAINNET_FOLKS_TOKEN_ID.MaticX]: 31, 37 | [MAINNET_FOLKS_TOKEN_ID.ATH_eth]: 32, 38 | [MAINNET_FOLKS_TOKEN_ID.pyUSD_eth]: 33, 39 | [MAINNET_FOLKS_TOKEN_ID.rlUSD_eth]: 34, 40 | [MAINNET_FOLKS_TOKEN_ID.wstETH_eth]: 35, 41 | [MAINNET_FOLKS_TOKEN_ID.weETH_eth]: 36, 42 | [MAINNET_FOLKS_TOKEN_ID.AERO_base]: 37, 43 | [MAINNET_FOLKS_TOKEN_ID.cbETH_base]: 38, 44 | [MAINNET_FOLKS_TOKEN_ID.wstETH_base]: 39, 45 | [MAINNET_FOLKS_TOKEN_ID.weETH_base]: 40, 46 | [MAINNET_FOLKS_TOKEN_ID.VIRTUAL_base]: 41, 47 | [MAINNET_FOLKS_TOKEN_ID.KAITO_base]: 42, 48 | [MAINNET_FOLKS_TOKEN_ID.aUSD_pol]: 43, 49 | } as const satisfies Record; 50 | 51 | export const TESTNET_POOLS = { 52 | [TESTNET_FOLKS_TOKEN_ID.USDC]: 128, 53 | [TESTNET_FOLKS_TOKEN_ID.AVAX]: 129, 54 | [TESTNET_FOLKS_TOKEN_ID.ETH_eth_sep]: 130, 55 | [TESTNET_FOLKS_TOKEN_ID.ETH_base_sep]: 131, 56 | [TESTNET_FOLKS_TOKEN_ID.ETH_arb_sep]: 132, 57 | [TESTNET_FOLKS_TOKEN_ID.LINK_eth_sep]: 133, 58 | [TESTNET_FOLKS_TOKEN_ID.BNB]: 134, 59 | [TESTNET_FOLKS_TOKEN_ID.CCIP_BnM]: 135, 60 | [TESTNET_FOLKS_TOKEN_ID.MON]: 136, 61 | [TESTNET_FOLKS_TOKEN_ID.sMON]: 138, 62 | [TESTNET_FOLKS_TOKEN_ID.aprMON]: 139, 63 | [TESTNET_FOLKS_TOKEN_ID.gMON]: 140, 64 | [TESTNET_FOLKS_TOKEN_ID.shMON]: 141, 65 | } as const satisfies Record; 66 | 67 | const MAINNET_FOLKS_TOKEN_IDS_FROM_POOL = Object.fromEntries( 68 | Object.entries(MAINNET_POOLS).map(([token, poolId]) => [poolId, token]), 69 | ) as Partial>; 70 | 71 | const TESTNET_FOLKS_TOKEN_IDS_FROM_POOL = Object.fromEntries( 72 | Object.entries(TESTNET_POOLS).map(([token, poolId]) => [poolId, token]), 73 | ) as Partial>; 74 | 75 | export const FOLKS_TOKEN_IDS_FROM_POOL_BY_NETWORK = { 76 | [NetworkType.MAINNET]: MAINNET_FOLKS_TOKEN_IDS_FROM_POOL, 77 | [NetworkType.TESTNET]: TESTNET_FOLKS_TOKEN_IDS_FROM_POOL, 78 | } as Record>>; 79 | -------------------------------------------------------------------------------- /src/common/constants/reward.ts: -------------------------------------------------------------------------------- 1 | export const MAINNET_REWARDS_TOKEN_ID = { 2 | AVAX: 1, 3 | GoGoPool: 2, 4 | USDC_arb: 3, 5 | POL: 4, 6 | } as const; 7 | 8 | export const TESTNET_REWARDS_TOKEN_ID = { 9 | AVAX: 128, 10 | USDC_base_sep: 129, 11 | } as const; 12 | 13 | export const REWARDS_TYPE = { 14 | V1: "V1", 15 | V2: "V2", 16 | } as const; 17 | -------------------------------------------------------------------------------- /src/common/constants/token.ts: -------------------------------------------------------------------------------- 1 | import { HUB_CHAIN } from "../../chains/evm/hub/constants/chain.js"; 2 | import { NetworkType } from "../types/chain.js"; 3 | import { MAINNET_LOAN_TYPE_ID, TESTNET_LOAN_TYPE_ID } from "../types/lending.js"; 4 | import { MAINNET_FOLKS_TOKEN_ID, TESTNET_FOLKS_TOKEN_ID } from "../types/token.js"; 5 | 6 | import type { LoanTypeId } from "../types/lending.js"; 7 | import type { FolksTokenId } from "../types/token.js"; 8 | 9 | export const CROSS_CHAIN_FOLKS_TOKEN_ID: Array = [ 10 | MAINNET_FOLKS_TOKEN_ID.USDC, 11 | MAINNET_FOLKS_TOKEN_ID.SolvBTC, 12 | TESTNET_FOLKS_TOKEN_ID.USDC, 13 | TESTNET_FOLKS_TOKEN_ID.CCIP_BnM, 14 | ]; 15 | 16 | export const FOLKS_TOKEN_IDS_BY_LOAN_TYPE: Record>>> = { 17 | [NetworkType.MAINNET]: Object.fromEntries( 18 | Object.values(MAINNET_LOAN_TYPE_ID).map((loanType) => [ 19 | loanType, 20 | Object.values(HUB_CHAIN.MAINNET.tokens) 21 | .filter((token) => token.supportedLoanTypes.has(loanType)) 22 | .map((token) => token.folksTokenId), 23 | ]), 24 | ), 25 | [NetworkType.TESTNET]: Object.fromEntries( 26 | Object.values(TESTNET_LOAN_TYPE_ID).map((loanType) => [ 27 | loanType, 28 | Object.values(HUB_CHAIN.TESTNET.tokens) 29 | .filter((token) => token.supportedLoanTypes.has(loanType)) 30 | .map((token) => token.folksTokenId), 31 | ]), 32 | ), 33 | }; 34 | -------------------------------------------------------------------------------- /src/common/types/adapter.ts: -------------------------------------------------------------------------------- 1 | import type { FolksChainId, NetworkType } from "./chain.js"; 2 | import type { ClaimRewardAction, DataAction, ReceiveTokenAction, SendTokenAction } from "./message.js"; 3 | import type { RewardsTokenId, RewardsType } from "./rewards.js"; 4 | import type { FolksTokenId } from "./token.js"; 5 | 6 | export enum MessageAdapterParamsType { 7 | SendToken, 8 | ReceiveToken, 9 | Data, 10 | ClaimReward, 11 | } 12 | 13 | export type SendTokenMessageAdapterParams = { 14 | messageAdapterParamType: MessageAdapterParamsType.SendToken; 15 | action: SendTokenAction; 16 | network: NetworkType; 17 | sourceFolksChainId: FolksChainId; 18 | folksTokenId: FolksTokenId; 19 | }; 20 | 21 | export type ReceiveTokenMessageAdapterParams = { 22 | messageAdapterParamType: MessageAdapterParamsType.ReceiveToken; 23 | action: ReceiveTokenAction; 24 | network: NetworkType; 25 | sourceFolksChainId: FolksChainId; 26 | destFolksChainId: FolksChainId; 27 | folksTokenId: FolksTokenId; 28 | }; 29 | 30 | export type DataMessageAdapterParams = { 31 | messageAdapterParamType: MessageAdapterParamsType.Data; 32 | action: DataAction; 33 | network: NetworkType; 34 | sourceFolksChainId: FolksChainId; 35 | }; 36 | 37 | export type RewardMessageAdapterParams = { 38 | messageAdapterParamType: MessageAdapterParamsType.ClaimReward; 39 | action: ClaimRewardAction; 40 | rewardType: RewardsType; 41 | network: NetworkType; 42 | sourceFolksChainId: FolksChainId; 43 | rewardTokenIds: Array; 44 | }; 45 | 46 | export type MessageAdapterParams = 47 | | SendTokenMessageAdapterParams 48 | | ReceiveTokenMessageAdapterParams 49 | | DataMessageAdapterParams 50 | | RewardMessageAdapterParams; 51 | -------------------------------------------------------------------------------- /src/common/types/address.ts: -------------------------------------------------------------------------------- 1 | import type { Branded } from "./brand.js"; 2 | import type { ChainType } from "./chain.js"; 3 | 4 | export type GenericAddress = Branded<`0x${string}`, "GenericAddress">; 5 | export type EvmAddress = Branded<`0x${string}`, "EvmAddress">; 6 | 7 | type AddressTypeMap = { 8 | [ChainType.EVM]: EvmAddress; 9 | }; 10 | 11 | export type AddressType = AddressTypeMap[T]; 12 | -------------------------------------------------------------------------------- /src/common/types/brand.ts: -------------------------------------------------------------------------------- 1 | declare const __brand: unique symbol; 2 | type Brand = { [__brand]: B }; 3 | export type Branded = T & Brand; 4 | -------------------------------------------------------------------------------- /src/common/types/chain.ts: -------------------------------------------------------------------------------- 1 | import type { GenericAddress } from "./address.js"; 2 | import type { AdapterType } from "./message.js"; 3 | import type { SpokeRewardTokenData } from "./rewards-v2.js"; 4 | import type { RewardsTokenId } from "./rewards.js"; 5 | import type { FolksTokenId, SpokeTokenData } from "./token.js"; 6 | import type { EvmChainName } from "../../chains/evm/common/types/chain.js"; 7 | import type { FOLKS_CHAIN_ID, MAINNET_FOLKS_CHAIN_ID, TESTNET_FOLKS_CHAIN_ID } from "../constants/chain.js"; 8 | import type { REWARDS_TYPE } from "../constants/reward.js"; 9 | 10 | export enum ChainType { 11 | EVM = "EVM", 12 | } 13 | 14 | export enum NetworkType { 15 | MAINNET = "MAINNET", 16 | TESTNET = "TESTNET", 17 | } 18 | 19 | export type SpokeRewardsV2 = { 20 | spokeRewardsCommonAddress: GenericAddress; 21 | tokens: Partial>; 22 | }; 23 | 24 | export type SpokeRewardsMap = { 25 | bridgeRouterAddress: GenericAddress; 26 | adapters: Partial>; 27 | [REWARDS_TYPE.V1]?: undefined; 28 | [REWARDS_TYPE.V2]?: SpokeRewardsV2; 29 | }; 30 | 31 | export type FolksChainName = EvmChainName; 32 | export type MainnetFolksChainId = (typeof MAINNET_FOLKS_CHAIN_ID)[keyof typeof MAINNET_FOLKS_CHAIN_ID]; 33 | export type TestnetFolksChainId = (typeof TESTNET_FOLKS_CHAIN_ID)[keyof typeof TESTNET_FOLKS_CHAIN_ID]; 34 | export type FolksChainId = (typeof FOLKS_CHAIN_ID)[keyof typeof FOLKS_CHAIN_ID]; 35 | 36 | export type IFolksChain = { 37 | folksChainId: FolksChainId; 38 | }; 39 | 40 | export type FolksChain = { 41 | chainName: string; 42 | chainType: ChainType; 43 | chainId: number | string | undefined; 44 | network: NetworkType; 45 | } & IFolksChain; 46 | 47 | export type SpokeChain = { 48 | spokeCommonAddress: GenericAddress; 49 | bridgeRouterAddress: GenericAddress; 50 | adapters: Partial>; 51 | tokens: Partial>; 52 | rewards: SpokeRewardsMap; 53 | } & IFolksChain; 54 | -------------------------------------------------------------------------------- /src/common/types/core.ts: -------------------------------------------------------------------------------- 1 | import type { ChainType, NetworkType, FolksChainId } from "./chain.js"; 2 | import type { Client as EVMProvider, WalletClient as EVMSigner } from "viem"; 3 | 4 | type FolksProviderTypeMap = { 5 | [ChainType.EVM]: EVMProvider; 6 | }; 7 | 8 | type FolksSignerTypeMap = { 9 | [ChainType.EVM]: EVMSigner; 10 | }; 11 | 12 | export type FolksProviderType = FolksProviderTypeMap[T]; 13 | export type FolksSignerType = FolksSignerTypeMap[T]; 14 | 15 | export type FolksProvider = EVMProvider | null; 16 | export type FolksSigner = { signer: EVMSigner; folksChainId: FolksChainId }; 17 | 18 | export type FolksCoreProvider = { 19 | evm: Partial>; 20 | }; 21 | export type FolksCoreConfig = { 22 | network: NetworkType; 23 | provider: FolksCoreProvider; 24 | }; 25 | 26 | export type FolksEvmSigner = { 27 | signer: EVMSigner; 28 | chainType: ChainType.EVM; 29 | }; 30 | 31 | export type FolksChainSigner = FolksEvmSigner; 32 | -------------------------------------------------------------------------------- /src/common/types/gmp.ts: -------------------------------------------------------------------------------- 1 | import type { GenericAddress } from "./address.js"; 2 | import type { Branded } from "./brand.js"; 3 | 4 | export enum MessageDirection { 5 | SpokeToHub, 6 | HubToSpoke, 7 | } 8 | 9 | export type WormholeData = { 10 | wormholeChainId: number; 11 | wormholeRelayer: GenericAddress; 12 | }; 13 | 14 | export type CCIPData = { 15 | ccipChainId: bigint; 16 | ccipRouter: GenericAddress; 17 | }; 18 | 19 | export type MessageId = Branded<`0x${string}`, "MessageId">; 20 | -------------------------------------------------------------------------------- /src/common/types/lending.ts: -------------------------------------------------------------------------------- 1 | import type { Branded } from "./brand.js"; 2 | 3 | export type Nonce = Branded<`0x${string}`, "Nonce">; 4 | export type AccountId = Branded<`0x${string}`, "AccountId">; 5 | export type LoanId = Branded<`0x${string}`, "LoanId">; 6 | export type LoanName = Branded<`0x${string}`, "LoanName">; 7 | 8 | export const MAINNET_LOAN_TYPE_ID = { 9 | DEPOSIT: 1, // no support for borrows 10 | GENERAL: 2, 11 | AVAX_EFFICIENCY: 3, 12 | ETH_EFFICIENCY: 4, 13 | STABLECOIN_EFFICIENCY: 5, 14 | POL_EFFICIENCY: 6, 15 | BTC_EFFICIENCY: 7, 16 | } as const; 17 | export type MainnetLoanTypeId = (typeof MAINNET_LOAN_TYPE_ID)[keyof typeof MAINNET_LOAN_TYPE_ID]; 18 | 19 | export const TESTNET_LOAN_TYPE_ID = { 20 | DEPOSIT: 1, // no support for borrows 21 | GENERAL: 2, 22 | } as const; 23 | export type TestnetLoanTypeId = (typeof TESTNET_LOAN_TYPE_ID)[keyof typeof TESTNET_LOAN_TYPE_ID]; 24 | 25 | export type LoanTypeId = MainnetLoanTypeId | TestnetLoanTypeId; 26 | -------------------------------------------------------------------------------- /src/common/types/module.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | PrepareCreateAccountCall as PrepareCreateAccountEVMCall, 3 | PrepareInviteAddressCall as PrepareInviteAddressEVMCall, 4 | PrepareAcceptInviteAddressCall as PrepareAcceptInviteAddressEVMCall, 5 | PrepareUnregisterAddressCall as PrepareUnregisterAddressEVMCall, 6 | PrepareCreateLoanCall as PrepareCreateLoanEVMCall, 7 | PrepareDeleteLoanCall as PrepareDeleteLoanEVMCall, 8 | PrepareCreateLoanAndDepositCall as PrepareCreateLoanAndDepositEVMCall, 9 | PrepareDepositCall as PrepareDepositEVMCall, 10 | PrepareWithdrawCall as PrepareWithdrawEVMCall, 11 | PrepareCall as PrepareEVMCall, 12 | PrepareBorrowCall as PrepareBorrowEVMCall, 13 | PrepareRepayCall as PrepareRepayEVMCall, 14 | PrepareRepayWithCollateralCall as PrepareRepayWithCollateralEVMCall, 15 | PrepareSwitchBorrowTypeCall as PrepareSwitchBorrowTypeEVMCall, 16 | PrepareLiquidateCall as PrepareLiquidateEVMCall, 17 | PrepareUpdateUserPointsInLoansCall as PrepareUpdateUserPointsInLoansEVMCall, 18 | PrepareRetryMessageCall as PrepareRetryMessageEVMCall, 19 | PrepareReverseMessageCall as PrepareReverseMessageEVMCall, 20 | PrepareResendWormholeMessageCall as PrepareResendWormholeMessageEVMCall, 21 | PrepareUpdateAccountsPointsForRewardsV1Call as PrepareUpdateAccountsPointsForRewardsV1EVMCall, 22 | PrepareUpdateAccountsPointsForRewardsV2Call as PrepareUpdateAccountsPointsForRewardsV2EVMCall, 23 | PrepareClaimRewardsV1Call as PrepareClaimRewardsV1EVMCall, 24 | PrepareClaimRewardsV2Call as PrepareClaimRewardsV2EVMCall, 25 | } from "../../chains/evm/common/types/module.js"; 26 | 27 | export type PrepareCall = PrepareEVMCall; 28 | 29 | export type PrepareCreateAccountCall = PrepareCreateAccountEVMCall; 30 | export type PrepareInviteAddressCall = PrepareInviteAddressEVMCall; 31 | export type PrepareAcceptInviteAddressCall = PrepareAcceptInviteAddressEVMCall; 32 | export type PrepareUnregisterAddressCall = PrepareUnregisterAddressEVMCall; 33 | 34 | export type PrepareCreateLoanCall = PrepareCreateLoanEVMCall; 35 | export type PrepareDeleteLoanCall = PrepareDeleteLoanEVMCall; 36 | export type PrepareCreateLoanAndDepositCall = PrepareCreateLoanAndDepositEVMCall; 37 | export type PrepareDepositCall = PrepareDepositEVMCall; 38 | export type PrepareWithdrawCall = PrepareWithdrawEVMCall; 39 | export type PrepareBorrowCall = PrepareBorrowEVMCall; 40 | export type PrepareRepayCall = PrepareRepayEVMCall; 41 | export type PrepareRepayWithCollateralCall = PrepareRepayWithCollateralEVMCall; 42 | export type PrepareSwitchBorrowTypeCall = PrepareSwitchBorrowTypeEVMCall; 43 | export type PrepareLiquidateCall = PrepareLiquidateEVMCall; 44 | export type PrepareUpdateUserPointsInLoansCall = PrepareUpdateUserPointsInLoansEVMCall; 45 | export type PrepareRetryMessageCall = PrepareRetryMessageEVMCall; 46 | export type PrepareReverseMessageCall = PrepareReverseMessageEVMCall; 47 | export type PrepareResendWormholeMessageCall = PrepareResendWormholeMessageEVMCall; 48 | export type PrepareUpdateAccountsPointsForRewardsV1Call = PrepareUpdateAccountsPointsForRewardsV1EVMCall; 49 | export type PrepareUpdateAccountsPointsForRewardsV2Call = PrepareUpdateAccountsPointsForRewardsV2EVMCall; 50 | export type PrepareClaimRewardsV1Call = PrepareClaimRewardsV1EVMCall; 51 | export type PrepareClaimRewardsV2Call = PrepareClaimRewardsV2EVMCall; 52 | -------------------------------------------------------------------------------- /src/common/types/rewards-v2.ts: -------------------------------------------------------------------------------- 1 | import type { GenericAddress } from "./address.js"; 2 | import type { RewardsTokenId } from "./rewards.js"; 3 | import type { Erc20SpokeTokenType, NativeTokenType } from "./token.js"; 4 | 5 | export type FolksSpokeRewardTokenType = Erc20SpokeTokenType | NativeTokenType; 6 | 7 | export type SpokeRewardTokenData = { 8 | rewardTokenId: RewardsTokenId; 9 | spokeAddress: GenericAddress; 10 | token: FolksSpokeRewardTokenType; 11 | }; 12 | -------------------------------------------------------------------------------- /src/common/types/rewards.ts: -------------------------------------------------------------------------------- 1 | import type { MAINNET_REWARDS_TOKEN_ID, REWARDS_TYPE, TESTNET_REWARDS_TOKEN_ID } from "../constants/reward.js"; 2 | 3 | export type RewardsType = (typeof REWARDS_TYPE)[keyof typeof REWARDS_TYPE]; 4 | 5 | export type TestnetRewardsTokenId = (typeof TESTNET_REWARDS_TOKEN_ID)[keyof typeof TESTNET_REWARDS_TOKEN_ID]; 6 | export type MainnetRewardsTokenId = (typeof MAINNET_REWARDS_TOKEN_ID)[keyof typeof MAINNET_REWARDS_TOKEN_ID]; 7 | 8 | export type RewardsTokenId = MainnetRewardsTokenId | TestnetRewardsTokenId; 9 | -------------------------------------------------------------------------------- /src/common/types/token.ts: -------------------------------------------------------------------------------- 1 | import type { GenericAddress } from "./address.js"; 2 | import type { AdapterType } from "./message.js"; 3 | 4 | export const MAINNET_FOLKS_TOKEN_ID = { 5 | USDC: "USDC", 6 | AVAX: "AVAX", 7 | sAVAX: "sAVAX", 8 | ETH_eth: "ETH_eth", 9 | ETH_base: "ETH_base", 10 | wETH_ava: "wETH_ava", 11 | wBTC_eth: "wBTC_eth", 12 | BTCb_ava: "BTCb_ava", 13 | cbBTC_base: "cbBTC_base", 14 | BNB: "BNB", 15 | ETHB_bsc: "ETHB_bsc", 16 | BTCB_bsc: "BTCB_bsc", 17 | ETH_arb: "ETH_arb", 18 | ARB: "ARB", 19 | SolvBTC: "SolvBTC", 20 | JOE: "JOE", 21 | ggAVAX: "ggAVAX", 22 | POL: "POL", 23 | wBTC_pol: "wBTC_pol", 24 | wETH_pol: "wETH_pol", 25 | aUSD_ava: "aUSD_ava", 26 | savUSD: "savUSD", 27 | wBTC_arb: "wBTC_arb", 28 | tBTC_arb: "tBTC_arb", 29 | wstETH_arb: "wstETH_arb", 30 | weETH_arb: "weETH_arb", 31 | rsETH_arb: "rsETH_arb", 32 | wstETH_pol: "wstETH_pol", 33 | LINK_pol: "LINK_pol", 34 | MaticX: "MaticX", 35 | ATH_eth: "ATH_eth", 36 | pyUSD_eth: "pyUSD_eth", 37 | rlUSD_eth: "rlUSD_eth", 38 | wstETH_eth: "wstETH_eth", 39 | weETH_eth: "weETH_eth", 40 | AERO_base: "AERO_base", 41 | cbETH_base: "cbETH_base", 42 | wstETH_base: "wstETH_base", 43 | weETH_base: "weETH_base", 44 | VIRTUAL_base: "VIRTUAL_base", 45 | KAITO_base: "KAITO_base", 46 | aUSD_pol: "aUSD_pol", 47 | } as const; 48 | export type MainnetFolksTokenId = (typeof MAINNET_FOLKS_TOKEN_ID)[keyof typeof MAINNET_FOLKS_TOKEN_ID]; 49 | 50 | export const TESTNET_FOLKS_TOKEN_ID = { 51 | USDC: "USDC", 52 | AVAX: "AVAX", 53 | ETH_eth_sep: "ETH_eth_sep", 54 | ETH_base_sep: "ETH_base_sep", 55 | ETH_arb_sep: "ETH_arb_sep", 56 | LINK_eth_sep: "LINK_eth_sep", 57 | BNB: "BNB", 58 | CCIP_BnM: "CCIP_BnM", 59 | MON: "MON", 60 | sMON: "sMON", 61 | aprMON: "aprMON", 62 | gMON: "gMON", 63 | shMON: "shMON", 64 | } as const; 65 | export type TestnetFolksTokenId = (typeof TESTNET_FOLKS_TOKEN_ID)[keyof typeof TESTNET_FOLKS_TOKEN_ID]; 66 | 67 | export type FolksTokenId = MainnetFolksTokenId | TestnetFolksTokenId; 68 | 69 | export enum TokenType { 70 | NATIVE = "NATIVE", 71 | ERC20 = "ERC20", 72 | CROSS_CHAIN = "CROSS_CHAIN", 73 | } 74 | 75 | export type ITokenData = { 76 | folksTokenId: FolksTokenId; 77 | }; 78 | 79 | export type Erc20SpokeTokenType = { 80 | type: TokenType.ERC20; 81 | address: GenericAddress; 82 | decimals: number; 83 | }; 84 | 85 | export type Erc20HubTokenType = { 86 | type: TokenType.ERC20; 87 | decimals: number; 88 | }; 89 | 90 | export type CrossChainTokenType = { 91 | type: TokenType.CROSS_CHAIN; 92 | adapters: Array; 93 | address: GenericAddress; 94 | decimals: number; 95 | }; 96 | 97 | export type NativeTokenType = { 98 | type: TokenType.NATIVE; 99 | decimals: number; 100 | }; 101 | 102 | export type FolksSpokeTokenType = Erc20SpokeTokenType | CrossChainTokenType | NativeTokenType; 103 | export type FolksHubTokenType = Erc20HubTokenType | CrossChainTokenType | NativeTokenType; 104 | 105 | export type SpokeTokenData = { 106 | poolId: number; 107 | spokeAddress: GenericAddress; 108 | token: FolksSpokeTokenType; 109 | } & ITokenData; 110 | -------------------------------------------------------------------------------- /src/common/utils/adapter.ts: -------------------------------------------------------------------------------- 1 | import { getHubTokenData, isHubChain } from "../../chains/evm/hub/utils/chain.js"; 2 | import { ensureNonEmpty, intersect } from "../../utils/array.js"; 3 | import { exhaustiveCheck } from "../../utils/exhaustive-check.js"; 4 | import { FolksCore } from "../../xchain/core/folks-core.js"; 5 | import { DATA_ADAPTERS } from "../constants/adapter.js"; 6 | import { MessageAdapterParamsType } from "../types/adapter.js"; 7 | import { AdapterType } from "../types/message.js"; 8 | import { TokenType } from "../types/token.js"; 9 | 10 | import { getRewardTokenSpokeChain, getSpokeChain } from "./chain.js"; 11 | 12 | import type { NonEmptyArray } from "../../types/generics.js"; 13 | import type { MessageAdapterParams, ReceiveTokenMessageAdapterParams } from "../types/adapter.js"; 14 | import type { FolksChainId, NetworkType } from "../types/chain.js"; 15 | import type { SupportedMessageAdaptersMap } from "../types/message.js"; 16 | import type { CrossChainTokenType, FolksTokenId } from "../types/token.js"; 17 | 18 | export function getSpokeAdapterIds( 19 | folksChainId: FolksChainId, 20 | network: NetworkType, 21 | isRewards = false, 22 | ): NonEmptyArray { 23 | const spokeChain = getSpokeChain(folksChainId, network); 24 | const { adapters } = isRewards ? spokeChain.rewards : spokeChain; 25 | const adapterIds = Object.keys(adapters).map(Number); 26 | return ensureNonEmpty(adapterIds, `No adapters found for chain ${folksChainId}`); 27 | } 28 | 29 | export function doesAdapterSupportDataMessage(folksChainId: FolksChainId, adapterId: AdapterType): boolean { 30 | const isHub = isHubChain(folksChainId, FolksCore.getSelectedNetwork()); 31 | return ( 32 | (isHub && adapterId === AdapterType.HUB) || 33 | (!isHub && (adapterId === AdapterType.WORMHOLE_DATA || adapterId === AdapterType.CCIP_DATA)) 34 | ); 35 | } 36 | 37 | export function assertAdapterSupportsDataMessage(folksChainId: FolksChainId, adapterId: AdapterType): void { 38 | if (!doesAdapterSupportDataMessage(folksChainId, adapterId)) 39 | throw Error(`Adapter ${adapterId} does not support data message for folksChainId: ${folksChainId}`); 40 | } 41 | 42 | export function doesAdapterSupportTokenMessage(folksChainId: FolksChainId, adapterId: AdapterType): boolean { 43 | const isHub = isHubChain(folksChainId, FolksCore.getSelectedNetwork()); 44 | return ( 45 | (isHub && adapterId === AdapterType.HUB) || 46 | (!isHub && (adapterId === AdapterType.WORMHOLE_CCTP || adapterId === AdapterType.CCIP_TOKEN)) 47 | ); 48 | } 49 | 50 | export function assertAdapterSupportsTokenMessage(folksChainId: FolksChainId, adapterId: AdapterType): void { 51 | if (!doesAdapterSupportTokenMessage(folksChainId, adapterId)) 52 | throw Error(`Adapter ${adapterId} does not support token message for folksChainId: ${folksChainId}`); 53 | } 54 | 55 | export function doesAdapterSupportCrossChainToken( 56 | crossChainToken: CrossChainTokenType, 57 | folksChainId: FolksChainId, 58 | adapterId: AdapterType, 59 | ): boolean { 60 | const isHub = isHubChain(folksChainId, FolksCore.getSelectedNetwork()); 61 | return (isHub && adapterId === AdapterType.HUB) || (!isHub && crossChainToken.adapters.includes(adapterId)); 62 | } 63 | 64 | export function assertCrossChainTokenSupportedByAdapter( 65 | crossChainToken: CrossChainTokenType, 66 | folksChainId: FolksChainId, 67 | adapterId: AdapterType, 68 | ): void { 69 | if (!doesAdapterSupportCrossChainToken(crossChainToken, folksChainId, adapterId)) 70 | throw Error(`Adapter ${adapterId} does not support cross chain token: ${crossChainToken.address}`); 71 | } 72 | 73 | export function assertAdapterSupportsCrossChainToken( 74 | folksChainId: FolksChainId, 75 | crossChainToken: CrossChainTokenType, 76 | adapterId: AdapterType, 77 | ): void { 78 | assertAdapterSupportsTokenMessage(folksChainId, adapterId); 79 | assertCrossChainTokenSupportedByAdapter(crossChainToken, folksChainId, adapterId); 80 | } 81 | 82 | export function doesAdapterSupportReceiverValue(folksChainId: FolksChainId, adapterId: AdapterType): boolean { 83 | const isHub = isHubChain(folksChainId, FolksCore.getSelectedNetwork()); 84 | return ( 85 | (isHub && adapterId === AdapterType.HUB) || 86 | (!isHub && (adapterId === AdapterType.WORMHOLE_DATA || adapterId === AdapterType.WORMHOLE_CCTP)) 87 | ); 88 | } 89 | 90 | export function assertAdapterSupportsReceiverValue(folksChainId: FolksChainId, adapterId: AdapterType): void { 91 | if (!doesAdapterSupportReceiverValue(folksChainId, adapterId)) 92 | throw Error(`Adapter ${adapterId} does not support receiver value for folksChainId: ${folksChainId}`); 93 | } 94 | 95 | function getSendTokenAdapterIds(folksTokenId: FolksTokenId, network: NetworkType) { 96 | const hubTokenData = getHubTokenData(folksTokenId, network); 97 | if (hubTokenData.token.type == TokenType.CROSS_CHAIN) return hubTokenData.token.adapters; 98 | return DATA_ADAPTERS; 99 | } 100 | 101 | function getMessageAdapterIds(messageAdapterParams: MessageAdapterParams) { 102 | const { network, messageAdapterParamType } = messageAdapterParams; 103 | if (messageAdapterParamType === MessageAdapterParamsType.SendToken) 104 | return getSendTokenAdapterIds(messageAdapterParams.folksTokenId, network); 105 | return DATA_ADAPTERS; 106 | } 107 | 108 | function getReturnMessageAdapterIds({ folksTokenId, network }: ReceiveTokenMessageAdapterParams) { 109 | return getSendTokenAdapterIds(folksTokenId, network); 110 | } 111 | 112 | export function getSupportedMessageAdapters( 113 | params: T, 114 | ): SupportedMessageAdaptersMap[T["messageAdapterParamType"]] { 115 | const isRewards = params.messageAdapterParamType === MessageAdapterParamsType.ClaimReward; 116 | const { messageAdapterParamType, sourceFolksChainId, network } = params; 117 | const spokeAdapterIds = getSpokeAdapterIds(sourceFolksChainId, network, isRewards); 118 | const supportedAdapterIds = ensureNonEmpty( 119 | getMessageAdapterIds(params).filter(intersect(spokeAdapterIds)), 120 | `No supported adapters found for chain ${sourceFolksChainId}`, 121 | ); 122 | 123 | switch (messageAdapterParamType) { 124 | case MessageAdapterParamsType.SendToken: 125 | return { 126 | adapterIds: supportedAdapterIds, 127 | returnAdapterIds: supportedAdapterIds, 128 | } as SupportedMessageAdaptersMap[T["messageAdapterParamType"]]; 129 | 130 | case MessageAdapterParamsType.ReceiveToken: { 131 | const destSpokeAdapterIds = getSpokeAdapterIds(params.destFolksChainId, network, isRewards); 132 | const supportedReturnAdapterIds = ensureNonEmpty( 133 | getReturnMessageAdapterIds(params).filter(intersect(destSpokeAdapterIds)), 134 | `No supported return adapters found for chain ${params.destFolksChainId}`, 135 | ); 136 | return { 137 | adapterIds: supportedAdapterIds, 138 | returnAdapterIds: supportedReturnAdapterIds, 139 | } as SupportedMessageAdaptersMap[T["messageAdapterParamType"]]; 140 | } 141 | 142 | case MessageAdapterParamsType.Data: 143 | return { 144 | adapterIds: supportedAdapterIds, 145 | returnAdapterIds: [AdapterType.HUB], 146 | } as SupportedMessageAdaptersMap[T["messageAdapterParamType"]]; 147 | 148 | case MessageAdapterParamsType.ClaimReward: { 149 | const { rewardTokenIds, rewardType } = params; 150 | return { 151 | adapterIds: supportedAdapterIds, 152 | returnAdapterIds: Object.fromEntries( 153 | rewardTokenIds.map((rewardTokenIds) => [ 154 | rewardTokenIds, 155 | getSpokeAdapterIds( 156 | getRewardTokenSpokeChain(rewardTokenIds, network, rewardType).folksChainId, 157 | network, 158 | isRewards, 159 | ), 160 | ]), 161 | ), 162 | } as SupportedMessageAdaptersMap[T["messageAdapterParamType"]]; 163 | } 164 | 165 | default: 166 | return exhaustiveCheck(messageAdapterParamType); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/common/utils/address.ts: -------------------------------------------------------------------------------- 1 | import { getAddress, pad, sliceHex } from "viem"; 2 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 3 | 4 | import { exhaustiveCheck } from "../../utils/exhaustive-check.js"; 5 | import { BYTES32_LENGTH, EVM_ADDRESS_BYTES_LENGTH } from "../constants/bytes.js"; 6 | import { ChainType } from "../types/chain.js"; 7 | 8 | import type { AddressType, EvmAddress, GenericAddress } from "../types/address.js"; 9 | 10 | export function getRandomGenericAddress(): GenericAddress { 11 | return pad(privateKeyToAccount(generatePrivateKey()).address, { 12 | size: BYTES32_LENGTH, 13 | }).toLowerCase() as GenericAddress; 14 | } 15 | 16 | export function isGenericAddress(address: GenericAddress): boolean { 17 | return address.length === 64 + 2; 18 | } 19 | 20 | export function convertToGenericAddress( 21 | address: AddressType, 22 | fromChainType: ChainType, 23 | ): GenericAddress { 24 | switch (fromChainType) { 25 | case ChainType.EVM: 26 | return pad(address as EvmAddress, { 27 | size: BYTES32_LENGTH, 28 | }).toLowerCase() as GenericAddress; 29 | default: 30 | return exhaustiveCheck(fromChainType); 31 | } 32 | } 33 | 34 | export function convertFromGenericAddress( 35 | address: GenericAddress, 36 | toChainType: ChainType, 37 | ): AddressType { 38 | switch (toChainType) { 39 | case ChainType.EVM: 40 | return getAddress(sliceHex(address, BYTES32_LENGTH - EVM_ADDRESS_BYTES_LENGTH)) as EvmAddress; 41 | default: 42 | return exhaustiveCheck(toChainType); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/common/utils/bytes.ts: -------------------------------------------------------------------------------- 1 | import { bytesToHex, pad, toHex } from "viem"; 2 | 3 | import type { Hex } from "viem"; 4 | 5 | export function getEmptyBytes(length: number): string { 6 | return pad("0x", { size: length }); 7 | } 8 | 9 | export function convertNumberToBytes(num: number | bigint, length: number): Hex { 10 | // insert 0s at the beginning if data is smaller than length bytes 11 | const buf = Buffer.alloc(length, 0); 12 | 13 | // convert num to bytes 14 | const hex = num.toString(16); 15 | const isEven = hex.length % 2 === 0; 16 | const bytes = Buffer.from(isEven ? hex : "0" + hex, "hex"); 17 | 18 | // write bytes to fixed length buf 19 | bytes.copy(buf, buf.length - bytes.length); 20 | return toHex(buf); 21 | } 22 | 23 | export function convertStringToBytes(str: string): Hex { 24 | return toHex("0x" + Buffer.from(str).toString("hex")); 25 | } 26 | 27 | export function convertBooleanToByte(bool: boolean): Hex { 28 | return bool ? "0x01" : "0x00"; 29 | } 30 | 31 | export function getRandomBytes(length: number): Hex { 32 | return bytesToHex(crypto.getRandomValues(new Uint8Array(length))); 33 | } 34 | -------------------------------------------------------------------------------- /src/common/utils/chain.ts: -------------------------------------------------------------------------------- 1 | import { getEvmSignerAddress } from "../../chains/evm/common/utils/chain.js"; 2 | import { getHubChainAdapterAddress, isHubChain } from "../../chains/evm/hub/utils/chain.js"; 3 | import { exhaustiveCheck } from "../../utils/exhaustive-check.js"; 4 | import { FOLKS_CHAIN, SPOKE_CHAIN } from "../constants/chain.js"; 5 | import { ChainType } from "../types/chain.js"; 6 | 7 | import { convertToGenericAddress } from "./address.js"; 8 | 9 | import type { GenericAddress } from "../types/address.js"; 10 | import type { FolksChain, FolksChainId, NetworkType, SpokeChain } from "../types/chain.js"; 11 | import type { FolksChainSigner } from "../types/core.js"; 12 | import type { AdapterType } from "../types/message.js"; 13 | import type { SpokeRewardTokenData } from "../types/rewards-v2.js"; 14 | import type { RewardsTokenId, RewardsType } from "../types/rewards.js"; 15 | import type { FolksTokenId, SpokeTokenData } from "../types/token.js"; 16 | 17 | export function getFolksChain(folksChainId: FolksChainId, network: NetworkType): FolksChain { 18 | const folksChain = FOLKS_CHAIN[network][folksChainId]; 19 | if (!folksChain) throw new Error(`Folks Chain not found for folksChainId: ${folksChainId}`); 20 | 21 | return folksChain; 22 | } 23 | 24 | export function getFolksChainsByNetwork(network: NetworkType): Array { 25 | return Object.values(FOLKS_CHAIN[network]); 26 | } 27 | 28 | export function getFolksChainIdsByNetwork(networkType: NetworkType): Array { 29 | return getFolksChainsByNetwork(networkType).map((folksChain) => folksChain.folksChainId); 30 | } 31 | 32 | export function isSpokeChainSupported(folksChainId: FolksChainId, network: NetworkType): boolean { 33 | return SPOKE_CHAIN[network][folksChainId] !== undefined; 34 | } 35 | 36 | export function assertSpokeChainSupported(folksChainId: FolksChainId, network: NetworkType) { 37 | if (!isSpokeChainSupported(folksChainId, network)) 38 | throw new Error(`Spoke chain is not supported for folksChainId: ${folksChainId}`); 39 | } 40 | 41 | export function getSpokeChain(folksChainId: FolksChainId, network: NetworkType): SpokeChain { 42 | const spokeChain = SPOKE_CHAIN[network][folksChainId]; 43 | if (!spokeChain) throw new Error(`Spoke chain not found for folksChainId: ${folksChainId}`); 44 | 45 | return spokeChain; 46 | } 47 | 48 | export function doesSpokeSupportFolksToken(spokeChain: SpokeChain, folksTokenId: FolksTokenId): boolean { 49 | return spokeChain.tokens[folksTokenId] !== undefined; 50 | } 51 | 52 | export function getSpokeTokenData(spokeChain: SpokeChain, folksTokenId: FolksTokenId): SpokeTokenData { 53 | const tokenData = spokeChain.tokens[folksTokenId]; 54 | if (!tokenData) throw new Error(`Spoke Token not found for folksTokenId: ${folksTokenId}`); 55 | 56 | return tokenData; 57 | } 58 | 59 | export function getRewardTokenSpokeChain( 60 | rewardTokenId: RewardsTokenId, 61 | network: NetworkType, 62 | rewardType: RewardsType, 63 | ): SpokeChain { 64 | const spokeChain = Object.values(SPOKE_CHAIN[network]).find( 65 | (spokeChain) => spokeChain.rewards[rewardType]?.tokens[rewardTokenId] !== undefined, 66 | ); 67 | if (!spokeChain) throw new Error(`Spoke chain not found for rewardTokenId: ${rewardTokenId} - ${rewardType}`); 68 | 69 | return spokeChain; 70 | } 71 | 72 | export function getSpokeRewardsCommonAddress(spokeChain: SpokeChain, rewardType: RewardsType): GenericAddress { 73 | const spokeRewardsCommonAddress = spokeChain.rewards[rewardType]?.spokeRewardsCommonAddress; 74 | if (!spokeRewardsCommonAddress) throw new Error(`Rewards ${rewardType} Spoke Common Address not found`); 75 | 76 | return spokeRewardsCommonAddress; 77 | } 78 | 79 | export function getSpokeRewardsTokenData( 80 | spokeChain: SpokeChain, 81 | rewardTokenId: RewardsTokenId, 82 | rewardType: RewardsType, 83 | ): SpokeRewardTokenData { 84 | const spokeRewardTokenData = spokeChain.rewards[rewardType]?.tokens[rewardTokenId]; 85 | if (!spokeRewardTokenData) 86 | throw new Error(`Rewards ${rewardType} Spoke Token not found for rewardTokenId: ${rewardTokenId}`); 87 | 88 | return spokeRewardTokenData; 89 | } 90 | 91 | export function isFolksTokenSupported( 92 | folksTokenId: FolksTokenId, 93 | folksChainId: FolksChainId, 94 | network: NetworkType, 95 | ): boolean { 96 | const spokeChain = getSpokeChain(folksChainId, network); 97 | return doesSpokeSupportFolksToken(spokeChain, folksTokenId); 98 | } 99 | 100 | export function assertSpokeChainSupportFolksToken( 101 | folksChainId: FolksChainId, 102 | folksTokenId: FolksTokenId, 103 | network: NetworkType, 104 | ) { 105 | if (!isFolksTokenSupported(folksTokenId, folksChainId, network)) 106 | throw new Error(`Folks Token ${folksTokenId} is not supported on Folks Chain ${folksChainId}`); 107 | } 108 | 109 | export function getSpokeChainAdapterAddress( 110 | folksChainId: FolksChainId, 111 | network: NetworkType, 112 | adapterType: AdapterType, 113 | isRewards = false, 114 | ): GenericAddress { 115 | const spokeChain = getSpokeChain(folksChainId, network); 116 | const { adapters } = isRewards ? spokeChain.rewards : spokeChain; 117 | const adapterAddress = adapters[adapterType]; 118 | if (adapterAddress) return adapterAddress; 119 | throw new Error(`Adapter ${adapterType} not found for spoke chain ${folksChainId}`); 120 | } 121 | 122 | export function getAdapterAddress( 123 | folksChainId: FolksChainId, 124 | network: NetworkType, 125 | adapterType: AdapterType, 126 | isRewards = false, 127 | ) { 128 | if (isHubChain(folksChainId, network)) return getHubChainAdapterAddress(network, adapterType, isRewards); 129 | return getSpokeChainAdapterAddress(folksChainId, network, adapterType, isRewards); 130 | } 131 | 132 | export function getSpokeChainBridgeRouterAddress(spokeChain: SpokeChain, isRewards = false) { 133 | const { bridgeRouterAddress } = isRewards ? spokeChain.rewards : spokeChain; 134 | return bridgeRouterAddress; 135 | } 136 | 137 | export function getSignerGenericAddress(folksChainSigner: FolksChainSigner): GenericAddress { 138 | const chainType = folksChainSigner.chainType; 139 | switch (chainType) { 140 | case ChainType.EVM: 141 | return convertToGenericAddress(getEvmSignerAddress(folksChainSigner.signer), chainType); 142 | default: 143 | return exhaustiveCheck(chainType); 144 | } 145 | } 146 | 147 | export function assertHubChainSelected(folksChainId: FolksChainId, network: NetworkType) { 148 | if (!isHubChain(folksChainId, network)) throw new Error(`Wrong chain selected: ${folksChainId}. Expected Hub chain`); 149 | } 150 | -------------------------------------------------------------------------------- /src/common/utils/formulae.ts: -------------------------------------------------------------------------------- 1 | import * as dn from "dnum"; 2 | 3 | import { SECONDS_IN_YEAR, expBySquaring, unixTime } from "./math-lib.js"; 4 | 5 | import type { Dnum } from "dnum"; 6 | 7 | export function calcPeriodNumber(offset: bigint, length: bigint): bigint { 8 | return (BigInt(unixTime()) + offset) / length; 9 | } 10 | 11 | export function calcNextPeriodReset(periodNumber: bigint, offset: bigint, length: bigint): bigint { 12 | return (periodNumber + BigInt(1)) * length - offset; 13 | } 14 | 15 | export function calcOverallBorrowInterestRate( 16 | totalVarDebt: bigint, 17 | totalStableDebt: bigint, 18 | variableBorInterestRate: Dnum, 19 | avgStableBorInterestRate: Dnum, 20 | ): Dnum { 21 | const totalDebt = totalVarDebt + totalStableDebt; 22 | if (totalDebt === 0n) return dn.from(0, 18); 23 | 24 | return dn.div( 25 | dn.add( 26 | dn.mul(totalVarDebt, variableBorInterestRate, { rounding: "ROUND_DOWN", decimals: 0 }), 27 | dn.mul(totalStableDebt, avgStableBorInterestRate, { rounding: "ROUND_DOWN", decimals: 0 }), 28 | ), 29 | totalDebt, 30 | { rounding: "ROUND_DOWN", decimals: 18 }, 31 | ); 32 | } 33 | 34 | export function calcDepositInterestIndex( 35 | dirt1: Dnum, 36 | diit1: Dnum, 37 | latestUpdate: bigint, 38 | lastTimestamp = BigInt(unixTime()), 39 | ): Dnum { 40 | const dt = lastTimestamp - latestUpdate; 41 | return dn.mul( 42 | diit1, 43 | dn.add( 44 | dn.from(1, 18), 45 | dn.div(dn.mul(dirt1, dt, { rounding: "ROUND_DOWN" }), SECONDS_IN_YEAR, { 46 | rounding: "ROUND_DOWN", 47 | }), 48 | ), 49 | { rounding: "ROUND_DOWN" }, 50 | ); 51 | } 52 | 53 | export function calcBorrowInterestIndex( 54 | birt1: Dnum, 55 | biit1: Dnum, 56 | latestUpdate: bigint, 57 | lastTimestamp = BigInt(unixTime()), 58 | ): Dnum { 59 | const dt = lastTimestamp - latestUpdate; 60 | return dn.mul( 61 | biit1, 62 | expBySquaring(dn.add(dn.from(1, 18), dn.div(birt1, SECONDS_IN_YEAR, { rounding: "ROUND_DOWN" })), dt), 63 | { rounding: "ROUND_DOWN" }, 64 | ); 65 | } 66 | 67 | export function calcRetention( 68 | actualRetained: bigint, 69 | totalDebt: bigint, 70 | overallBorrowInterestRate: Dnum, 71 | retentionRate: Dnum, 72 | latestUpdate: bigint, 73 | lastTimestamp = BigInt(unixTime()), 74 | ): bigint { 75 | const dt = lastTimestamp - latestUpdate; 76 | 77 | const [retainedDelta] = dn.div( 78 | dn.mul( 79 | dn.mul(dn.mul(totalDebt, overallBorrowInterestRate, { rounding: "ROUND_DOWN", decimals: 0 }), retentionRate, { 80 | rounding: "ROUND_DOWN", 81 | decimals: 0, 82 | }), 83 | dt, 84 | { rounding: "ROUND_DOWN" }, 85 | ), 86 | SECONDS_IN_YEAR, 87 | { 88 | rounding: "ROUND_DOWN", 89 | }, 90 | ); 91 | return actualRetained + retainedDelta; 92 | } 93 | 94 | export function calcRewardIndex(used: bigint, ma: bigint, rit1: Dnum, rs: Dnum, latestUpdate: bigint): Dnum { 95 | if (used <= ma) return rit1; 96 | const dt = BigInt(unixTime()) - latestUpdate; 97 | return dn.add( 98 | rit1, 99 | dn.div(dn.mul(rs, dt, { rounding: "ROUND_DOWN" }), used, { 100 | rounding: "ROUND_DOWN", 101 | }), 102 | ); 103 | } 104 | 105 | export function calcAccruedRewards(amount: bigint, rit: Dnum, ritn1: Dnum): bigint { 106 | const [accruedRewards] = dn.mul([amount, 0], dn.sub(rit, ritn1), { rounding: "ROUND_DOWN" }); 107 | return accruedRewards; 108 | } 109 | 110 | export function toFAmount(underlyingAmount: bigint, diit: Dnum): bigint { 111 | const [fAmount] = dn.div([underlyingAmount, 0], diit, { rounding: "ROUND_DOWN" }); 112 | return fAmount; 113 | } 114 | 115 | export function toUnderlyingAmount(fAmount: bigint, diit: Dnum): bigint { 116 | const [underlyingAmount] = dn.mul([fAmount, 0], diit, { rounding: "ROUND_DOWN" }); 117 | return underlyingAmount; 118 | } 119 | 120 | export function calcAssetDollarValue(amount: bigint, tokenPrice: Dnum, tokenDecimals: number): Dnum { 121 | return dn.mul(tokenPrice, [amount, tokenDecimals], { 122 | rounding: "ROUND_DOWN", 123 | }); 124 | } 125 | 126 | export function calcCollateralAssetLoanValue( 127 | amount: bigint, 128 | tokenPrice: Dnum, 129 | tokenDecimals: number, 130 | collateralFactor: Dnum, 131 | ): Dnum { 132 | return dn.mul(calcAssetDollarValue(amount, tokenPrice, tokenDecimals), collateralFactor, { 133 | rounding: "ROUND_DOWN", 134 | decimals: 8, 135 | }); 136 | } 137 | 138 | export function calcBorrowAssetLoanValue( 139 | amount: bigint, 140 | tokenPrice: Dnum, 141 | tokenDecimals: number, 142 | borrowFactor: Dnum, 143 | ): Dnum { 144 | return dn.mul(calcAssetDollarValue(amount, tokenPrice, tokenDecimals), borrowFactor, { 145 | rounding: "ROUND_UP", 146 | decimals: 8, 147 | }); 148 | } 149 | 150 | export function calcBorrowBalance(bbtn1: bigint, biit: Dnum, biitn1: Dnum): bigint { 151 | const [borrowBalance] = dn.mul([bbtn1, 0], dn.div(biit, biitn1, { rounding: "ROUND_UP" }), { 152 | rounding: "ROUND_UP", 153 | }); 154 | return borrowBalance; 155 | } 156 | 157 | export function calcStableInterestRate(bbt: bigint, amount: bigint, sbirtn1: Dnum, sbirt1: Dnum): Dnum { 158 | return dn.div(dn.add(dn.mul(sbirtn1, [bbt, 0]), dn.mul(sbirt1, [amount, 0])), dn.add([bbt, 0], [amount, 0])); 159 | } 160 | 161 | export function calcLtvRatio(totalBorrowBalanceValue: Dnum, totalCollateralBalanceValue: Dnum): Dnum { 162 | const [, decimals] = totalBorrowBalanceValue; 163 | if (dn.equal(totalCollateralBalanceValue, 0)) return dn.from(0, decimals); 164 | return dn.div(totalBorrowBalanceValue, totalCollateralBalanceValue, { 165 | rounding: "ROUND_UP", 166 | }); 167 | } 168 | 169 | export function calcBorrowUtilisationRatio( 170 | totalEffectiveBorrowBalanceValue: Dnum, 171 | totalEffectiveCollateralBalanceValue: Dnum, 172 | ): Dnum { 173 | const [, decimals] = totalEffectiveBorrowBalanceValue; 174 | if (dn.equal(totalEffectiveCollateralBalanceValue, 0)) return dn.from(0, decimals); 175 | return dn.div(totalEffectiveBorrowBalanceValue, totalEffectiveCollateralBalanceValue, { rounding: "ROUND_UP" }); 176 | } 177 | 178 | export function calcLiquidationMargin( 179 | totalEffectiveBorrowBalanceValue: Dnum, 180 | totalEffectiveCollateralBalanceValue: Dnum, 181 | ): Dnum { 182 | const [, decimals] = totalEffectiveBorrowBalanceValue; 183 | if (dn.equal(totalEffectiveCollateralBalanceValue, 0)) return dn.from(0, decimals); 184 | return dn.div( 185 | dn.sub(totalEffectiveCollateralBalanceValue, totalEffectiveBorrowBalanceValue), 186 | totalEffectiveCollateralBalanceValue, 187 | { 188 | rounding: "ROUND_DOWN", 189 | }, 190 | ); 191 | } 192 | -------------------------------------------------------------------------------- /src/common/utils/gmp.ts: -------------------------------------------------------------------------------- 1 | import { CCIP_DATA, WORMHOLE_DATA } from "../constants/gmp.js"; 2 | 3 | import type { FolksChainId } from "../types/chain.js"; 4 | import type { CCIPData, WormholeData } from "../types/gmp.js"; 5 | 6 | export function getWormholeData(folksChainId: FolksChainId): WormholeData { 7 | return WORMHOLE_DATA[folksChainId]; 8 | } 9 | 10 | export function getCcipData(folksChainId: FolksChainId): CCIPData { 11 | return CCIP_DATA[folksChainId]; 12 | } 13 | -------------------------------------------------------------------------------- /src/common/utils/lending.ts: -------------------------------------------------------------------------------- 1 | import { concat, keccak256, pad, toHex } from "viem"; 2 | 3 | import { BYTES32_LENGTH, UINT16_LENGTH } from "../constants/bytes.js"; 4 | 5 | import { convertNumberToBytes } from "./bytes.js"; 6 | 7 | import type { GenericAddress } from "../types/address.js"; 8 | import type { FolksChainId } from "../types/chain.js"; 9 | import type { AccountId, LoanId, LoanName, Nonce } from "../types/lending.js"; 10 | 11 | export function isAccountId(accountId: AccountId): boolean { 12 | return accountId.length === 64 + 2; 13 | } 14 | 15 | export function buildAccountId(addr: GenericAddress, chainId: FolksChainId, nonce: Nonce): AccountId { 16 | return keccak256(concat([addr, convertNumberToBytes(chainId, UINT16_LENGTH), nonce])) as AccountId; 17 | } 18 | 19 | export function buildLoanId(accountId: AccountId, nonce: Nonce): LoanId { 20 | return keccak256(concat([accountId, nonce])) as LoanId; 21 | } 22 | 23 | export function convertStringToLoanName(name: string): LoanName { 24 | return pad(toHex(name), { size: BYTES32_LENGTH }) as LoanName; 25 | } 26 | -------------------------------------------------------------------------------- /src/common/utils/math-lib.ts: -------------------------------------------------------------------------------- 1 | import * as dn from "dnum"; 2 | 3 | import type { Dnum } from "dnum"; 4 | 5 | export const SECONDS_IN_YEAR = BigInt(365 * 24 * 60 * 60); 6 | export const HOURS_IN_YEAR = BigInt(365 * 24); 7 | 8 | export function unixTime(): number { 9 | return Math.floor(Date.now() / 1000); 10 | } 11 | 12 | export function expBySquaring(x: Dnum, n: bigint): Dnum { 13 | const [, decimals] = x; 14 | const one = dn.from(1, decimals); 15 | 16 | if (n === BigInt(0)) return one; 17 | 18 | let y = one; 19 | while (n > BigInt(1)) { 20 | if (n % BigInt(2)) { 21 | y = dn.mul(x, y, { rounding: "ROUND_DOWN" }); 22 | n = (n - BigInt(1)) / BigInt(2); 23 | } else { 24 | n = n / BigInt(2); 25 | } 26 | x = dn.mul(x, x, { rounding: "ROUND_DOWN" }); 27 | } 28 | return dn.mul(x, y, { rounding: "ROUND_DOWN" }); 29 | } 30 | 31 | function compound(rate: Dnum, period: bigint): Dnum { 32 | const [, decimals] = rate; 33 | const one = dn.from(1, decimals); 34 | return dn.sub(expBySquaring(dn.add(one, dn.div(rate, period)), period), one); 35 | } 36 | 37 | export function compoundEverySecond(rate: Dnum): Dnum { 38 | return compound(rate, SECONDS_IN_YEAR); 39 | } 40 | 41 | export function compoundEveryHour(rate: Dnum): Dnum { 42 | return compound(rate, HOURS_IN_YEAR); 43 | } 44 | 45 | export const bigIntMax = (...args: Array) => args.reduce((m, e) => (e > m ? e : m)); 46 | export const bigIntMin = (...args: Array) => args.reduce((m, e) => (e < m ? e : m)); 47 | 48 | export const increaseByPercent = (value: bigint, percent: number) => { 49 | return value + (value * BigInt(10_000 * percent)) / BigInt(10_000); 50 | }; 51 | -------------------------------------------------------------------------------- /src/common/utils/token.ts: -------------------------------------------------------------------------------- 1 | import { FOLKS_TOKEN_IDS_FROM_POOL_BY_NETWORK } from "../constants/pool.js"; 2 | import { CROSS_CHAIN_FOLKS_TOKEN_ID, FOLKS_TOKEN_IDS_BY_LOAN_TYPE } from "../constants/token.js"; 3 | 4 | import type { NetworkType } from "../types/chain.js"; 5 | import type { LoanTypeId } from "../types/lending.js"; 6 | import type { FolksTokenId } from "../types/token.js"; 7 | 8 | export function isCrossChainToken(folksTokenId: FolksTokenId): boolean { 9 | return CROSS_CHAIN_FOLKS_TOKEN_ID.includes(folksTokenId); 10 | } 11 | 12 | export function getFolksTokenIdFromPool(poolId: number, network: NetworkType): FolksTokenId { 13 | const folksTokenId = FOLKS_TOKEN_IDS_FROM_POOL_BY_NETWORK[network][poolId]; 14 | if (!folksTokenId) throw new Error(`Unknown poolId: ${poolId}`); 15 | return folksTokenId; 16 | } 17 | 18 | export function getFolksTokenIdsByLoanType(loanType: LoanTypeId, network: NetworkType): Array { 19 | return FOLKS_TOKEN_IDS_BY_LOAN_TYPE[network][loanType] ?? []; 20 | } 21 | -------------------------------------------------------------------------------- /src/common/utils/transaction.ts: -------------------------------------------------------------------------------- 1 | import { waitForTransactionReceipt } from "viem/actions"; 2 | 3 | import { exhaustiveCheck } from "../../utils/exhaustive-check.js"; 4 | import { ChainType } from "../types/chain.js"; 5 | 6 | import type { FolksProvider } from "../types/core.js"; 7 | import type { Hex, TransactionReceipt } from "viem"; 8 | 9 | export async function waitTransaction( 10 | chainType: ChainType, 11 | folksProvider: FolksProvider, 12 | txnHash: Hex, 13 | confirmations = 1, 14 | ): Promise { 15 | switch (chainType) { 16 | case ChainType.EVM: { 17 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 18 | const receipt = await waitForTransactionReceipt(folksProvider!, { 19 | hash: txnHash, 20 | confirmations, 21 | }); 22 | return receipt; 23 | } 24 | default: 25 | return exhaustiveCheck(chainType); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // === CORE === 2 | export { FolksCore } from "./xchain/core/folks-core.js"; 3 | 4 | // === MODULES === 5 | export { 6 | FolksAccount, 7 | FolksLoan, 8 | FolksOracle, 9 | FolksPool, 10 | FolksGmp, 11 | FolksRewardsV1, 12 | FolksRewardsV2, 13 | } from "./xchain/modules/index.js"; 14 | 15 | // === COMMON === 16 | export * from "./common/types/adapter.js"; 17 | export * from "./common/types/address.js"; 18 | export * from "./common/types/chain.js"; 19 | export * from "./common/types/core.js"; 20 | export * from "./common/types/gmp.js"; 21 | export * from "./common/types/lending.js"; 22 | export * from "./common/types/message.js"; 23 | export * from "./common/types/module.js"; 24 | export * from "./common/types/rewards.js"; 25 | export * from "./common/types/rewards-v2.js"; 26 | export * from "./common/types/token.js"; 27 | 28 | export * from "./common/constants/bytes.js"; 29 | export * from "./common/constants/chain.js"; 30 | export * from "./common/constants/gmp.js"; 31 | export * from "./common/constants/message.js"; 32 | export * from "./common/constants/pool.js"; 33 | export * from "./common/constants/reward.js"; 34 | 35 | export { getSupportedMessageAdapters } from "./common/utils/adapter.js"; 36 | export { convertFromGenericAddress, convertToGenericAddress } from "./common/utils/address.js"; 37 | export { getRandomBytes } from "./common/utils/bytes.js"; 38 | export { getAdapterAddress, getRewardTokenSpokeChain } from "./common/utils/chain.js"; 39 | export { toFAmount, toUnderlyingAmount, calcAssetDollarValue } from "./common/utils/formulae.js"; 40 | export { getCcipData, getWormholeData } from "./common/utils/gmp.js"; 41 | export { buildAccountId, buildLoanId } from "./common/utils/lending.js"; 42 | export { getOperationIdsByTransaction, waitOperationIds } from "./common/utils/messages.js"; 43 | export { isCrossChainToken, getFolksTokenIdsByLoanType } from "./common/utils/token.js"; 44 | export { waitTransaction } from "./common/utils/transaction.js"; 45 | 46 | // === HUB === 47 | export { isHubChain } from "./chains/evm/hub/utils/chain.js"; 48 | export { HUB_CHAIN } from "./chains/evm/hub/constants/chain.js"; 49 | 50 | // === CHAINS === 51 | 52 | // - EVM 53 | export * from "./chains/evm/common/constants/chain.js"; 54 | 55 | export * from "./chains/evm/common/types/chain.js"; 56 | 57 | export { isEvmChainId } from "./chains/evm/common/utils/chain.js"; 58 | 59 | // - EVM: HUB 60 | export * from "./chains/evm/hub/types/account.js"; 61 | export * from "./chains/evm/hub/types/chain.js"; 62 | export * from "./chains/evm/hub/types/loan.js"; 63 | export * from "./chains/evm/hub/types/oracle.js"; 64 | export * from "./chains/evm/hub/types/pool.js"; 65 | export * as rewardsV1 from "./chains/evm/hub/types/rewards-v1.js"; 66 | export * from "./chains/evm/hub/types/rewards-v2.js"; 67 | export * from "./chains/evm/hub/types/token.js"; 68 | 69 | // - EVM: SPOKE 70 | export * from "./chains/evm/spoke/types/pool.js"; 71 | -------------------------------------------------------------------------------- /src/types/generics.ts: -------------------------------------------------------------------------------- 1 | export type NonEmptyArray = [T, ...Array]; 2 | -------------------------------------------------------------------------------- /src/utils/array.ts: -------------------------------------------------------------------------------- 1 | import type { NonEmptyArray } from "../types/generics.js"; 2 | 3 | export const intersect = 4 | (arr: Array) => 5 | (x: T) => 6 | arr.includes(x); 7 | 8 | export const ensureNonEmpty = (arr: Array, errMsg?: string): NonEmptyArray => { 9 | if (arr.length === 0) throw new Error(errMsg ?? "Array cannot be empty"); 10 | return arr as NonEmptyArray; 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/exhaustive-check.ts: -------------------------------------------------------------------------------- 1 | export const exhaustiveCheck = (value: never): never => { 2 | throw new Error(`ERROR! Reached forbidden guard function with unexpected value: ${JSON.stringify(value)}`); 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/random.ts: -------------------------------------------------------------------------------- 1 | export function getRandomElement(arr: ReadonlyArray): T { 2 | return arr[Math.floor(Math.random() * arr.length)]; 3 | } 4 | -------------------------------------------------------------------------------- /src/xchain/core/folks-core.ts: -------------------------------------------------------------------------------- 1 | import { initProviders } from "../../chains/evm/common/utils/provider.js"; 2 | import { getHubChain } from "../../chains/evm/hub/utils/chain.js"; 3 | import { ChainType } from "../../common/types/chain.js"; 4 | import { getFolksChain } from "../../common/utils/chain.js"; 5 | import { exhaustiveCheck } from "../../utils/exhaustive-check.js"; 6 | 7 | import type { FolksChainId, FolksChain, NetworkType } from "../../common/types/chain.js"; 8 | import type { 9 | FolksCoreProvider, 10 | FolksSigner, 11 | FolksCoreConfig, 12 | FolksProviderType, 13 | FolksSignerType, 14 | FolksProvider, 15 | } from "../../common/types/core.js"; 16 | import type { Client as EVMProvider } from "viem"; 17 | 18 | export class FolksCore { 19 | private static instance: FolksCore | undefined; 20 | private folksCoreProvider: FolksCoreProvider; 21 | 22 | private selectedNetwork: NetworkType; 23 | private folksSigner?: FolksSigner; 24 | 25 | private constructor(folksCoreConfig: FolksCoreConfig) { 26 | this.selectedNetwork = folksCoreConfig.network; 27 | this.folksCoreProvider = { evm: {} as Record }; 28 | this.folksCoreProvider.evm = initProviders(folksCoreConfig.provider.evm); 29 | } 30 | 31 | static init(folksCoreConfig: FolksCoreConfig): FolksCore { 32 | if (FolksCore.instance) throw new Error("FolksCore is already initialized"); 33 | FolksCore.instance = new FolksCore(folksCoreConfig); 34 | return FolksCore.instance; 35 | } 36 | 37 | static isInitialized(): boolean { 38 | return !!FolksCore.instance; 39 | } 40 | 41 | static getInstance(): FolksCore { 42 | if (FolksCore.instance) return FolksCore.instance; 43 | throw new Error("FolksCore is not initialized"); 44 | } 45 | 46 | static getProvider(folksChainId: FolksChainId): FolksProviderType { 47 | const folksChain = getFolksChain(folksChainId, this.getSelectedNetwork()); 48 | switch (folksChain.chainType) { 49 | case ChainType.EVM: 50 | return FolksCore.getEVMProvider(folksChainId) as FolksProviderType; 51 | default: 52 | return exhaustiveCheck(folksChain.chainType); 53 | } 54 | } 55 | 56 | static getFolksSigner() { 57 | const instance = this.getInstance(); 58 | if (!instance.folksSigner) throw new Error("FolksSigner is not initialized"); 59 | 60 | return instance.folksSigner; 61 | } 62 | 63 | static getSigner(): FolksSignerType { 64 | const { signer } = this.getFolksSigner(); 65 | 66 | return signer as FolksSignerType; 67 | } 68 | 69 | static getSelectedFolksChain(): FolksChain { 70 | const { folksChainId } = this.getFolksSigner(); 71 | 72 | return getFolksChain(folksChainId, this.getSelectedNetwork()); 73 | } 74 | 75 | static getSelectedNetwork() { 76 | const instance = this.getInstance(); 77 | return instance.selectedNetwork; 78 | } 79 | 80 | static getHubProvider(): EVMProvider { 81 | const instance = this.getInstance(); 82 | const hubFolksChainId = getHubChain(instance.selectedNetwork).folksChainId; 83 | 84 | const hubProvider = instance.folksCoreProvider.evm[hubFolksChainId]; 85 | if (!hubProvider) throw new Error(`Hub Provider has not been initialized`); 86 | 87 | return hubProvider; 88 | } 89 | 90 | static setProvider(folksChainId: FolksChainId, provider: FolksProvider) { 91 | const instance = this.getInstance(); 92 | const folksChain = getFolksChain(folksChainId, this.getSelectedNetwork()); 93 | switch (folksChain.chainType) { 94 | case ChainType.EVM: 95 | // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style -- in the future FolksProvider will contain more than just EVMProvider 96 | instance.folksCoreProvider.evm[folksChainId] = provider as EVMProvider; 97 | break; 98 | default: 99 | return exhaustiveCheck(folksChain.chainType); 100 | } 101 | } 102 | 103 | static setNetwork(network: NetworkType) { 104 | const instance = this.getInstance(); 105 | instance.selectedNetwork = network; 106 | } 107 | 108 | static setFolksSigner(folksSigner: FolksSigner) { 109 | const instance = this.getInstance(); 110 | const folksChain = getFolksChain(folksSigner.folksChainId, this.getSelectedNetwork()); 111 | 112 | switch (folksChain.chainType) { 113 | case ChainType.EVM: 114 | instance.folksSigner = folksSigner; 115 | break; 116 | default: 117 | return exhaustiveCheck(folksChain.chainType); 118 | } 119 | } 120 | 121 | static getEVMProvider(folksChainId: FolksChainId): EVMProvider { 122 | const instance = this.getInstance(); 123 | const evmProvider = instance.folksCoreProvider.evm[folksChainId]; 124 | if (!evmProvider) throw new Error(`EVM Provider not found for folksChainId: ${folksChainId}`); 125 | 126 | return evmProvider; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/xchain/modules/folks-oracle.ts: -------------------------------------------------------------------------------- 1 | import { FolksHubOracle } from "../../chains/evm/hub/modules/index.js"; 2 | import { getHubTokenData, getHubTokensData, getHubRewardsV2TokensData } from "../../chains/evm/hub/utils/chain.js"; 3 | import { FolksCore } from "../core/folks-core.js"; 4 | 5 | import type { 6 | OraclePrice, 7 | OraclePrices, 8 | OracleNodePrice, 9 | OracleNodePrices, 10 | OracleNode, 11 | } from "../../chains/evm/hub/types/oracle.js"; 12 | import type { FolksTokenId } from "../../common/types/token.js"; 13 | 14 | export const read = { 15 | async oraclePrice(folksTokenId: FolksTokenId, blockNumber?: bigint): Promise { 16 | const network = FolksCore.getSelectedNetwork(); 17 | const { poolId } = getHubTokenData(folksTokenId, network); 18 | return await FolksHubOracle.getOraclePrice(FolksCore.getHubProvider(), network, poolId, blockNumber); 19 | }, 20 | 21 | async oraclePrices(folksTokenIds?: Array, blockNumber?: bigint): Promise { 22 | const network = FolksCore.getSelectedNetwork(); 23 | const tokensData = folksTokenIds 24 | ? folksTokenIds.map((folksTokenId) => getHubTokenData(folksTokenId, network)) 25 | : Object.values(getHubTokensData(network)); 26 | return FolksHubOracle.getOraclePrices(FolksCore.getHubProvider(), network, tokensData, blockNumber); 27 | }, 28 | 29 | async oracleNodePrice(oracleNode: OracleNode): Promise { 30 | const network = FolksCore.getSelectedNetwork(); 31 | return await FolksHubOracle.getNodePrice(FolksCore.getHubProvider(), network, oracleNode); 32 | }, 33 | 34 | async oracleNodePrices(oracleNodes?: Array): Promise { 35 | const network = FolksCore.getSelectedNetwork(); 36 | const nodes = 37 | oracleNodes ?? 38 | Object.values(getHubRewardsV2TokensData(network)).map(({ nodeId, token }) => ({ 39 | nodeId, 40 | decimals: token.decimals, 41 | })); 42 | return await FolksHubOracle.getNodePrices(FolksCore.getHubProvider(), network, nodes); 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /src/xchain/modules/folks-pool.ts: -------------------------------------------------------------------------------- 1 | import { FolksHubPool } from "../../chains/evm/hub/modules/index.js"; 2 | import { FolksCore } from "../core/folks-core.js"; 3 | 4 | import type { PoolInfo } from "../../chains/evm/hub/types/pool.js"; 5 | import type { FolksTokenId } from "../../common/types/token.js"; 6 | 7 | export const read = { 8 | async poolInfo(folksTokenId: FolksTokenId, blockNumber?: bigint): Promise { 9 | return FolksHubPool.getPoolInfo( 10 | FolksCore.getHubProvider(), 11 | FolksCore.getSelectedNetwork(), 12 | folksTokenId, 13 | blockNumber, 14 | ); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/xchain/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * as FolksAccount from "./folks-account.js"; 2 | export * as FolksLoan from "./folks-loan.js"; 3 | export * as FolksOracle from "./folks-oracle.js"; 4 | export * as FolksPool from "./folks-pool.js"; 5 | export * as FolksRewardsV1 from "./folks-rewards-v1.js"; 6 | export * as FolksRewardsV2 from "./folks-rewards-v2.js"; 7 | export * as FolksGmp from "./folks-gmp.js"; 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": true, 5 | "declaration": true, 6 | "outDir": "dist" 7 | }, 8 | "exclude": ["examples/**/*"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "skipLibCheck": true, 5 | "target": "es2022", 6 | "resolveJsonModule": true, 7 | "moduleDetection": "force", 8 | "isolatedModules": true, 9 | "verbatimModuleSyntax": true, 10 | "strict": true, 11 | "module": "NodeNext", 12 | "forceConsistentCasingInFileNames": true 13 | }, 14 | "include": ["src/**/*", "examples/**/*"], 15 | "exclude": ["node_modules"] 16 | } 17 | --------------------------------------------------------------------------------