├── .changeset ├── README.md └── config.json ├── .github ├── actions │ └── setup │ │ └── action.yml └── workflows │ ├── gen-docs.yml │ ├── lint-and-typecheck.yml │ ├── publish-preview.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .prettierignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── eslint.config.mjs ├── examples ├── config.ts ├── lend │ ├── complex-flash-loan.ts │ ├── liquidate.ts │ └── simple-flash-loan.ts └── xalgo │ └── stake.ts ├── media └── repo-header.jpg ├── package.json ├── pnpm-lock.yaml ├── prettier.config.mjs ├── src ├── algo-liquid-governance │ ├── common │ │ ├── governance.ts │ │ ├── index.ts │ │ ├── mainnet-constants.ts │ │ └── types.ts │ ├── v1 │ │ ├── constants │ │ │ ├── abi-contracts.ts │ │ │ └── mainnet-constants.ts │ │ ├── governance.ts │ │ ├── index.ts │ │ └── types.ts │ └── v2 │ │ ├── constants │ │ ├── abi-contracts.ts │ │ └── mainnet-constants.ts │ │ ├── governance.ts │ │ ├── index.ts │ │ └── types.ts ├── index.ts ├── lend │ ├── abi-contracts │ │ ├── deposit-staking.json │ │ ├── deposits.json │ │ ├── index.ts │ │ ├── loan.json │ │ ├── lp-token-oracle.json │ │ ├── oracle-adapter.json │ │ └── pool.json │ ├── amm.ts │ ├── constants │ │ ├── mainnet-constants.ts │ │ └── testnet-constants.ts │ ├── deposit-staking.ts │ ├── deposit.ts │ ├── formulae.ts │ ├── index.ts │ ├── loan.ts │ ├── opup.ts │ ├── oracle.ts │ ├── types.ts │ └── utils.ts ├── math-lib.ts ├── utils.ts └── xalgo │ ├── abi-contracts │ ├── index.ts │ ├── stake_and_deposit.json │ └── xalgo.json │ ├── consensus.ts │ ├── constants │ ├── mainnet-constants.ts │ └── testnet-constants.ts │ ├── formulae.ts │ ├── index.ts │ └── types.ts ├── tsconfig.json └── typedoc.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.4/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "Folks-Finance/algorand-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/gen-docs.yml: -------------------------------------------------------------------------------- 1 | name: Generate docs and deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | concurrency: 15 | group: "pages" 16 | cancel-in-progress: false 17 | 18 | jobs: 19 | deploy: 20 | environment: 21 | name: github-pages 22 | url: ${{ steps.deployment.outputs.page_url }} 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | 28 | - name: Setup 29 | uses: ./.github/actions/setup 30 | 31 | - name: Generate documentation 32 | run: pnpm run gen-docs 33 | 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v5 36 | 37 | - name: Upload artifact 38 | uses: actions/upload-pages-artifact@v3 39 | with: 40 | path: "docs" 41 | 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 45 | -------------------------------------------------------------------------------- /.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 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 | /docs 7 | 8 | # debug 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | .pnpm-debug.log* 13 | 14 | # typescript 15 | *.tsbuildinfo 16 | 17 | # eslint 18 | .eslintcache 19 | 20 | # macos 21 | .DS_Store 22 | 23 | # local env files 24 | .env 25 | .env*.local 26 | 27 | # intellij 28 | .idea 29 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm exec lint-staged 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.12.0 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.mjs", 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.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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @folks-finance/algorand-sdk 2 | 3 | ## 0.1.6 4 | 5 | ### Patch Changes 6 | 7 | - [#84](https://github.com/Folks-Finance/algorand-js-sdk/pull/84) [`f515b0b`](https://github.com/Folks-Finance/algorand-js-sdk/commit/f515b0bac216e43020ac1e7e44eee7717f04ea23) Thanks [@gidonkatten](https://github.com/gidonkatten)! - add ability to close out governance escrow 8 | 9 | ## 0.1.5 10 | 11 | ### Patch Changes 12 | 13 | - [#81](https://github.com/Folks-Finance/algorand-js-sdk/pull/81) [`e23f100`](https://github.com/Folks-Finance/algorand-js-sdk/commit/e23f100103f38d3fb78be44fd345fad92f90cf03) Thanks [@gidonkatten](https://github.com/gidonkatten)! - support for staking apr in lending pool info 14 | 15 | ## 0.1.4 16 | 17 | ### Patch Changes 18 | 19 | - [#78](https://github.com/Folks-Finance/algorand-js-sdk/pull/78) [`113c7cc`](https://github.com/Folks-Finance/algorand-js-sdk/commit/113c7cc2b407cfa8bbd7484dea4897fc41e2472a) Thanks [@gidonkatten](https://github.com/gidonkatten)! - transaction to claim x algo fees 20 | 21 | ## 0.1.3 22 | 23 | ### Patch Changes 24 | 25 | - [#76](https://github.com/Folks-Finance/algorand-js-sdk/pull/76) [`03313b7`](https://github.com/Folks-Finance/algorand-js-sdk/commit/03313b7237c32f366a7a1fd2a177d6ecdefab400) Thanks [@gidonkatten](https://github.com/gidonkatten)! - option to include additional interest in net rate and yield calculations 26 | 27 | ## 0.1.2 28 | 29 | ### Patch Changes 30 | 31 | - [#74](https://github.com/Folks-Finance/algorand-js-sdk/pull/74) [`dd291d6`](https://github.com/Folks-Finance/algorand-js-sdk/commit/dd291d6754ac0ec2d8c37d041e4a0dc3e4a5b0e3) Thanks [@gidonkatten](https://github.com/gidonkatten)! - fix unexpected register fee amount 32 | 33 | ## 0.1.1 34 | 35 | ### Patch Changes 36 | 37 | - [#72](https://github.com/Folks-Finance/algorand-js-sdk/pull/72) [`a724ede`](https://github.com/Folks-Finance/algorand-js-sdk/commit/a724edee55b4d9337ff7713b1b2a9dfc0b584543) Thanks [@gidonkatten](https://github.com/gidonkatten)! - new xalgo usdc lending pools 38 | 39 | ## 0.1.0 40 | 41 | ### Minor Changes 42 | 43 | - [#56](https://github.com/Folks-Finance/algorand-js-sdk/pull/56) [`592c0fa`](https://github.com/Folks-Finance/algorand-js-sdk/commit/592c0faa187cdd4542c8a26bae0a3310207ca8e1) Thanks [@gidonkatten](https://github.com/gidonkatten)! - updated transactions for new xalgo version 44 | 45 | ### Patch Changes 46 | 47 | - [#65](https://github.com/Folks-Finance/algorand-js-sdk/pull/65) [`1829e69`](https://github.com/Folks-Finance/algorand-js-sdk/commit/1829e696f76cf5ec3ae46ff884788435f7dddb36) Thanks [@gidonkatten](https://github.com/gidonkatten)! - add xalgo pool 48 | 49 | - [#56](https://github.com/Folks-Finance/algorand-js-sdk/pull/56) [`592c0fa`](https://github.com/Folks-Finance/algorand-js-sdk/commit/592c0faa187cdd4542c8a26bae0a3310207ca8e1) Thanks [@gidonkatten](https://github.com/gidonkatten)! - stake and deposit util 50 | 51 | ## 0.0.7 52 | 53 | ### Patch Changes 54 | 55 | - [#69](https://github.com/Folks-Finance/algorand-js-sdk/pull/69) [`30119c7`](https://github.com/Folks-Finance/algorand-js-sdk/commit/30119c7857855fcfcacb52e2afa36c8bc85af442) Thanks [@gidonkatten](https://github.com/gidonkatten)! - return group transaction for register online 56 | 57 | ## 0.0.6 58 | 59 | ### Patch Changes 60 | 61 | - [#67](https://github.com/Folks-Finance/algorand-js-sdk/pull/67) [`889fc32`](https://github.com/Folks-Finance/algorand-js-sdk/commit/889fc3231d327ed851607c1afed355b5caea54ac) Thanks [@gidonkatten](https://github.com/gidonkatten)! - fix register online abi for new distributor 62 | 63 | ## 0.0.5 64 | 65 | ### Patch Changes 66 | 67 | - [#64](https://github.com/Folks-Finance/algorand-js-sdk/pull/64) [`2f0e705`](https://github.com/Folks-Finance/algorand-js-sdk/commit/2f0e705f71e74eb1ba983d6059ae7e94459b464c) Thanks [@gidonkatten](https://github.com/gidonkatten)! - new galgo distributor 68 | 69 | ## 0.0.4 70 | 71 | ### Patch Changes 72 | 73 | - [#61](https://github.com/Folks-Finance/algorand-js-sdk/pull/61) [`c149f70`](https://github.com/Folks-Finance/algorand-js-sdk/commit/c149f700b352631ac2c6ecf693a747189607afed) Thanks [@gidonkatten](https://github.com/gidonkatten)! - fix testnet xalgo pool app id 74 | 75 | ## 0.0.3 76 | 77 | ### Patch Changes 78 | 79 | - [#59](https://github.com/Folks-Finance/algorand-js-sdk/pull/59) [`b094b00`](https://github.com/Folks-Finance/algorand-js-sdk/commit/b094b00dd429fd06aba4bd1055adc8b4e811005b) Thanks [@gidonkatten](https://github.com/gidonkatten)! - update testnet oracle and add xalgo pool to testnet 80 | 81 | ## 0.0.2 82 | 83 | ### Patch Changes 84 | 85 | - [#57](https://github.com/Folks-Finance/algorand-js-sdk/pull/57) [`87a3cf0`](https://github.com/Folks-Finance/algorand-js-sdk/commit/87a3cf0baad5e96bb40ac374b2b2a2efd461e095) Thanks [@stefanofa](https://github.com/stefanofa)! - Fixed unstake transaction bug by initializing arrays with BigInt(0)s to prevent undefined elements during iteration. 86 | 87 | ## 0.0.1 88 | 89 | ### Patch Changes 90 | 91 | - [#53](https://github.com/Folks-Finance/algorand-js-sdk/pull/53) [`b21e6dd`](https://github.com/Folks-Finance/algorand-js-sdk/commit/b21e6ddf0c2d6a8ab3a4e5779287a40369c5e792) Thanks [@stefanofa](https://github.com/stefanofa)! - Setup eslint 92 | 93 | - [#51](https://github.com/Folks-Finance/algorand-js-sdk/pull/51) [`72f5eb7`](https://github.com/Folks-Finance/algorand-js-sdk/commit/72f5eb7407afb402c0ae71afcd89d34ddde1d024) Thanks [@stefanofa](https://github.com/stefanofa)! - Migrate package to @folks-finance/algorand-sdk 94 | 95 | - [#54](https://github.com/Folks-Finance/algorand-js-sdk/pull/54) [`7b0f156`](https://github.com/Folks-Finance/algorand-js-sdk/commit/7b0f15666902b313af55b19df7f5215bd99cd059) Thanks [@stefanofa](https://github.com/stefanofa)! - Generate docs using github actions 96 | 97 | - [`2684d99`](https://github.com/Folks-Finance/algorand-js-sdk/commit/2684d995bba22afc81f358f928b66f469cadc9fe) Thanks [@stefanofa](https://github.com/stefanofa)! - Remove code related to v1 protocol 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Folks Finance 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @folks-finance/algorand-sdk 2 | 3 | [![License: MIT][license-image]][license-url] 4 | [![CI][ci-image]][ci-url] 5 | [![NPM version][npm-image]][npm-url] 6 | [![Downloads][downloads-image]][npm-url] 7 | 8 | ![Algorand Header](https://github.com/Folks-Finance/algorand-js-sdk/raw/main/media/repo-header.jpg) 9 | 10 | The official JavaScript SDK for the Algorand Folks Finance protocol 11 | 12 | ## Installation 13 | 14 | ### Package manager 15 | 16 | Using npm: 17 | 18 | ```bash 19 | npm install @folks-finance/algorand-sdk 20 | ``` 21 | 22 | Using yarn: 23 | 24 | ```bash 25 | yarn add @folks-finance/algorand-sdk 26 | ``` 27 | 28 | Using pnpm: 29 | 30 | ```bash 31 | pnpm add @folks-finance/algorand-sdk 32 | ``` 33 | 34 | Using bun: 35 | 36 | ```bash 37 | bun add @folks-finance/algorand-sdk 38 | ``` 39 | 40 | No extra setup is required if you're just using the sdk as a dependency in your project. 41 | 42 | ## Documentation 43 | 44 | Documentation is generated with TypeDoc and available at . 45 | 46 | ## Running Examples (For Contributors) 47 | 48 | If you've forked or cloned this repository, you can run the included examples: 49 | 50 | 1. Update `examples/config.ts` with your configuration. 51 | 2. Run an example, for example: 52 | 53 | ```bash 54 | pnpm example examples/v2/liquidate.ts 55 | ``` 56 | 57 | [license-image]: https://img.shields.io/badge/License-MIT-brightgreen.svg?style=flat-square 58 | [license-url]: https://opensource.org/licenses/MIT 59 | [ci-image]: https://img.shields.io/github/actions/workflow/status/Folks-Finance/algorand-js-sdk/lint-and-typecheck.yml?branch=main&logo=github&style=flat-square 60 | [ci-url]: https://github.com/Folks-Finance/algorand-js-sdk/actions/workflows/lint-and-typecheck.yml 61 | [npm-image]: https://img.shields.io/npm/v/@folks-finance/algorand-sdk.svg?style=flat-square 62 | [npm-url]: https://www.npmjs.com/package/@folks-finance/algorand-sdk 63 | [downloads-image]: https://img.shields.io/npm/dm/@folks-finance/algorand-sdk.svg?style=flat-square 64 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js"; 2 | import tsParser from "@typescript-eslint/parser"; 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 | // eslint-disable-next-line import-x/no-default-export 10 | export default tseslint.config( 11 | { ignores: ["dist/", "docs/", "examples/"] }, 12 | { files: ["**/*.{js,mjs,cjs,ts}"] }, 13 | { 14 | languageOptions: { 15 | parser: tsParser, 16 | parserOptions: { 17 | project: true, 18 | tsconfigRootDir: import.meta.dirname, 19 | }, 20 | globals: { ...globals.browser, ...globals.node }, 21 | }, 22 | }, 23 | eslint.configs.recommended, 24 | tseslint.configs.recommended, 25 | { 26 | plugins: { 27 | "import-x": eslintPluginImportX, 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/consistent-type-imports": ["error", { prefer: "type-imports" }], 46 | "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], 47 | "@typescript-eslint/restrict-template-expressions": ["error", { allowNumber: true }], 48 | "@typescript-eslint/switch-exhaustiveness-check": "error", 49 | "unicorn/better-regex": "error", 50 | "unicorn/consistent-function-scoping": "error", 51 | "unicorn/expiring-todo-comments": "error", 52 | "unicorn/filename-case": ["error", { case: "kebabCase" }], 53 | "unicorn/no-array-for-each": "error", 54 | "unicorn/no-for-loop": "error", 55 | }, 56 | }, 57 | { 58 | files: ["**/*?(.c|.m)js"], 59 | ...tseslint.configs.disableTypeChecked, 60 | }, 61 | { 62 | settings: { 63 | "import-x/parsers": { 64 | "@typescript-eslint/parser": [".ts", ".tsx"], 65 | }, 66 | "import-x/resolver": { 67 | typescript: true, 68 | node: true, 69 | }, 70 | }, 71 | }, 72 | { linterOptions: { reportUnusedDisableDirectives: true } }, 73 | eslintConfigPrettier, 74 | ); 75 | -------------------------------------------------------------------------------- /examples/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Algodv2, 3 | generateAccount, 4 | Indexer, 5 | //mnemonicToSecretKey 6 | } from "algosdk"; 7 | 8 | // TODO: Replace 9 | // export const sender = mnemonicToSecretKey(""); 10 | export const sender = generateAccount(); 11 | 12 | export const algodClient = new Algodv2("", "https://testnet-api.algonode.cloud/", 443); 13 | export const indexerClient = new Indexer("", "https://testnet-idx.algonode.cloud/", 443); 14 | -------------------------------------------------------------------------------- /examples/lend/complex-flash-loan.ts: -------------------------------------------------------------------------------- 1 | import { assignGroupID } from "algosdk"; 2 | 3 | import { 4 | calcFlashLoanRepayment, 5 | prepareFlashLoanBegin, 6 | prepareFlashLoanEnd, 7 | TestnetPools, 8 | TestnetReserveAddress, 9 | } from "../../src"; 10 | import { algodClient, sender } from "../config"; 11 | 12 | import type { Transaction } from "algosdk"; 13 | 14 | async function main() { 15 | const reserveAddress = TestnetReserveAddress; 16 | const pools = TestnetPools; 17 | 18 | // can put arbitrary txns inside flash loan 19 | const insideTxns: Transaction[] = []; 20 | 21 | // retrieve params 22 | const params = await algodClient.getTransactionParams().do(); 23 | 24 | // there are two ways you can use flash loans 25 | // will show here the complex version where have complete flexibility and 26 | // can do multiple flash loans in single group transaction 27 | 28 | // final result will be: 29 | // - flash loan borrow of 2 ALGO 30 | // - flash loan borrow of 1 USDC 31 | // - 32 | // - flash loan repay of 2.002001 ALGO (0.1% fee + 0.000001) 33 | // - flash loan repay of 1.002001 USDC (0.1% fee + 0.000001) 34 | 35 | // flash loan of 2 ALGO, repayment will be 2.002 ALGO (0.1% + 1) 36 | const algoBorrowAmount = 2e6; 37 | const algoRepaymentAmount = calcFlashLoanRepayment(BigInt(algoBorrowAmount), BigInt(0.001e16)); 38 | const algoTxnIndexForFlashLoanEnd = insideTxns.length + 3; 39 | const algoFlashLoanBegin = prepareFlashLoanBegin( 40 | pools.ALGO, 41 | sender.addr, 42 | sender.addr, 43 | algoBorrowAmount, 44 | algoTxnIndexForFlashLoanEnd, 45 | params, 46 | ); 47 | const algoFlashLoanEnd = prepareFlashLoanEnd(pools.ALGO, sender.addr, reserveAddress, algoRepaymentAmount, params); 48 | 49 | // flash loan of 1 USDC, repayment will be 1.001 USDC (0.1% + 1) 50 | const usdcBorrowAmount = 1e6; 51 | const usdcRepaymentAmount = calcFlashLoanRepayment(BigInt(usdcBorrowAmount), BigInt(0.001e16)); 52 | const usdcTxnIndexForFlashLoanEnd = insideTxns.length + 5; 53 | const usdcFlashLoanBegin = prepareFlashLoanBegin( 54 | pools.USDC, 55 | sender.addr, 56 | sender.addr, 57 | usdcBorrowAmount, 58 | usdcTxnIndexForFlashLoanEnd, 59 | params, 60 | ); 61 | const usdcFlashLoanEnd = prepareFlashLoanEnd(pools.USDC, sender.addr, reserveAddress, usdcRepaymentAmount, params); 62 | 63 | // build 64 | const flashLoanTxns: Transaction[] = [ 65 | algoFlashLoanBegin, 66 | usdcFlashLoanBegin, 67 | ...insideTxns, 68 | ...algoFlashLoanEnd, 69 | ...usdcFlashLoanEnd, 70 | ]; 71 | 72 | // group, sign and submit 73 | assignGroupID(flashLoanTxns); 74 | const signedTxns = flashLoanTxns.map((txn) => txn.signTxn(sender.sk)); 75 | await algodClient.sendRawTransaction(signedTxns).do(); 76 | } 77 | 78 | main().catch(console.error); 79 | -------------------------------------------------------------------------------- /examples/lend/liquidate.ts: -------------------------------------------------------------------------------- 1 | import { assignGroupID, waitForConfirmation } from "algosdk"; 2 | 3 | import { 4 | getOraclePrices, 5 | prefixWithOpUp, 6 | prepareLiquidateLoan, 7 | retrieveLiquidatableLoans, 8 | retrieveLoanInfo, 9 | retrievePoolManagerInfo, 10 | TestnetLoans, 11 | TestnetOpUp, 12 | TestnetOracle, 13 | TestnetPoolManagerAppId, 14 | TestnetPools, 15 | TestnetReserveAddress 16 | } from "../../src"; 17 | import { algodClient, indexerClient, sender } from "../config"; 18 | 19 | import type { 20 | LPToken, 21 | LPTokenPool, 22 | Pool, 23 | UserLoanInfo} from "../../src"; 24 | 25 | function sleep(ms: number) { 26 | return new Promise((resolve) => setTimeout(resolve, ms)); 27 | } 28 | 29 | const isLPTokenPool = (pool: Pool): pool is LPTokenPool => { 30 | return "lpToken" in pool; 31 | }; 32 | 33 | const getAssetFromAppId = (pools: Record, appId: number): LPToken | number => { 34 | const [, pool] = Object.entries(pools).find(([_, pool]) => pool.appId === appId)!; 35 | return isLPTokenPool(pool) ? (pool as LPTokenPool).lpToken : pool.assetId; 36 | }; 37 | 38 | export const getUserLoanAssets = (pools: Record, userLoan: UserLoanInfo) => { 39 | const lpAssets: LPToken[] = []; 40 | const baseAssetIds: number[] = []; 41 | const loanPoolAppIds = new Set(); // use set to remove duplicates (assets which are both collateral and borrow) 42 | 43 | for (const { poolAppId } of userLoan.collaterals) loanPoolAppIds.add(poolAppId); 44 | for (const { poolAppId } of userLoan.borrows) loanPoolAppIds.add(poolAppId); 45 | 46 | // add to lp assets and base assets 47 | for (const poolAppId of loanPoolAppIds) { 48 | const asset = getAssetFromAppId(pools, poolAppId); 49 | if (Number.isNaN(asset)) { 50 | lpAssets.push(asset as LPToken); 51 | } else { 52 | baseAssetIds.push(asset as number); 53 | } 54 | } 55 | 56 | return { lpAssets, baseAssetIds }; 57 | }; 58 | 59 | async function main() { 60 | const poolManagerAppId = TestnetPoolManagerAppId; 61 | const loanAppId = TestnetLoans.GENERAL!; 62 | const oracle = TestnetOracle; 63 | const pools = TestnetPools; 64 | const reserveAddress = TestnetReserveAddress; 65 | const opup = TestnetOpUp; 66 | 67 | // get pool manager info, loan info and oracle prices 68 | const poolManagerInfo = await retrievePoolManagerInfo(indexerClient, poolManagerAppId); 69 | const loanInfo = await retrieveLoanInfo(indexerClient, loanAppId); 70 | const oraclePrices = await getOraclePrices(indexerClient, oracle); 71 | 72 | // retrieve params 73 | const params = await algodClient.getTransactionParams().do(); 74 | 75 | // loop through loans and liquidate them if possible 76 | let nextToken = undefined; 77 | do { 78 | // sleep for 0.1 seconds to prevent hitting request limit 79 | await sleep(100); 80 | 81 | // find liquidatable loans 82 | const liquidatableLoans: { loans: UserLoanInfo[]; nextToken?: string } = await retrieveLiquidatableLoans( 83 | indexerClient, 84 | loanAppId, 85 | poolManagerInfo, 86 | loanInfo, 87 | oraclePrices, 88 | nextToken, 89 | ); 90 | nextToken = liquidatableLoans.nextToken; 91 | 92 | // liquidate 93 | for (const loan of liquidatableLoans.loans) { 94 | // decide on which collateral to seize 95 | const [, collateralPool] = Object.entries(pools).find( 96 | ([_, pool]) => pool.appId === loan.collaterals[0].poolAppId, 97 | )!; 98 | 99 | // decide on which borrow to repay 100 | const [, borrowPool] = Object.entries(pools).find(([_, pool]) => pool.appId === loan.borrows[0].poolAppId)!; 101 | 102 | // decide on how much to repay 103 | const repayAmount = loan.borrows[0].borrowBalance / BigInt(2); 104 | 105 | // decide on minimum collateral willing to accept 106 | // TODO: MUST SET IF RUNNING ACTUAL LIQUIDATOR 107 | const minCollateral = 0; 108 | 109 | // get assets in user loan 110 | const { lpAssets, baseAssetIds } = getUserLoanAssets(pools, loan); 111 | 112 | // transaction 113 | let liquidateTxns = prepareLiquidateLoan( 114 | loanAppId, 115 | poolManagerAppId, 116 | sender.addr, 117 | loan.escrowAddress, 118 | reserveAddress, 119 | collateralPool, 120 | borrowPool, 121 | oracle, 122 | lpAssets, 123 | baseAssetIds, 124 | repayAmount, 125 | minCollateral, 126 | loan.borrows[0].isStable, 127 | params, 128 | ); 129 | 130 | // add opup transactions to increase opcode budget TODO better estimate 131 | const budget = Math.ceil(10 + lpAssets.length + 0.5 * baseAssetIds.length); 132 | liquidateTxns = prefixWithOpUp(opup, sender.addr, liquidateTxns, budget, params); 133 | 134 | // group, sign and submit 135 | assignGroupID(liquidateTxns); 136 | const signedTxns = liquidateTxns.map((txn) => txn.signTxn(sender.sk)); 137 | try { 138 | const { txId } = await algodClient.sendRawTransaction(signedTxns).do(); 139 | await waitForConfirmation(algodClient, txId, 1000); 140 | console.log("Successfully liquidated: " + loan.escrowAddress); 141 | } catch (e) { 142 | console.error(e); 143 | console.log("Failed to liquidate: " + loan.escrowAddress); 144 | } 145 | } 146 | } while (nextToken !== undefined); 147 | } 148 | 149 | main().catch(console.error); 150 | -------------------------------------------------------------------------------- /examples/lend/simple-flash-loan.ts: -------------------------------------------------------------------------------- 1 | import { assignGroupID } from "algosdk"; 2 | 3 | import { TestnetPools, TestnetReserveAddress, wrapWithFlashLoan } from "../../src"; 4 | import { algodClient, sender } from "../config"; 5 | 6 | import type { Transaction } from "algosdk"; 7 | 8 | async function main() { 9 | const reserveAddress = TestnetReserveAddress; 10 | const pools = TestnetPools; 11 | 12 | // can put arbitrary txns inside flash loan 13 | const insideTxns: Transaction[] = []; 14 | 15 | // retrieve params 16 | const params = await algodClient.getTransactionParams().do(); 17 | 18 | // there are two ways you can use flash loans 19 | // will show here the simple version where just wrapping inside txns with flash loan 20 | 21 | // final result will be: 22 | // - flash loan borrow of 1 ALGO 23 | // - 24 | // - flash loan repay of 1.001001 ALGO (0.1% fee + 0.000001) 25 | 26 | // flash loan of 1 ALGO, repayment will be 1.001001 ALGO (0.1% + 0.000001) 27 | const algoBorrowAmount = 1e6; 28 | const flashLoanTxns = wrapWithFlashLoan( 29 | insideTxns, 30 | pools.ALGO, 31 | sender.addr, 32 | sender.addr, 33 | reserveAddress, 34 | algoBorrowAmount, 35 | params, 36 | ); 37 | 38 | // group, sign and submit 39 | assignGroupID(flashLoanTxns); 40 | const signedTxns = flashLoanTxns.map((txn) => txn.signTxn(sender.sk)); 41 | await algodClient.sendRawTransaction(signedTxns).do(); 42 | } 43 | 44 | main().catch(console.error); 45 | -------------------------------------------------------------------------------- /examples/xalgo/stake.ts: -------------------------------------------------------------------------------- 1 | import { assignGroupID } from "algosdk"; 2 | 3 | import { 4 | getConsensusState, 5 | prefixWithOpUp, 6 | prepareImmediateStakeTransactions, 7 | TestnetConsensusConfig, 8 | TestnetOpUp, 9 | } from "../../src"; 10 | import { algodClient, sender } from "../config"; 11 | 12 | async function main() { 13 | const opup = TestnetOpUp; 14 | const consensusConfig = TestnetConsensusConfig; 15 | 16 | // retrieve params and consensus state 17 | const params = await algodClient.getTransactionParams().do(); 18 | const consensusState = await getConsensusState(algodClient, consensusConfig); 19 | 20 | // stake 1 ALGO 21 | const stakeAmount = 1e6; 22 | const minReceivedAmount = 0; 23 | let stakeTransactions = prepareImmediateStakeTransactions( 24 | consensusConfig, 25 | consensusState, 26 | sender.addr, 27 | sender.addr, 28 | stakeAmount, 29 | minReceivedAmount, 30 | params, 31 | ); 32 | 33 | // add additional opcode budget (if needed) 34 | stakeTransactions = prefixWithOpUp(opup, sender.addr, stakeTransactions, 0, params); 35 | 36 | // group, sign and submit 37 | assignGroupID(stakeTransactions); 38 | const signedTxns = stakeTransactions.map((txn) => txn.signTxn(sender.sk)); 39 | await algodClient.sendRawTransaction(signedTxns).do(); 40 | } 41 | 42 | main().catch(console.error); 43 | -------------------------------------------------------------------------------- /media/repo-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Folks-Finance/algorand-js-sdk/5d48e97b05c03a744db0e1ad52fda1859827a28a/media/repo-header.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@folks-finance/algorand-sdk", 3 | "version": "0.1.6", 4 | "description": "The official JavaScript SDK for the Folks Finance Protocol", 5 | "main": "dist/index.js", 6 | "types": "dist/types/index.d.ts", 7 | "files": [ 8 | "dist/**/*", 9 | "src" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/Folks-Finance/algorand-js-sdk.git" 14 | }, 15 | "scripts": { 16 | "prepare": "husky", 17 | "build": "tsc -p tsconfig.json", 18 | "typecheck": "tsc --noEmit", 19 | "lint": "eslint .", 20 | "format": "prettier --write 'src/**/*'", 21 | "gen-docs": "typedoc", 22 | "example": "pnpx tsx", 23 | "release": "pnpm build && changeset publish" 24 | }, 25 | "dependencies": { 26 | "algosdk": "^2.9.0" 27 | }, 28 | "devDependencies": { 29 | "@changesets/changelog-github": "^0.5.0", 30 | "@changesets/cli": "^2.27.10", 31 | "@eslint/js": "^9.16.0", 32 | "@types/node": "^22.10.1", 33 | "@typescript-eslint/parser": "^8.18.0", 34 | "@typhonjs-typedoc/typedoc-theme-dmt": "^0.3.0", 35 | "eslint": "^9.16.0", 36 | "eslint-config-prettier": "^9.1.0", 37 | "eslint-import-resolver-typescript": "^3.7.0", 38 | "eslint-plugin-import-x": "^4.5.0", 39 | "eslint-plugin-unicorn": "^56.0.1", 40 | "globals": "^15.13.0", 41 | "husky": "^9.1.7", 42 | "lint-staged": "^15.2.10", 43 | "prettier": "^3.4.2", 44 | "typedoc": "^0.27.4", 45 | "typescript": "^5.7.2", 46 | "typescript-eslint": "^8.17.0" 47 | }, 48 | "author": "Folks Finance", 49 | "license": "MIT", 50 | "lint-staged": { 51 | "*.src/**/*": "prettier --write" 52 | }, 53 | "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c", 54 | "keywords": [ 55 | "folks-finance", 56 | "lending", 57 | "algorand", 58 | "blockchain", 59 | "defi" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | // eslint-disable-next-line import-x/no-default-export 5 | export default { 6 | printWidth: 120, 7 | tabWidth: 2, 8 | useTabs: false, 9 | semi: true, 10 | singleQuote: false, 11 | trailingComma: "all", 12 | }; 13 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/common/governance.ts: -------------------------------------------------------------------------------- 1 | import { getParsedValueFromState, parseUint64s } from "../../utils"; 2 | 3 | import type { Dispenser, DispenserInfo } from "./types"; 4 | import type { Indexer } from "algosdk"; 5 | 6 | /** 7 | * 8 | * Returns information regarding the given liquid governance dispenser. 9 | * 10 | * @param indexerClient - Algorand indexer client to query 11 | * @param dispenser - dispenser to query about 12 | * @returns DispenserInfo[] dispenser info 13 | */ 14 | export async function getDispenserInfo(indexerClient: Indexer, dispenser: Dispenser): Promise { 15 | const { appId } = dispenser; 16 | const res = await indexerClient.lookupApplications(appId).do(); 17 | const state = res["application"]["params"]["global-state"]; 18 | 19 | const distributorAppIds = parseUint64s(String(getParsedValueFromState(state, "distribs"))).map((appId) => 20 | Number(appId), 21 | ); 22 | const isMintingPaused = Boolean(getParsedValueFromState(state, "is_minting_paused") || 0); 23 | 24 | return { 25 | currentRound: res["current-round"], 26 | distributorAppIds, 27 | isMintingPaused, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./mainnet-constants"; 2 | export * from "./governance"; 3 | export * from "./types"; 4 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/common/mainnet-constants.ts: -------------------------------------------------------------------------------- 1 | import type { Dispenser } from "./types"; 2 | 3 | const govDispenser: Dispenser = { 4 | appId: 793119194, 5 | gAlgoId: 793124631, 6 | }; 7 | 8 | export { govDispenser }; 9 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/common/types.ts: -------------------------------------------------------------------------------- 1 | interface Dispenser { 2 | appId: number; 3 | gAlgoId: number; 4 | } 5 | 6 | interface DispenserInfo { 7 | currentRound: number; // round the data was read at 8 | distributorAppIds: number[]; // list of valid distributor app ids 9 | isMintingPaused: boolean; // flag indicating if users can mint gALGO 10 | } 11 | 12 | interface Distributor { 13 | appId: number; 14 | } 15 | 16 | export { Dispenser, DispenserInfo, Distributor }; 17 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v1/constants/abi-contracts.ts: -------------------------------------------------------------------------------- 1 | import { ABIContract } from "algosdk"; 2 | 3 | const abiDistributor = new ABIContract({ 4 | name: "algo-liquid-governance-distributor", 5 | desc: "Mints gALGO when called by verified distributor applications", 6 | methods: [ 7 | { 8 | name: "mint", 9 | desc: "Mint equivalent amount of gALGO as ALGO sent (if in commitment period then also commits user)", 10 | args: [ 11 | { type: "pay", name: "algo_sent", desc: "Send ALGO to the distributor application account" }, 12 | { type: "asset", name: "g_algo", desc: "The gALGO asset" }, 13 | { type: "application", name: "dispenser", desc: "The dispenser application that mints gALGO" }, 14 | ], 15 | returns: { type: "void" }, 16 | }, 17 | { 18 | name: "unmint_premint", 19 | desc: "Unmint in the commitment period pre-minted gALGO for equivalent amount of ALGO", 20 | args: [ 21 | { type: "uint64", name: "unmint_amount", desc: "The amount of pre-minted gALGO to unmint" }, 22 | { type: "application", name: "dispenser", desc: "The dispenser application that mints gALGO" }, 23 | ], 24 | returns: { type: "void" }, 25 | }, 26 | { 27 | name: "unmint", 28 | desc: "Unmint in the commitment period gALGO for equivalent amount of ALGO as gALGO sent", 29 | args: [ 30 | { type: "axfer", name: "g_algo_sent", desc: "Send gALGO to the distributor application account" }, 31 | { type: "application", name: "dispenser", desc: "The dispenser application that mints gALGO" }, 32 | ], 33 | returns: { type: "void" }, 34 | }, 35 | { 36 | name: "claim_premint", 37 | desc: "Claim pre-minted gALGO on behalf of yourself or another account", 38 | args: [ 39 | { type: "account", name: "receiver", desc: "The user that pre-minted gALGO" }, 40 | { type: "asset", name: "g_algo", desc: "The gALGO asset" }, 41 | { type: "application", name: "dispenser", desc: "The dispenser application that mints gALGO" }, 42 | ], 43 | returns: { type: "void" }, 44 | }, 45 | { 46 | name: "burn", 47 | desc: "Burn after the governance period gALGO for equivalent amount of ALGO as gALGO sent", 48 | args: [ 49 | { type: "axfer", name: "g_algo_sent", desc: "Send gALGO to the distributor application account" }, 50 | { type: "application", name: "dispenser", desc: "The dispenser application that mints gALGO" }, 51 | ], 52 | returns: { type: "void" }, 53 | }, 54 | { 55 | name: "early_claim_rewards", 56 | desc: "Early claim governance rewards in the form of gALGO", 57 | args: [ 58 | { type: "uint64", name: "amount", desc: "The amount of committed ALGO to early claim rewards on" }, 59 | { type: "asset", name: "g_algo", desc: "The gALGO asset" }, 60 | { type: "application", name: "dispenser", desc: "The dispenser application that mints gALGO" }, 61 | ], 62 | returns: { type: "void" }, 63 | }, 64 | { 65 | name: "claim_rewards", 66 | desc: "Claim governance rewards in the form of ALGO after they have been distributed by the foundation", 67 | args: [], 68 | returns: { type: "void" }, 69 | }, 70 | ], 71 | }); 72 | 73 | export { abiDistributor }; 74 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v1/constants/mainnet-constants.ts: -------------------------------------------------------------------------------- 1 | import type { Distributor } from "../../common"; 2 | 3 | const govDistributor4: Distributor = { 4 | appId: 793119270, 5 | }; 6 | 7 | const govDistributor5a: Distributor = { 8 | appId: 887391617, 9 | }; 10 | 11 | const govDistributor5b: Distributor = { 12 | appId: 902731930, 13 | }; 14 | 15 | const govDistributor6: Distributor = { 16 | appId: 991196662, 17 | }; 18 | 19 | export { govDistributor4, govDistributor5a, govDistributor5b, govDistributor6 }; 20 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v1/governance.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assignGroupID, 3 | AtomicTransactionComposer, 4 | getApplicationAddress, 5 | getMethodByName, 6 | makeApplicationOptInTxn, 7 | } from "algosdk"; 8 | 9 | import { getParsedValueFromState, signer, transferAlgoOrAsset } from "../../utils"; 10 | 11 | import { abiDistributor } from "./constants/abi-contracts"; 12 | 13 | import type { DistributorInfo, UserCommitmentInfo } from "./types"; 14 | import type { Dispenser, Distributor } from "../common"; 15 | import type { Indexer, SuggestedParams, Transaction } from "algosdk"; 16 | 17 | /** 18 | * 19 | * Returns information regarding the given liquid governance distributor. 20 | * 21 | * @param indexerClient - Algorand indexer client to query 22 | * @param distributor - distributor to query about 23 | * @returns DistributorInfo[] distributor info 24 | */ 25 | async function getDistributorInfo(indexerClient: Indexer, distributor: Distributor): Promise { 26 | const { appId } = distributor; 27 | const res = await indexerClient.lookupApplications(appId).do(); 28 | const state = res["application"]["params"]["global-state"]; 29 | 30 | const dispenserAppId = Number(getParsedValueFromState(state, "dispenser_app_id") || 0); 31 | const commitEnd = BigInt(getParsedValueFromState(state, "commit_end") || 0); 32 | const periodEnd = BigInt(getParsedValueFromState(state, "period_end") || 0); 33 | const totalCommitment = BigInt(getParsedValueFromState(state, "total_commitment") || 0); 34 | const totalCommitmentClaimed = BigInt(getParsedValueFromState(state, "total_commitment_claimed") || 0); 35 | const canClaimAlgoRewards = Boolean(getParsedValueFromState(state, "can_claim_algo_rewards") || 0); 36 | const rewardsPerAlgo = BigInt(getParsedValueFromState(state, "rewards_per_algo") || 0); 37 | const totalRewardsClaimed = BigInt(getParsedValueFromState(state, "total_rewards_claimed") || 0); 38 | const isBurningPaused = Boolean(getParsedValueFromState(state, "is_burning_paused") || 0); 39 | 40 | // optional 41 | const premintEndState = getParsedValueFromState(state, "premint_end"); 42 | const premintEnd = premintEndState !== undefined ? BigInt(premintEndState) : undefined; 43 | 44 | return { 45 | currentRound: res["current-round"], 46 | dispenserAppId, 47 | premintEnd, 48 | commitEnd, 49 | periodEnd, 50 | totalCommitment, 51 | totalCommitmentClaimed, 52 | canClaimAlgoRewards, 53 | rewardsPerAlgo, 54 | totalRewardsClaimed, 55 | isBurningPaused, 56 | }; 57 | } 58 | 59 | /** 60 | * 61 | * Returns information regarding a user's liquid governance commitment. 62 | * 63 | * @param indexerClient - Algorand indexer client to query 64 | * @param distributor - distributor to query about 65 | * @param userAddr - user address to get governance info about 66 | * @returns UserCommitmentInfo[] user commitment info 67 | */ 68 | async function getUserLiquidGovernanceInfo( 69 | indexerClient: Indexer, 70 | distributor: Distributor, 71 | userAddr: string, 72 | ): Promise { 73 | const { appId } = distributor; 74 | 75 | // get user account local state 76 | const req = indexerClient.lookupAccountAppLocalStates(userAddr).applicationID(appId); 77 | const res = await req.do(); 78 | 79 | // user local state 80 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 81 | const state = res["apps-local-states"]?.find((app: any) => app.id === appId)?.["key-value"]; 82 | if (state === undefined) throw new Error("Unable to find commitment for: " + userAddr + "."); 83 | const commitment = BigInt(getParsedValueFromState(state, "commitment") || 0); 84 | const commitmentClaimed = BigInt(getParsedValueFromState(state, "commitment_claimed") || 0); 85 | 86 | // optional 87 | const premintState = getParsedValueFromState(state, "premint"); 88 | const premint = premintState !== undefined ? BigInt(premintState) : undefined; 89 | 90 | return { 91 | currentRound: res["current-round"], 92 | premint, 93 | commitment, 94 | commitmentClaimed, 95 | }; 96 | } 97 | 98 | /** 99 | * 100 | * Returns a group transaction to mint gALGO for ALGO at a one-to-one rate. 101 | * If in the commitment period then also commits user into governance. 102 | * 103 | * @param dispenser - dispenser to mint gALGO from 104 | * @param distributor - distributor that calls dispenser and to send ALGO to 105 | * @param senderAddr - account address for the sender 106 | * @param amount - amount of ALGO to send and gALGO to mint 107 | * @param includeOptIn - whether to include an opt in transaction (must be opted in if minting in commitment period) 108 | * @param params - suggested params for the transactions with the fees overwritten 109 | * @param note - optional note to distinguish who is the minter (must pass to be eligible for revenue share) 110 | * @returns Transaction[] mint transactions 111 | */ 112 | function prepareMintTransactions( 113 | dispenser: Dispenser, 114 | distributor: Distributor, 115 | senderAddr: string, 116 | amount: number | bigint, 117 | includeOptIn: boolean, 118 | params: SuggestedParams, 119 | note?: Uint8Array, 120 | ): Transaction[] { 121 | const atc = new AtomicTransactionComposer(); 122 | const payment = { 123 | txn: transferAlgoOrAsset(0, senderAddr, getApplicationAddress(distributor.appId), amount, { 124 | ...params, 125 | fee: 0, 126 | flatFee: true, 127 | }), 128 | signer, 129 | }; 130 | atc.addMethodCall({ 131 | sender: senderAddr, 132 | signer, 133 | appID: distributor.appId, 134 | method: getMethodByName(abiDistributor.methods, "mint"), 135 | methodArgs: [payment, dispenser.gAlgoId, dispenser.appId], 136 | suggestedParams: { ...params, flatFee: true, fee: 4000 }, 137 | note, 138 | }); 139 | 140 | const txns = atc.buildGroup().map(({ txn }) => { 141 | txn.group = undefined; 142 | return txn; 143 | }); 144 | // for ledger compatibility (max 2 app args), remove index references which are not strictly needed 145 | txns[1].appArgs = txns[1].appArgs?.slice(0, -2); 146 | // user must be opted in before they can mint in the commitment period 147 | if (includeOptIn) 148 | txns.unshift(makeApplicationOptInTxn(senderAddr, { ...params, fee: 1000, flatFee: true }, distributor.appId)); 149 | return assignGroupID(txns); 150 | } 151 | 152 | /** 153 | * 154 | * Returns a transaction to unmint pre-minted gALGO for ALGO at a one-to-one rate. 155 | * Must be in commitment period. By unminting, you will lose your governance rewards. 156 | * 157 | * @param dispenser - dispenser to send gALGO to 158 | * @param distributor - distributor to receive ALGO from 159 | * @param senderAddr - account address for the sender 160 | * @param amount - amount of gALGO to unmint and ALGO to receive 161 | * @param params - suggested params for the transactions with the fees overwritten 162 | * @param note - optional note to distinguish who is the unminter (must pass to be eligible for revenue share) 163 | * @returns Transaction unmint pre-mint transaction 164 | */ 165 | function prepareUnmintPremintTransaction( 166 | dispenser: Dispenser, 167 | distributor: Distributor, 168 | senderAddr: string, 169 | amount: number | bigint, 170 | params: SuggestedParams, 171 | note?: Uint8Array, 172 | ): Transaction { 173 | const atc = new AtomicTransactionComposer(); 174 | atc.addMethodCall({ 175 | sender: senderAddr, 176 | signer, 177 | appID: distributor.appId, 178 | method: getMethodByName(abiDistributor.methods, "unmint_premint"), 179 | methodArgs: [amount, dispenser.appId], 180 | suggestedParams: { ...params, flatFee: true, fee: 2000 }, 181 | note, 182 | }); 183 | const txns = atc.buildGroup().map(({ txn }) => { 184 | txn.group = undefined; 185 | return txn; 186 | }); 187 | return txns[0]; 188 | } 189 | 190 | /** 191 | * 192 | * Returns a group transaction to unmint gALGO for ALGO at a one-to-one rate. 193 | * Must be in commitment period. By unminting, you will lose your governance rewards. 194 | * 195 | * @param dispenser - dispenser to send gALGO to 196 | * @param distributor - distributor to receive ALGO from 197 | * @param senderAddr - account address for the sender 198 | * @param amount - amount of gALGO to send and ALGO to receive 199 | * @param params - suggested params for the transactions with the fees overwritten 200 | * @param note - optional note to distinguish who is the unminter (must pass to be eligible for revenue share) 201 | * @returns Transaction[] unmint transactions 202 | */ 203 | function prepareUnmintTransactions( 204 | dispenser: Dispenser, 205 | distributor: Distributor, 206 | senderAddr: string, 207 | amount: number | bigint, 208 | params: SuggestedParams, 209 | note?: Uint8Array, 210 | ): Transaction[] { 211 | const atc = new AtomicTransactionComposer(); 212 | const assetTransfer = { 213 | txn: transferAlgoOrAsset(dispenser.gAlgoId, senderAddr, getApplicationAddress(dispenser.appId), amount, { 214 | ...params, 215 | fee: 0, 216 | flatFee: true, 217 | }), 218 | signer, 219 | }; 220 | atc.addMethodCall({ 221 | sender: senderAddr, 222 | signer, 223 | appID: distributor.appId, 224 | method: getMethodByName(abiDistributor.methods, "unmint"), 225 | methodArgs: [assetTransfer, dispenser.appId], 226 | suggestedParams: { ...params, flatFee: true, fee: 3000 }, 227 | note, 228 | }); 229 | 230 | const txns = atc.buildGroup().map(({ txn }) => { 231 | txn.group = undefined; 232 | return txn; 233 | }); 234 | return assignGroupID(txns); 235 | } 236 | 237 | /** 238 | * 239 | * Returns a transaction to claim pre-minted gALGO. 240 | * Can be called on behalf of yourself or another user. 241 | * 242 | * @param dispenser - dispenser to send gALGO to 243 | * @param distributor - distributor to receive ALGO from 244 | * @param senderAddr - account address for the sender 245 | * @param receiverAddr - account address for the pre-minter that will receiver the gALGO 246 | * @param params - suggested params for the transactions with the fees overwritten 247 | * @returns Transaction claim pre-mint transaction 248 | */ 249 | function prepareClaimPremintTransaction( 250 | dispenser: Dispenser, 251 | distributor: Distributor, 252 | senderAddr: string, 253 | receiverAddr: string, 254 | params: SuggestedParams, 255 | ): Transaction { 256 | const atc = new AtomicTransactionComposer(); 257 | atc.addMethodCall({ 258 | sender: senderAddr, 259 | signer, 260 | appID: distributor.appId, 261 | method: getMethodByName(abiDistributor.methods, "claim_premint"), 262 | methodArgs: [receiverAddr, dispenser.gAlgoId, dispenser.appId], 263 | suggestedParams: { ...params, flatFee: true, fee: 3000 }, 264 | }); 265 | const txns = atc.buildGroup().map(({ txn }) => { 266 | txn.group = undefined; 267 | return txn; 268 | }); 269 | return txns[0]; 270 | } 271 | 272 | /** 273 | * 274 | * Returns a group transaction to burn gALGO for ALGO at a one-to-one rate. 275 | * Must be after period end. 276 | * 277 | * @param dispenser - dispenser to send gALGO to 278 | * @param distributor - distributor to receive ALGO from 279 | * @param senderAddr - account address for the sender 280 | * @param amount - amount of gALGO to send and ALGO to receive 281 | * @param params - suggested params for the transactions with the fees overwritten 282 | * @returns Transaction[] burn transactions 283 | */ 284 | function prepareBurnTransactions( 285 | dispenser: Dispenser, 286 | distributor: Distributor, 287 | senderAddr: string, 288 | amount: number | bigint, 289 | params: SuggestedParams, 290 | ): Transaction[] { 291 | const atc = new AtomicTransactionComposer(); 292 | const assetTransfer = { 293 | txn: transferAlgoOrAsset(dispenser.gAlgoId, senderAddr, getApplicationAddress(dispenser.appId), amount, { 294 | ...params, 295 | fee: 0, 296 | flatFee: true, 297 | }), 298 | signer, 299 | }; 300 | atc.addMethodCall({ 301 | sender: senderAddr, 302 | signer, 303 | appID: distributor.appId, 304 | method: getMethodByName(abiDistributor.methods, "burn"), 305 | methodArgs: [assetTransfer, dispenser.appId], 306 | suggestedParams: { ...params, flatFee: true, fee: 3000 }, 307 | }); 308 | 309 | const txns = atc.buildGroup().map(({ txn }) => { 310 | txn.group = undefined; 311 | return txn; 312 | }); 313 | return assignGroupID(txns); 314 | } 315 | 316 | /** 317 | * 318 | * Returns a group transaction to early claim governance rewards for a given amount of ALGO. 319 | * Must be after commitment end. 320 | * Rewards received is in gALGO. Amount of rewards is determined by rewards_per_algo. 321 | * 322 | * @param dispenser - distributor to receive gALGO from 323 | * @param distributor - distributor which has sender's commitment 324 | * @param senderAddr - account address for the sender 325 | * @param amount - amount of ALGO to early claim rewards on 326 | * @param params - suggested params for the transactions with the fees overwritten 327 | * @returns Transaction early claim governance rewards transaction 328 | */ 329 | function prepareEarlyClaimGovernanceRewardsTransaction( 330 | dispenser: Dispenser, 331 | distributor: Distributor, 332 | senderAddr: string, 333 | amount: number | bigint, 334 | params: SuggestedParams, 335 | ): Transaction { 336 | const atc = new AtomicTransactionComposer(); 337 | atc.addMethodCall({ 338 | sender: senderAddr, 339 | signer, 340 | appID: distributor.appId, 341 | method: getMethodByName(abiDistributor.methods, "early_claim_rewards"), 342 | methodArgs: [amount, dispenser.gAlgoId, dispenser.appId], 343 | suggestedParams: { ...params, flatFee: true, fee: 3000 }, 344 | }); 345 | 346 | const txns = atc.buildGroup().map(({ txn }) => { 347 | txn.group = undefined; 348 | return txn; 349 | }); 350 | // for ledger compatibility (max 2 app args), remove index references which are not strictly needed 351 | txns[0].appArgs = txns[0].appArgs?.slice(0, -2); 352 | return txns[0]; 353 | } 354 | 355 | /** 356 | * 357 | * Returns a group transaction to claim governance rewards for unclaimed commitment. 358 | * Must be after period end and rewards have been distributed from Algorand Foundation. 359 | * Rewards received is in ALGO. Amount of rewards is determined by rewards_per_algo. 360 | * 361 | * @param distributor - distributor that calls dispenser and to send ALGO to 362 | * @param senderAddr - account address for the sender 363 | * @param params - suggested params for the transactions with the fees overwritten 364 | * @returns Transaction claim governance rewards transaction 365 | */ 366 | function prepareClaimGovernanceRewardsTransaction( 367 | distributor: Distributor, 368 | senderAddr: string, 369 | params: SuggestedParams, 370 | ): Transaction { 371 | const atc = new AtomicTransactionComposer(); 372 | atc.addMethodCall({ 373 | sender: senderAddr, 374 | signer, 375 | appID: distributor.appId, 376 | method: getMethodByName(abiDistributor.methods, "claim_rewards"), 377 | suggestedParams: { ...params, flatFee: true, fee: 2000 }, 378 | }); 379 | const txns = atc.buildGroup().map(({ txn }) => { 380 | txn.group = undefined; 381 | return txn; 382 | }); 383 | return txns[0]; 384 | } 385 | 386 | export { 387 | getDistributorInfo, 388 | getUserLiquidGovernanceInfo, 389 | prepareMintTransactions, 390 | prepareUnmintPremintTransaction, 391 | prepareUnmintTransactions, 392 | prepareClaimPremintTransaction, 393 | prepareBurnTransactions, 394 | prepareEarlyClaimGovernanceRewardsTransaction, 395 | prepareClaimGovernanceRewardsTransaction, 396 | }; 397 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v1/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants/abi-contracts"; 2 | export * from "./constants/mainnet-constants"; 3 | export * from "./governance"; 4 | export * from "./types"; 5 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v1/types.ts: -------------------------------------------------------------------------------- 1 | interface DistributorInfo { 2 | currentRound: number; // round the data was read at 3 | dispenserAppId: number; // id of dispenser app which mints gALGO 4 | premintEnd?: bigint; // unix timestamp for the end of the pre-mint period 5 | commitEnd: bigint; // unix timestamp for end of the commitment period 6 | periodEnd: bigint; // unix timestamp for end of the governance period 7 | totalCommitment: bigint; // total amount of ALGOs committed 8 | totalCommitmentClaimed: bigint; // total amount of ALGOs committed whose rewards have already been claimed 9 | canClaimAlgoRewards: boolean; // flag to indicate if users can claim ALGO rewards (excl early claims) 10 | rewardsPerAlgo: bigint; // reward amount per ALGO committed (16 d.p.) 11 | totalRewardsClaimed: bigint; // total amount of rewards claimed 12 | isBurningPaused: boolean; // flag to indicate if users can burn their ALGO for gALGO 13 | } 14 | 15 | interface UserCommitmentInfo { 16 | currentRound: number; 17 | premint?: bigint; // amount of ALGOs the user has pre-minted and not yet claimed 18 | commitment: bigint; // amount of ALGOs the user has committed 19 | commitmentClaimed: bigint; // amount of ALGOs the user has committed whose rewards have already been claimed 20 | } 21 | 22 | export { DistributorInfo, UserCommitmentInfo }; 23 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v2/constants/abi-contracts.ts: -------------------------------------------------------------------------------- 1 | import { ABIContract } from "algosdk"; 2 | 3 | const abiDistributor = new ABIContract({ 4 | name: "algo-liquid-governance-distributor", 5 | desc: "Mints gALGO when called by verified distributor applications", 6 | methods: [ 7 | { 8 | name: "add_escrow", 9 | desc: "", 10 | args: [ 11 | { type: "pay", name: "user_call", desc: "" }, 12 | { type: "bool", name: "delegate", desc: "" }, 13 | ], 14 | returns: { type: "void" }, 15 | }, 16 | { 17 | name: "mint", 18 | desc: "", 19 | args: [ 20 | { type: "pay", name: "send_algo", desc: "" }, 21 | { type: "account", name: "escrow", desc: "" }, 22 | { type: "application", name: "dispenser", desc: "" }, 23 | { type: "asset", name: "g_algo", desc: "" }, 24 | { type: "bool", name: "ensure_commit", desc: "" }, 25 | ], 26 | returns: { type: "void" }, 27 | }, 28 | { 29 | name: "unmint_premint", 30 | desc: "", 31 | args: [ 32 | { type: "account", name: "escrow", desc: "" }, 33 | { type: "uint64", name: "unmint_amount", desc: "" }, 34 | ], 35 | returns: { type: "void" }, 36 | }, 37 | { 38 | name: "unmint", 39 | desc: "", 40 | args: [ 41 | { type: "axfer", name: "send_galgo", desc: "" }, 42 | { type: "account", name: "escrow", desc: "" }, 43 | { type: "application", name: "dispenser", desc: "" }, 44 | ], 45 | returns: { type: "void" }, 46 | }, 47 | { 48 | name: "claim_premint", 49 | desc: "", 50 | args: [ 51 | { type: "account", name: "escrow", desc: "" }, 52 | { type: "account", name: "receiver", desc: "" }, 53 | { type: "application", name: "dispenser", desc: "" }, 54 | { type: "asset", name: "g_algo", desc: "" }, 55 | ], 56 | returns: { type: "void" }, 57 | }, 58 | { 59 | name: "register_online", 60 | desc: "", 61 | args: [ 62 | { type: "pay", name: "send_algo", desc: "" }, 63 | { type: "account", name: "escrow", desc: "" }, 64 | { type: "address", name: "vote_key", desc: "" }, 65 | { type: "address", name: "sel_key", desc: "" }, 66 | { type: "byte[64]", name: "state_proof_key", desc: "" }, 67 | { type: "uint64", name: "vote_first", desc: "" }, 68 | { type: "uint64", name: "vote_last", desc: "" }, 69 | { type: "uint64", name: "vote_key_dilution", desc: "" }, 70 | ], 71 | returns: { type: "void" }, 72 | }, 73 | { 74 | name: "register_offline", 75 | desc: "", 76 | args: [{ type: "account", name: "escrow", desc: "" }], 77 | returns: { type: "void" }, 78 | }, 79 | { 80 | name: "governance", 81 | desc: "", 82 | args: [ 83 | { type: "account", name: "escrow", desc: "" }, 84 | { type: "account", name: "dest", desc: "" }, 85 | { type: "string", name: "note", desc: "" }, 86 | ], 87 | returns: { type: "void" }, 88 | }, 89 | { 90 | name: "remove_escrow", 91 | desc: "", 92 | args: [{ type: "account", name: "escrow", desc: "" }], 93 | returns: { type: "void" }, 94 | }, 95 | { 96 | name: "burn", 97 | desc: "", 98 | args: [ 99 | { type: "axfer", name: "send_galgo", desc: "" }, 100 | { type: "application", name: "dispenser", desc: "" }, 101 | ], 102 | returns: { type: "void" }, 103 | }, 104 | ], 105 | }); 106 | 107 | export { abiDistributor }; 108 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v2/constants/mainnet-constants.ts: -------------------------------------------------------------------------------- 1 | import type { Distributor } from "../../common"; 2 | 3 | const govDistributor7: Distributor = { 4 | appId: 1073098885, 5 | }; 6 | 7 | const govDistributor8: Distributor = { 8 | appId: 1136393919, 9 | }; 10 | 11 | const govDistributor9: Distributor = { 12 | appId: 1200551652, 13 | }; 14 | 15 | const govDistributor10: Distributor = { 16 | appId: 1282254855, 17 | }; 18 | 19 | const govDistributor11: Distributor = { 20 | appId: 1702641473, 21 | }; 22 | 23 | const govDistributor12: Distributor = { 24 | appId: 2057814942, 25 | }; 26 | 27 | const govDistributor13: Distributor = { 28 | appId: 2330032485, 29 | }; 30 | 31 | const govDistributor14: Distributor = { 32 | appId: 2629511242, 33 | }; 34 | 35 | export { 36 | govDistributor7, 37 | govDistributor8, 38 | govDistributor9, 39 | govDistributor10, 40 | govDistributor11, 41 | govDistributor12, 42 | govDistributor13, 43 | govDistributor14, 44 | }; 45 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v2/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants/abi-contracts"; 2 | export * from "./constants/mainnet-constants"; 3 | export * from "./governance"; 4 | export * from "./types"; 5 | -------------------------------------------------------------------------------- /src/algo-liquid-governance/v2/types.ts: -------------------------------------------------------------------------------- 1 | interface DistributorInfo { 2 | currentRound?: number; // round the data was read at 3 | dispenserAppId: number; // id of dispenser app which mints gALGO 4 | premintEnd: bigint; // unix timestamp for the end of the pre-mint period 5 | commitEnd: bigint; // unix timestamp for end of the commitment period 6 | periodEnd: bigint; // unix timestamp for end of the governance period 7 | fee: bigint; // minting fee 4 d.p. 8 | totalCommitment: bigint; // total amount of ALGOs committed 9 | isBurningPaused: boolean; // flag to indicate if users can burn their ALGO for gALGO 10 | } 11 | 12 | interface UserCommitmentInfo { 13 | currentRound?: number; 14 | userAddress: string; 15 | canDelegate: boolean; // whether voting can be delegated to admin 16 | premint: bigint; // amount of ALGOs the user has pre-minted and not yet claimed 17 | commitment: bigint; // amount of ALGOs the user has committed 18 | nonCommitment: bigint; // amount of ALGOs the user has added after the commitment period 19 | } 20 | 21 | interface EscrowGovernanceStatus { 22 | currentRound?: number; 23 | balance: bigint; 24 | isOnline: boolean; 25 | status?: { 26 | version: number; 27 | commitment: bigint; 28 | beneficiaryAddress?: string; 29 | xGovControlAddress?: string; 30 | }; 31 | } 32 | 33 | export { DistributorInfo, UserCommitmentInfo, EscrowGovernanceStatus }; 34 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * as algoLiquidGovernanceV1 from "./algo-liquid-governance/v1"; 2 | export * from "./algo-liquid-governance/common"; 3 | export * from "./algo-liquid-governance/v2"; 4 | 5 | export * from "./lend"; 6 | 7 | export * from "./xalgo"; 8 | 9 | export * from "./math-lib"; 10 | -------------------------------------------------------------------------------- /src/lend/abi-contracts/deposit-staking.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "f_staking", 3 | "methods": [ 4 | { 5 | "name": "add_f_staking_escrow", 6 | "desc": "Add f staking escrow for a user. The escrow opts in and rekeys itself to the f staking application.", 7 | "args": [ 8 | { 9 | "type": "pay", 10 | "name": "user_call", 11 | "desc": "The transaction from the user to approve the f staking escrow." 12 | } 13 | ], 14 | "returns": { 15 | "type": "void" 16 | } 17 | }, 18 | { 19 | "name": "opt_escrow_into_asset", 20 | "desc": "Add support for a f staking escrow to hold a pool's f asset.", 21 | "args": [ 22 | { 23 | "type": "account", 24 | "name": "escrow", 25 | "desc": "The user's f staking escrow." 26 | }, 27 | { 28 | "type": "application", 29 | "name": "pool", 30 | "desc": "The pool application to support." 31 | }, 32 | { 33 | "type": "asset", 34 | "name": "f_asset", 35 | "desc": "The f asset of the pool." 36 | }, 37 | { 38 | "type": "uint8", 39 | "name": "stake_index", 40 | "desc": "The index of the staking program in the array." 41 | } 42 | ], 43 | "returns": { 44 | "type": "void" 45 | } 46 | }, 47 | { 48 | "name": "sync_stake", 49 | "desc": "Sync the stake balance of an escrow's staking program.", 50 | "args": [ 51 | { 52 | "type": "account", 53 | "name": "escrow", 54 | "desc": "The user's f staking escrow." 55 | }, 56 | { 57 | "type": "application", 58 | "name": "pool", 59 | "desc": "The pool of the staking program." 60 | }, 61 | { 62 | "type": "asset", 63 | "name": "f_asset", 64 | "desc": "The f asset of the pool." 65 | }, 66 | { 67 | "type": "uint8", 68 | "name": "stake_index", 69 | "desc": "The index of the staking program in the array." 70 | } 71 | ], 72 | "returns": { 73 | "type": "void" 74 | } 75 | }, 76 | { 77 | "name": "withdraw_stake", 78 | "desc": "Withdraw a stake from f staking escrow.", 79 | "args": [ 80 | { 81 | "type": "account", 82 | "name": "escrow", 83 | "desc": "The user's f staking escrow." 84 | }, 85 | { 86 | "type": "account", 87 | "name": "receiver", 88 | "desc": "The account to receive the stake sent from the f staking escrow." 89 | }, 90 | { 91 | "type": "application", 92 | "name": "pool_manager", 93 | "desc": "The pool manager application of the pool." 94 | }, 95 | { 96 | "type": "application", 97 | "name": "pool", 98 | "desc": "The pool of the staking program to withdraw from." 99 | }, 100 | { 101 | "type": "asset", 102 | "name": "asset", 103 | "desc": "The asset of the pool." 104 | }, 105 | { 106 | "type": "asset", 107 | "name": "f_asset", 108 | "desc": "The f asset of the pool." 109 | }, 110 | { 111 | "type": "uint64", 112 | "name": "amount", 113 | "desc": "The amount of asset / f asset to send to withdraw from escrow." 114 | }, 115 | { 116 | "type": "bool", 117 | "name": "is_f_asset_amount", 118 | "desc": "Whether the amount to withdraw is expressed in terms of f asset or asset." 119 | }, 120 | { 121 | "type": "bool", 122 | "name": "remain_deposited", 123 | "desc": "Whether receiver should get f asset or asset. Cannot remain deposited and use asset amount." 124 | }, 125 | { 126 | "type": "uint8", 127 | "name": "stake_index", 128 | "desc": "The index of the staking program in the array." 129 | } 130 | ], 131 | "returns": { 132 | "type": "void" 133 | } 134 | }, 135 | { 136 | "name": "claim_rewards", 137 | "desc": "Claim rewards for an escrow's staking program", 138 | "args": [ 139 | { 140 | "type": "account", 141 | "name": "escrow", 142 | "desc": "The user's f staking escrow." 143 | }, 144 | { 145 | "type": "account", 146 | "name": "receiver", 147 | "desc": "The account to receive the reward sent from the f staking application." 148 | }, 149 | { 150 | "type": "uint64", 151 | "name": "stake_index", 152 | "desc": "The index of the staking program in the array." 153 | } 154 | ], 155 | "returns": { 156 | "type": "void" 157 | } 158 | }, 159 | { 160 | "name": "close_out_escrow_from_asset", 161 | "desc": "Remove support for a f staking escrow to hold a pool's f asset.", 162 | "args": [ 163 | { 164 | "type": "account", 165 | "name": "escrow", 166 | "desc": "The user's f staking escrow." 167 | }, 168 | { 169 | "type": "account", 170 | "name": "f_asset_creator" 171 | }, 172 | { 173 | "type": "asset", 174 | "name": "f_asset" 175 | } 176 | ], 177 | "returns": { 178 | "type": "void" 179 | } 180 | }, 181 | { 182 | "name": "remove_f_staking_escrow", 183 | "desc": "Remove a f staking escrow for a user and return its minimum balance.", 184 | "args": [ 185 | { 186 | "type": "account", 187 | "name": "escrow", 188 | "desc": "The user's f staking escrow." 189 | } 190 | ], 191 | "returns": { 192 | "type": "void" 193 | } 194 | } 195 | ], 196 | "networks": {} 197 | } 198 | -------------------------------------------------------------------------------- /src/lend/abi-contracts/deposits.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deposits", 3 | "desc": "Allows users to create an escrow which will hold their deposits for them.", 4 | "methods": [ 5 | { 6 | "name": "add_deposit_escrow", 7 | "desc": "Add a deposit escrow for a user. The escrow opts in and rekeys itself to the deposit application.", 8 | "args": [ 9 | { 10 | "type": "pay", 11 | "name": "user_call", 12 | "desc": "The transaction from the user to approve the deposit escrow." 13 | } 14 | ], 15 | "returns": { 16 | "type": "void" 17 | } 18 | }, 19 | { 20 | "name": "opt_escrow_into_asset", 21 | "desc": "Add support for a deposit escrow to hold a pool's f asset.", 22 | "args": [ 23 | { 24 | "type": "account", 25 | "name": "escrow", 26 | "desc": "The user's deposit escrow." 27 | }, 28 | { 29 | "type": "application", 30 | "name": "pool_manager", 31 | "desc": "The pool manager application of the deposits application." 32 | }, 33 | { 34 | "type": "application", 35 | "name": "pool", 36 | "desc": "The pool application to support." 37 | }, 38 | { 39 | "type": "asset", 40 | "name": "f_asset", 41 | "desc": "The f asset of the pool." 42 | }, 43 | { 44 | "type": "uint8", 45 | "name": "index", 46 | "desc": "The index of the pool in the pool manager array." 47 | } 48 | ], 49 | "returns": { 50 | "type": "void" 51 | } 52 | }, 53 | { 54 | "name": "withdraw", 55 | "desc": "Withdraw an asset from deposit escrow.", 56 | "args": [ 57 | { 58 | "type": "account", 59 | "name": "escrow", 60 | "desc": "The user's deposit escrow." 61 | }, 62 | { 63 | "type": "account", 64 | "name": "receiver", 65 | "desc": "The account to receive the asset from the pool." 66 | }, 67 | { 68 | "type": "application", 69 | "name": "pool_manager", 70 | "desc": "The pool manager application of the pool." 71 | }, 72 | { 73 | "type": "application", 74 | "name": "pool", 75 | "desc": "The pool to withdraw from." 76 | }, 77 | { 78 | "type": "asset", 79 | "name": "asset", 80 | "desc": "The asset of the pool." 81 | }, 82 | { 83 | "type": "asset", 84 | "name": "f_asset", 85 | "desc": "The f asset of the pool." 86 | }, 87 | 88 | { 89 | "type": "uint64", 90 | "name": "amount", 91 | "desc": "The amount of asset / f asset to send to withdraw from escrow." 92 | }, 93 | { 94 | "type": "bool", 95 | "name": "is_f_asset_amount", 96 | "desc": "Whether the amount to withdraw is expressed in terms of f asset or asset." 97 | }, 98 | { 99 | "type": "bool", 100 | "name": "remain_deposited", 101 | "desc": "Whether receiver should get f asset or asset. Cannot remain deposited and use asset amount." 102 | }, 103 | { 104 | "type": "uint8", 105 | "name": "index", 106 | "desc": "The index of the pool in the pool manager array." 107 | } 108 | ], 109 | "returns": { 110 | "type": "void" 111 | } 112 | }, 113 | { 114 | "name": "close_out_escrow_from_asset", 115 | "desc": "Remove support for a deposit escrow to hold a pool's f asset.", 116 | "args": [ 117 | { 118 | "type": "account", 119 | "name": "escrow", 120 | "desc": "The user user's deposit escrow." 121 | }, 122 | { 123 | "type": "account", 124 | "name": "f_asset_creator", 125 | "desc": "The f asset creator account, also known as the pool account." 126 | }, 127 | { 128 | "type": "asset", 129 | "name": "f_asset", 130 | "desc": "The f asset of the pool." 131 | } 132 | ], 133 | "returns": { 134 | "type": "void" 135 | } 136 | }, 137 | { 138 | "name": "remove_deposit_escrow", 139 | "desc": "Remove a deposit escrow for a user and return its minimum balance.", 140 | "args": [ 141 | { 142 | "type": "account", 143 | "name": "escrow", 144 | "desc": "The user's deposit escrow." 145 | } 146 | ], 147 | "returns": { 148 | "type": "void" 149 | } 150 | } 151 | ], 152 | "networks": {} 153 | } 154 | -------------------------------------------------------------------------------- /src/lend/abi-contracts/index.ts: -------------------------------------------------------------------------------- 1 | import { ABIContract } from "algosdk"; 2 | 3 | import depositStakingABI from "./deposit-staking.json"; 4 | import depositsABI from "./deposits.json"; 5 | import loanABI from "./loan.json"; 6 | import lpTokenOracleABI from "./lp-token-oracle.json"; 7 | import oracleAdapterABI from "./oracle-adapter.json"; 8 | import poolABI from "./pool.json"; 9 | 10 | export const depositsABIContract = new ABIContract(depositsABI); 11 | export const depositStakingABIContract = new ABIContract(depositStakingABI); 12 | export const loanABIContract = new ABIContract(loanABI); 13 | export const lpTokenOracleABIContract = new ABIContract(lpTokenOracleABI); 14 | export const oracleAdapterABIContract = new ABIContract(oracleAdapterABI); 15 | export const poolABIContract = new ABIContract(poolABI); 16 | -------------------------------------------------------------------------------- /src/lend/abi-contracts/loan.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Loan", 3 | "desc": "Combines multiple pools together to allows loans to be taken out where some of these pool assets are collateral and some of the pool assets are borrowed.", 4 | "methods": [ 5 | { 6 | "name": "create_loan", 7 | "desc": "Add a loan escrow for a user. The escrow opts in and rekeys itself to the loan application.", 8 | "args": [ 9 | { 10 | "type": "pay", 11 | "name": "user_call", 12 | "desc": "The transaction from the user to approve the loan escrow." 13 | } 14 | ], 15 | "returns": { 16 | "type": "void" 17 | } 18 | }, 19 | { 20 | "name": "add_collateral", 21 | "desc": "Add support for a collateral in a loan escrow.", 22 | "args": [ 23 | { 24 | "type": "account", 25 | "name": "escrow", 26 | "desc": "The user's loan escrow." 27 | }, 28 | { 29 | "type": "asset", 30 | "name": "f_asset", 31 | "desc": "The f asset of the pool." 32 | }, 33 | { 34 | "type": "application", 35 | "name": "pool", 36 | "desc": "The pool application to add support for." 37 | }, 38 | { 39 | "type": "uint8", 40 | "name": "pool_manager_index", 41 | "desc": "The index of the pool in the pool manager array." 42 | }, 43 | { 44 | "type": "uint8", 45 | "name": "loan_index", 46 | "desc": "The index of the pool in the loan array." 47 | }, 48 | { 49 | "type": "application", 50 | "name": "pool_manager", 51 | "desc": "The pool manager application of the loan." 52 | } 53 | ], 54 | "returns": { 55 | "type": "void" 56 | } 57 | }, 58 | { 59 | "name": "sync_collateral", 60 | "desc": "Sync the collateral balance of a loan's given collateral. Should be proceeded with transaction sending the collateral to the loan escrow in the same group transaction.", 61 | "args": [ 62 | { 63 | "type": "appl", 64 | "name": "refresh_prices", 65 | "desc": "The transaction to refresh the pool's asset price." 66 | }, 67 | { 68 | "type": "account", 69 | "name": "escrow", 70 | "desc": "The user's loan escrow." 71 | }, 72 | { 73 | "type": "asset", 74 | "name": "f_asset", 75 | "desc": "The f asset of the pool." 76 | }, 77 | { 78 | "type": "application", 79 | "name": "pool", 80 | "desc": "The pool application to sync." 81 | }, 82 | { 83 | "type": "application", 84 | "name": "pool_manager", 85 | "desc": "The pool manager application of the loan." 86 | }, 87 | { 88 | "type": "application", 89 | "name": "oracle_adapter", 90 | "desc": "The oracle adapter application of the loan." 91 | } 92 | ], 93 | "returns": { 94 | "type": "void" 95 | } 96 | }, 97 | { 98 | "name": "reduce_collateral", 99 | "desc": "Reduce the collateral of a loan.", 100 | "args": [ 101 | { 102 | "type": "appl", 103 | "name": "refresh_prices", 104 | "desc": "The transaction to refresh the loan's asset prices." 105 | }, 106 | { 107 | "type": "account", 108 | "name": "escrow", 109 | "desc": "The user's escrow." 110 | }, 111 | { 112 | "type": "account", 113 | "name": "receiver", 114 | "desc": "The account to receive the f asset sent from the loan escrow." 115 | }, 116 | { 117 | "type": "asset", 118 | "name": "asset", 119 | "desc": "The asset of the pool." 120 | }, 121 | { 122 | "type": "asset", 123 | "name": "f_asset", 124 | "desc": "The f asset of the pool." 125 | }, 126 | { 127 | "type": "uint64", 128 | "name": "amount", 129 | "desc": "The amount to reduce the collateral by." 130 | }, 131 | { 132 | "type": "bool", 133 | "name": "is_f_asset_amount", 134 | "desc": "Whether the amount of collateral to reduce by is expressed in terms of f asset or asset." 135 | }, 136 | { 137 | "type": "application", 138 | "name": "pool", 139 | "desc": "The pool to reduce the collateral of." 140 | }, 141 | { 142 | "type": "application", 143 | "name": "pool_manager", 144 | "desc": "The pool manager application of the pool." 145 | }, 146 | { 147 | "type": "application", 148 | "name": "oracle_adapter", 149 | "desc": "The oracle adapter application of the loan." 150 | } 151 | ], 152 | "returns": { 153 | "type": "void" 154 | } 155 | }, 156 | { 157 | "name": "swap_collateral_begin", 158 | "desc": "Withdraw collateral from a loan escrow without checking if under-collateralized. Must be groped together with swap_collateral_end method call.", 159 | "args": [ 160 | { 161 | "type": "account", 162 | "name": "escrow", 163 | "desc": "The user's loan escrow." 164 | }, 165 | { 166 | "type": "account", 167 | "name": "receiver", 168 | "desc": "The account to receive the f asset sent from the loan escrow." 169 | }, 170 | { 171 | "type": "asset", 172 | "name": "asset", 173 | "desc": "The asset of the pool." 174 | }, 175 | { 176 | "type": "asset", 177 | "name": "f_asset", 178 | "desc": "The f asset of the pool." 179 | }, 180 | { 181 | "type": "uint64", 182 | "name": "amount", 183 | "desc": "The amount of collateral to swap." 184 | }, 185 | { 186 | "type": "bool", 187 | "name": "is_f_asset_amount", 188 | "desc": "Whether the amount of collateral to swap is expressed in terms of f asset or asset." 189 | }, 190 | { 191 | "type": "uint64", 192 | "name": "txn_index", 193 | "desc": "The transaction index in the group transaction for the swap_collateral_end method call." 194 | }, 195 | { 196 | "type": "application", 197 | "name": "pool", 198 | "desc": "The pool to swap the collateral of." 199 | }, 200 | { 201 | "type": "application", 202 | "name": "pool_manager", 203 | "desc": "The pool manager application of the pool." 204 | } 205 | ], 206 | "returns": { 207 | "type": "void" 208 | } 209 | }, 210 | { 211 | "name": "swap_collateral_end", 212 | "desc": "Finalise a swap collateral. Must be grouped together with swap_collateral_begin method call.", 213 | "args": [ 214 | { 215 | "type": "appl", 216 | "name": "refresh_prices", 217 | "desc": "The transaction to refresh the loan's asset prices." 218 | }, 219 | { 220 | "type": "account", 221 | "name": "escrow", 222 | "desc": "The user's loan escrow." 223 | }, 224 | { 225 | "type": "application", 226 | "name": "pool_manager", 227 | "desc": "The pool manager application of the pool." 228 | }, 229 | { 230 | "type": "application", 231 | "name": "oracle_adapter", 232 | "desc": "The oracle adapter application of the loan." 233 | } 234 | ], 235 | "returns": { 236 | "type": "void" 237 | } 238 | }, 239 | { 240 | "name": "remove_collateral", 241 | "desc": "Remove support for a collateral in a loan escrow.", 242 | "args": [ 243 | { 244 | "type": "account", 245 | "name": "escrow", 246 | "desc": "The user's loan escrow." 247 | }, 248 | { 249 | "type": "asset", 250 | "name": "f_asset", 251 | "desc": "The f asset of the pool." 252 | }, 253 | { 254 | "type": "application", 255 | "name": "pool", 256 | "desc": "The pool application to remove support for." 257 | } 258 | ], 259 | "returns": { 260 | "type": "void" 261 | } 262 | }, 263 | { 264 | "name": "borrow", 265 | "desc": "Borrow an asset using collateral of a loan escrow.", 266 | "args": [ 267 | { 268 | "type": "appl", 269 | "name": "refresh_prices", 270 | "desc": "The transaction to refresh the loan's asset prices." 271 | }, 272 | { 273 | "type": "account", 274 | "name": "escrow", 275 | "desc": "The user's loan escrow." 276 | }, 277 | { 278 | "type": "account", 279 | "name": "receiver", 280 | "desc": "The account to receive the asset sent by the pool." 281 | }, 282 | { 283 | "type": "asset", 284 | "name": "asset", 285 | "desc": "The asset of the pool." 286 | }, 287 | { 288 | "type": "uint64", 289 | "name": "amount", 290 | "desc": "The amount to borrow." 291 | }, 292 | { 293 | "type": "uint64", 294 | "name": "max_stable_rate", 295 | "desc": "The maximum stable rate of the borrow. If zero then borrow is interpreted as a variable rate borrow." 296 | }, 297 | { 298 | "type": "uint8", 299 | "name": "pool_manager_index", 300 | "desc": "The index of the pool in the pool manager array." 301 | }, 302 | { 303 | "type": "uint8", 304 | "name": "loan_index", 305 | "desc": "The index of the pool in the loan array." 306 | }, 307 | { 308 | "type": "application", 309 | "name": "pool", 310 | "desc": "The pool to borrow from." 311 | }, 312 | { 313 | "type": "application", 314 | "name": "pool_manager", 315 | "desc": "The pool manager application of the pool." 316 | }, 317 | { 318 | "type": "application", 319 | "name": "oracle_adapter", 320 | "desc": "The oracle adapter application of the loan." 321 | } 322 | ], 323 | "returns": { 324 | "type": "void" 325 | } 326 | }, 327 | { 328 | "name": "switch_borrow_type", 329 | "desc": "Switch the borrow type of a borrow from variable to stable or stable to variable.", 330 | "args": [ 331 | { 332 | "type": "account", 333 | "name": "escrow", 334 | "desc": "The user's loan escrow." 335 | }, 336 | { 337 | "type": "asset", 338 | "name": "asset", 339 | "desc": "The asset of the pool." 340 | }, 341 | { 342 | "type": "uint64", 343 | "name": "max_stable_rate", 344 | "desc": "The maximum stable rate to switch the borrow to. If zero then interpreted as switching a stable rate borrow to a variable rate borrow." 345 | }, 346 | { 347 | "type": "application", 348 | "name": "pool", 349 | "desc": "The pool application so switch the borrow type of." 350 | }, 351 | { 352 | "type": "application", 353 | "name": "pool_manager", 354 | "desc": "The pool manager application of the pool." 355 | } 356 | ], 357 | "returns": { 358 | "type": "void" 359 | } 360 | }, 361 | { 362 | "name": "repay_with_txn", 363 | "desc": "Repay a borrow using an asset transfer transaction.", 364 | "args": [ 365 | { 366 | "type": "txn", 367 | "name": "send_asset_txn", 368 | "desc": "The transaction to the pool to repay the borrow." 369 | }, 370 | { 371 | "type": "account", 372 | "name": "escrow", 373 | "desc": "The user's loan escrow." 374 | }, 375 | { 376 | "type": "account", 377 | "name": "receiver", 378 | "desc": "The account to receive the fr asset rewards if there are any." 379 | }, 380 | { 381 | "type": "account", 382 | "name": "reserve", 383 | "desc": "The account to receive the protocol revenue from the percentage of the accrued interest." 384 | }, 385 | { 386 | "type": "asset", 387 | "name": "asset", 388 | "desc": "The asset of the pool." 389 | }, 390 | { 391 | "type": "asset", 392 | "name": "fr_asset", 393 | "desc": "The fr asset of the pool." 394 | }, 395 | { 396 | "type": "bool", 397 | "name": "is_stable", 398 | "desc": "Whether the borrow that is being repaid is a stable or variable rate borrow." 399 | }, 400 | { 401 | "type": "application", 402 | "name": "pool", 403 | "desc": "The pool to repay." 404 | }, 405 | { 406 | "type": "application", 407 | "name": "pool_manager", 408 | "desc": "The pool manager application of the pool." 409 | } 410 | ], 411 | "returns": { 412 | "type": "void" 413 | } 414 | }, 415 | { 416 | "name": "repay_with_collateral", 417 | "desc": "Repay a borrow using existing collateral in the loan escrow.", 418 | "args": [ 419 | { 420 | "type": "account", 421 | "name": "escrow", 422 | "desc": "The user's loan escrow." 423 | }, 424 | { 425 | "type": "account", 426 | "name": "receiver", 427 | "desc": "The account to receive the fr asset rewards if there are any." 428 | }, 429 | { 430 | "type": "account", 431 | "name": "reserve", 432 | "desc": "The account to receive the protocol revenue from the percentage of the accrued interest." 433 | }, 434 | { 435 | "type": "asset", 436 | "name": "asset", 437 | "desc": "The asset of the pool." 438 | }, 439 | { 440 | "type": "asset", 441 | "name": "f_asset", 442 | "desc": "The f asset of the pool." 443 | }, 444 | { 445 | "type": "asset", 446 | "name": "fr_asset", 447 | "desc": "The fr asset of the pool." 448 | }, 449 | { 450 | "type": "uint64", 451 | "name": "amount", 452 | "desc": "The amount to repay expressed in terms of the asset." 453 | }, 454 | { 455 | "type": "bool", 456 | "name": "is_stable", 457 | "desc": "Whether the borrow that is being repaid is a stable or variable rate borrow." 458 | }, 459 | { 460 | "type": "application", 461 | "name": "pool", 462 | "desc": "The pool to repay and collateral to use." 463 | }, 464 | { 465 | "type": "application", 466 | "name": "pool_manager", 467 | "desc": "The pool manager application of the pool." 468 | } 469 | ], 470 | "returns": { 471 | "type": "void" 472 | } 473 | }, 474 | { 475 | "name": "liquidate", 476 | "desc": "Liquidate a borrow and seize collateral from the loan escrow.", 477 | "args": [ 478 | { 479 | "type": "appl", 480 | "name": "refresh_prices", 481 | "desc": "The transaction to refresh the loan's asset prices." 482 | }, 483 | { 484 | "type": "txn", 485 | "name": "send_asset_txn", 486 | "desc": "The transaction to the pool to repay the borrow." 487 | }, 488 | { 489 | "type": "account", 490 | "name": "escrow", 491 | "desc": "The user's loan escrow." 492 | }, 493 | { 494 | "type": "account", 495 | "name": "reserve", 496 | "desc": "The account to receive the protocol revenue from the percentage of the accrued interest." 497 | }, 498 | { 499 | "type": "asset", 500 | "name": "asset", 501 | "desc": "The asset of the borrow pool." 502 | }, 503 | { 504 | "type": "asset", 505 | "name": "f_asset", 506 | "desc": "The f asset of the collateral pool." 507 | }, 508 | { 509 | "type": "uint64", 510 | "name": "min_col_amount", 511 | "desc": "The minimum collateral amount for the liquidator to receive." 512 | }, 513 | { 514 | "type": "bool", 515 | "name": "is_stable", 516 | "desc": "Whether the borrow that is being repaid is a stable or variable rate borrow." 517 | }, 518 | { 519 | "type": "application", 520 | "name": "col_pool", 521 | "desc": "The pool whose collateral is seized." 522 | }, 523 | { 524 | "type": "application", 525 | "name": "bor_pool", 526 | "desc": "The pool whose borrow is repaid." 527 | }, 528 | { 529 | "type": "application", 530 | "name": "pool_manager", 531 | "desc": "The pool manager application of the pool." 532 | }, 533 | { 534 | "type": "application", 535 | "name": "oracle_adapter", 536 | "desc": "The oracle adapter application of the loan." 537 | } 538 | ], 539 | "returns": { 540 | "type": "void" 541 | } 542 | }, 543 | { 544 | "name": "rebalance_up", 545 | "desc": "Increase the stable interest rate of a borrow through rebalancing", 546 | "args": [ 547 | { 548 | "type": "account", 549 | "name": "escrow", 550 | "desc": "The loan escrow whose borrow to rebalance up." 551 | }, 552 | { 553 | "type": "asset", 554 | "name": "asset", 555 | "desc": "The asset of the pool." 556 | }, 557 | { 558 | "type": "application", 559 | "name": "pool", 560 | "desc": "The pool whose borrow to rebalance up." 561 | }, 562 | { 563 | "type": "application", 564 | "name": "pool_manager", 565 | "desc": "The pool manager application of the pool." 566 | } 567 | ], 568 | "returns": { 569 | "type": "void" 570 | } 571 | }, 572 | { 573 | "name": "rebalance_down", 574 | "desc": "Decrease the stable interest rate of a borrow through rebalancing", 575 | "args": [ 576 | { 577 | "type": "account", 578 | "name": "escrow", 579 | "desc": "The loan whose borrow to rebalance down escrow." 580 | }, 581 | { 582 | "type": "asset", 583 | "name": "asset", 584 | "desc": "The asset of the pool." 585 | }, 586 | { 587 | "type": "application", 588 | "name": "pool", 589 | "desc": "The pool whose borrow to rebalance down." 590 | }, 591 | { 592 | "type": "application", 593 | "name": "pool_manager", 594 | "desc": "The pool manager application of the pool." 595 | } 596 | ], 597 | "returns": { 598 | "type": "void" 599 | } 600 | }, 601 | { 602 | "name": "remove_loan", 603 | "desc": "Remove a loan escrow for a user and return its minimum balance.", 604 | "args": [ 605 | { 606 | "type": "account", 607 | "name": "escrow", 608 | "desc": "The user's loan escrow." 609 | } 610 | ], 611 | "returns": { 612 | "type": "void" 613 | } 614 | } 615 | ], 616 | "networks": {} 617 | } 618 | -------------------------------------------------------------------------------- /src/lend/abi-contracts/lp-token-oracle.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LP Token Oracle", 3 | "desc": "Contains the information necessary to calculate the price of an LP token.", 4 | "methods": [ 5 | { 6 | "name": "update_lp_tokens", 7 | "desc": "Update the given LP tokens supplies such that they are valid to calculate the corresponding LP asset price. The pools are excluded from method signature but must still be passed.", 8 | "args": [ 9 | { 10 | "type": "uint64[]", 11 | "name": "assets", 12 | "desc": "The asset ids to update." 13 | } 14 | ], 15 | "returns": { 16 | "type": "void" 17 | } 18 | } 19 | ], 20 | "networks": {} 21 | } 22 | -------------------------------------------------------------------------------- /src/lend/abi-contracts/oracle-adapter.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Oracle Adapter", 3 | "desc": "Calculates and exposes given asset prices.", 4 | "methods": [ 5 | { 6 | "name": "refresh_prices", 7 | "desc": "Refresh the prices of the given assets.", 8 | "args": [ 9 | { 10 | "type": "uint64[]", 11 | "name": "lp_assets", 12 | "desc": "The list of LP assets to refresh the prices of." 13 | }, 14 | { 15 | "type": "uint64[]", 16 | "name": "non_lp_assets", 17 | "desc": "The list of non-LP assets to refresh the prices of." 18 | }, 19 | { 20 | "type": "application", 21 | "name": "oracle_0", 22 | "desc": "The first oracle price source of the oracle adapter." 23 | }, 24 | { 25 | "type": "application", 26 | "name": "oracle_1", 27 | "desc": "The second price source of the oracle adapter." 28 | }, 29 | { 30 | "type": "application", 31 | "name": "lp_token_oracle", 32 | "desc": "The LP Token Oracle of the oracle adapter." 33 | } 34 | ], 35 | "returns": { 36 | "type": "void" 37 | } 38 | } 39 | ], 40 | "networks": {} 41 | } 42 | -------------------------------------------------------------------------------- /src/lend/abi-contracts/pool.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pool", 3 | "desc": "Responsible for managing the deposits and withdrawals of an asset and exposes functionality to facilitate borrowing.", 4 | "methods": [ 5 | { 6 | "name": "deposit", 7 | "desc": "Deposit the asset into the pool.", 8 | "args": [ 9 | { 10 | "type": "txn", 11 | "name": "send_asset_txn", 12 | "desc": "The transaction to send the asset to the pool." 13 | }, 14 | { 15 | "type": "account", 16 | "name": "receiver", 17 | "desc": "The account to receive the f asset from the pool." 18 | }, 19 | { 20 | "type": "asset", 21 | "name": "asset", 22 | "desc": "The asset of the pool." 23 | }, 24 | { 25 | "type": "asset", 26 | "name": "f_asset", 27 | "desc": "The f asset of the pool." 28 | }, 29 | { 30 | "type": "application", 31 | "name": "pool_manager", 32 | "desc": "The pool manager application of the pool." 33 | } 34 | ], 35 | "returns": { 36 | "type": "uint64", 37 | "desc": "The amount of f asset sent by pool to receiver." 38 | } 39 | }, 40 | { 41 | "name": "withdraw", 42 | "desc": "Withdraw the asset from the pool.", 43 | "args": [ 44 | { 45 | "type": "axfer", 46 | "name": "send_f_asset_txn", 47 | "desc": "The transaction to send the f asset to the pool." 48 | }, 49 | { 50 | "type": "uint64", 51 | "name": "received_amount", 52 | "desc": "The amount of asset to receive. Any excess f asset sent will be returned to the sender. If zero then variable." 53 | }, 54 | { 55 | "type": "account", 56 | "name": "receiver", 57 | "desc": "The account to receive the asset from the pool." 58 | }, 59 | { 60 | "type": "asset", 61 | "name": "asset", 62 | "desc": "The asset of the pool." 63 | }, 64 | { 65 | "type": "asset", 66 | "name": "f_asset", 67 | "desc": "The f asset of the pool." 68 | }, 69 | { 70 | "type": "application", 71 | "name": "pool_manager", 72 | "desc": "The pool manager application of the pool." 73 | } 74 | ], 75 | "returns": { 76 | "type": "uint64", 77 | "desc": "The amount of asset sent by the pool to the receiver." 78 | } 79 | }, 80 | { 81 | "name": "update_pool_interest_indexes", 82 | "desc": "Update the pool interest indexes.", 83 | "args": [ 84 | { 85 | "type": "application", 86 | "name": "pool_manager", 87 | "desc": "The pool manager application of the pool." 88 | } 89 | ], 90 | "returns": { 91 | "type": "void" 92 | } 93 | }, 94 | { 95 | "name": "flash_loan_begin", 96 | "desc": "Request a flash loan of the asset. Must be grouped together with flash_loan_end method call.", 97 | "args": [ 98 | { 99 | "type": "uint64", 100 | "name": "amount", 101 | "desc": "The amount of the asset to borrow." 102 | }, 103 | { 104 | "type": "uint64", 105 | "name": "txn_index", 106 | "desc": "The transaction index in the group transaction for the flash_loan_end method call." 107 | }, 108 | { 109 | "type": "account", 110 | "name": "receiver", 111 | "desc": "The account to receive the asset from the pool." 112 | }, 113 | { 114 | "type": "asset", 115 | "name": "asset", 116 | "desc": "The asset of the pool." 117 | } 118 | ], 119 | "returns": { 120 | "type": "void" 121 | } 122 | }, 123 | { 124 | "name": "flash_loan_end", 125 | "desc": "Repay a requested flash loan. Must be grouped together with flash_loan_begin method call.", 126 | "args": [ 127 | { 128 | "type": "txn", 129 | "name": "send_asset_txn", 130 | "desc": "The transaction to the asset to the pool." 131 | }, 132 | { 133 | "type": "account", 134 | "name": "pool_admin", 135 | "desc": "The pool admin address that will receive the flash loan fee." 136 | }, 137 | { 138 | "type": "asset", 139 | "name": "asset", 140 | "desc": "The asset of the pool." 141 | } 142 | ], 143 | "returns": { 144 | "type": "void" 145 | } 146 | } 147 | ], 148 | "networks": {} 149 | } 150 | -------------------------------------------------------------------------------- /src/lend/amm.ts: -------------------------------------------------------------------------------- 1 | import { compoundEveryHour, ONE_12_DP, ONE_16_DP } from "../math-lib"; 2 | import { 3 | getAccountApplicationLocalState, 4 | getApplicationGlobalState, 5 | getParsedValueFromState, 6 | parseUint64s, 7 | } from "../utils"; 8 | 9 | import type { 10 | AssetsAdditionalInterest, 11 | LendingPool, 12 | PactLendingPool, 13 | PactLendingPoolInfo, 14 | PoolManagerInfo, 15 | TinymanLendingPool, 16 | TinymanLendingPoolInfo, 17 | } from "./types"; 18 | import type { Algodv2, Indexer } from "algosdk"; 19 | 20 | /** 21 | * 22 | * Returns information regarding the given Pact lending pool. 23 | * 24 | * @param client - Algorand client to query 25 | * @param lendingPool - Pact lending pool to query about 26 | * @param poolManagerInfo - pool manager info which is returned by retrievePoolManagerInfo function 27 | * @param additionalInterests - optional additional interest to consider 28 | * @returns Promise lending pool info 29 | */ 30 | async function retrievePactLendingPoolInfo( 31 | client: Algodv2 | Indexer, 32 | lendingPool: PactLendingPool, 33 | poolManagerInfo: PoolManagerInfo, 34 | additionalInterests?: AssetsAdditionalInterest, 35 | ): Promise { 36 | const { currentRound, globalState: state } = await getApplicationGlobalState(client, lendingPool.lpPoolAppId); 37 | if (state === undefined) throw Error("Could not find lending pool"); 38 | const config = parseUint64s(String(getParsedValueFromState(state, "CONFIG"))); 39 | const fa0s = BigInt(getParsedValueFromState(state, "A") || 0); 40 | const fa1s = BigInt(getParsedValueFromState(state, "B") || 0); 41 | const ltcs = BigInt(getParsedValueFromState(state, "L") || 0); 42 | 43 | // pact pool swap fee interest 44 | const lpInfoRes = await fetch(`https://api.pact.fi/api/pools/${lendingPool.lpPoolAppId}`); 45 | if (!lpInfoRes.ok || lpInfoRes.status !== 200) throw Error("Failed to fetch pact swap fee from api"); 46 | const pactPoolData = await lpInfoRes.json(); 47 | const swapFeeInterestRate = BigInt(Math.round(Number(pactPoolData?.["apr_7d"] || 0) * 1e16)); 48 | const tvlUsd = Number(pactPoolData?.["tvl_usd"] || 0); 49 | 50 | // lending pool deposit interest and additional interest 51 | const commonLendingPoolInterest = getDepositAndAdditionalInterest(lendingPool, poolManagerInfo, additionalInterests); 52 | 53 | return { 54 | ...commonLendingPoolInterest, 55 | currentRound, 56 | fAsset0Supply: fa0s, 57 | fAsset1Supply: fa1s, 58 | liquidityTokenCirculatingSupply: ltcs, 59 | fee: config[2], 60 | swapFeeInterestRate, 61 | swapFeeInterestYield: compoundEveryHour(swapFeeInterestRate, ONE_16_DP), 62 | tvlUsd, 63 | }; 64 | } 65 | 66 | /** 67 | * 68 | * Returns information regarding the given Tinyman lending pool. 69 | * 70 | * @param client - Algorand client to query 71 | * @param tinymanAppId - Tinyman application id where lending pool belongs to 72 | * @param lendingPool - Pact lending pool to query about 73 | * @param poolManagerInfo - pool manager info which is returned by retrievePoolManagerInfo function 74 | * @param additionalInterests - optional additional interest to consider 75 | * @returns Promise lending pool info 76 | */ 77 | async function retrieveTinymanLendingPoolInfo( 78 | client: Algodv2 | Indexer, 79 | tinymanAppId: number, 80 | lendingPool: TinymanLendingPool, 81 | poolManagerInfo: PoolManagerInfo, 82 | additionalInterests?: AssetsAdditionalInterest, 83 | ): Promise { 84 | const { currentRound, localState: state } = await getAccountApplicationLocalState( 85 | client, 86 | tinymanAppId, 87 | lendingPool.lpPoolAppAddress, 88 | ); 89 | if (state === undefined) throw Error("Could not find lending pool"); 90 | const fee = BigInt(getParsedValueFromState(state, "total_fee_share") || 0); 91 | const fa0s = BigInt(getParsedValueFromState(state, "asset_2_reserves") || 0); 92 | const fa1s = BigInt(getParsedValueFromState(state, "asset_1_reserves") || 0); 93 | const ltcs = BigInt(getParsedValueFromState(state, "issued_pool_tokens") || 0); 94 | 95 | // pact pool swap fee interest 96 | const res = await fetch(`https://mainnet.analytics.tinyman.org/api/v1/pools/${lendingPool.lpPoolAppAddress}`); 97 | if (!res.ok || res.status !== 200) throw Error("Failed to fetch tinyman swap fee from api"); 98 | const tmPoolData = await res.json(); 99 | 100 | const swapFeeInterestRate = BigInt(Math.round(Number(tmPoolData?.["annual_percentage_rate"] || 0) * 1e16)); 101 | const swapFeeInterestYield = BigInt(Math.round(Number(tmPoolData?.["annual_percentage_yield"] || 0) * 1e16)); 102 | const farmInterestYield = BigInt( 103 | Math.round(Number(tmPoolData?.["staking_total_annual_percentage_yield"] || 0) * 1e16), 104 | ); 105 | const tvlUsd = Number(tmPoolData?.["liquidity_in_usd"] || 0); 106 | 107 | // lending pool deposit interest and additional interest 108 | const commonLendingPoolInterest = getDepositAndAdditionalInterest(lendingPool, poolManagerInfo, additionalInterests); 109 | 110 | return { 111 | ...commonLendingPoolInterest, 112 | currentRound, 113 | fAsset0Supply: fa0s, 114 | fAsset1Supply: fa1s, 115 | liquidityTokenCirculatingSupply: ltcs, 116 | fee, 117 | swapFeeInterestRate, 118 | swapFeeInterestYield, 119 | farmInterestYield, 120 | tvlUsd, 121 | }; 122 | } 123 | 124 | function getDepositAndAdditionalInterest( 125 | lendingPool: LendingPool, 126 | poolManagerInfo: PoolManagerInfo, 127 | additionalInterests?: AssetsAdditionalInterest, 128 | ) { 129 | const { asset0Id, asset1Id, pool0AppId, pool1AppId } = lendingPool; 130 | 131 | // lending pool deposit interest 132 | const pool0 = poolManagerInfo.pools[pool0AppId]; 133 | const pool1 = poolManagerInfo.pools[pool1AppId]; 134 | if (pool0 === undefined || pool1 === undefined) throw Error("Could not find deposit pool"); 135 | const asset0DepositInterestRate = pool0.depositInterestRate / BigInt(2); 136 | const asset0DepositInterestYield = pool0.depositInterestYield / BigInt(2); 137 | const asset1DepositInterestRate = pool1.depositInterestRate / BigInt(2); 138 | const asset1DepositInterestYield = pool1.depositInterestYield / BigInt(2); 139 | 140 | // add additional interests if specified 141 | let additionalInterestRate; 142 | let additionalInterestYield; 143 | if (additionalInterests) { 144 | for (const assetId of [asset0Id, asset1Id]) { 145 | if (additionalInterests[assetId]) { 146 | const { rateBps, yieldBps } = additionalInterests[assetId]; 147 | // multiply by 1e12 to standardise at 16 d.p. 148 | additionalInterestRate = (additionalInterestRate || BigInt(0)) + (rateBps * ONE_12_DP) / BigInt(2); 149 | additionalInterestYield = (additionalInterestYield || BigInt(0)) + (yieldBps * ONE_12_DP) / BigInt(2); 150 | } 151 | } 152 | } 153 | 154 | return { 155 | asset0DepositInterestRate, 156 | asset0DepositInterestYield, 157 | asset1DepositInterestRate, 158 | asset1DepositInterestYield, 159 | additionalInterestRate, 160 | additionalInterestYield, 161 | }; 162 | } 163 | 164 | export { retrievePactLendingPoolInfo, retrieveTinymanLendingPoolInfo }; 165 | -------------------------------------------------------------------------------- /src/lend/constants/mainnet-constants.ts: -------------------------------------------------------------------------------- 1 | import { ONE_4_DP } from "../../math-lib"; 2 | import { LoanType, LPTokenProvider } from "../types"; 3 | 4 | import type { LendingPool, OpUp, Oracle, Pool, ReserveAddress } from "../types"; 5 | 6 | const MainnetPoolManagerAppId = 971350278; 7 | 8 | const MainnetDepositsAppId = 971353536; 9 | 10 | const MainnetDepositStakingAppId = 1093729103; 11 | 12 | type MainnetPoolKey = 13 | | "ALGO" 14 | | "gALGO" 15 | | "xALGO" 16 | | "USDC" 17 | | "USDt" 18 | | "GARD" 19 | | "EURS" 20 | | "goBTC" 21 | | "goETH" 22 | | "WBTC" 23 | | "WETH" 24 | | "WAVAX" 25 | | "WSOL" 26 | | "WLINK" 27 | | "GOLD" 28 | | "SILVER" 29 | | "OPUL" 30 | | "WMPL"; 31 | const MainnetPools: Record = { 32 | ALGO: { 33 | appId: 971368268, 34 | assetId: 0, 35 | fAssetId: 971381860, 36 | frAssetId: 971381861, 37 | assetDecimals: 6, 38 | poolManagerIndex: 0, 39 | loans: { 40 | 971388781: BigInt(0), 41 | 971389489: BigInt(0), 42 | 1202382736: BigInt(1), 43 | 1202382829: BigInt(1), 44 | }, 45 | }, 46 | gALGO: { 47 | appId: 971370097, 48 | assetId: 793124631, 49 | fAssetId: 971383839, 50 | frAssetId: 971383840, 51 | assetDecimals: 6, 52 | poolManagerIndex: 1, 53 | loans: { 54 | 971388781: BigInt(1), 55 | 971389489: BigInt(1), 56 | }, 57 | }, 58 | xALGO: { 59 | appId: 2611131944, 60 | assetId: 1134696561, 61 | fAssetId: 2611138444, 62 | frAssetId: 2611138445, 63 | assetDecimals: 6, 64 | poolManagerIndex: 17, 65 | loans: { 66 | 971388781: BigInt(17), 67 | 971389489: BigInt(2), 68 | }, 69 | }, 70 | USDC: { 71 | appId: 971372237, 72 | assetId: 31566704, 73 | fAssetId: 971384592, 74 | frAssetId: 971384593, 75 | assetDecimals: 6, 76 | poolManagerIndex: 2, 77 | loans: { 78 | 971388781: BigInt(2), 79 | 971388977: BigInt(0), 80 | 1202382736: BigInt(0), 81 | 1202382829: BigInt(0), 82 | }, 83 | }, 84 | USDt: { 85 | appId: 971372700, 86 | assetId: 312769, 87 | fAssetId: 971385312, 88 | frAssetId: 971385313, 89 | assetDecimals: 6, 90 | poolManagerIndex: 3, 91 | loans: { 92 | 971388781: BigInt(3), 93 | 971388977: BigInt(1), 94 | }, 95 | }, 96 | GARD: { 97 | appId: 1060585819, 98 | assetId: 684649988, 99 | fAssetId: 1060587336, 100 | frAssetId: 1060587337, 101 | assetDecimals: 6, 102 | poolManagerIndex: 7, 103 | loans: { 104 | 971388781: BigInt(7), 105 | 971388977: BigInt(2), 106 | }, 107 | }, 108 | EURS: { 109 | appId: 1247053569, 110 | assetId: 227855942, 111 | fAssetId: 1247054501, 112 | frAssetId: 1247054502, 113 | assetDecimals: 6, 114 | poolManagerIndex: 14, 115 | loans: { 116 | 971388781: BigInt(14), 117 | 971388977: BigInt(3), 118 | }, 119 | }, 120 | goBTC: { 121 | appId: 971373361, 122 | assetId: 386192725, 123 | fAssetId: 971386173, 124 | frAssetId: 971386174, 125 | assetDecimals: 8, 126 | poolManagerIndex: 4, 127 | loans: { 128 | 971388781: BigInt(4), 129 | 1202382736: BigInt(2), 130 | 1202382829: BigInt(2), 131 | }, 132 | }, 133 | goETH: { 134 | appId: 971373611, 135 | assetId: 386195940, 136 | fAssetId: 971387073, 137 | frAssetId: 971387074, 138 | assetDecimals: 8, 139 | poolManagerIndex: 5, 140 | loans: { 141 | 971388781: BigInt(5), 142 | 1202382736: BigInt(3), 143 | 1202382829: BigInt(3), 144 | }, 145 | }, 146 | WBTC: { 147 | appId: 1067289273, 148 | assetId: 1058926737, 149 | fAssetId: 1067295154, 150 | frAssetId: 1067295155, 151 | assetDecimals: 8, 152 | poolManagerIndex: 8, 153 | loans: { 154 | 971388781: BigInt(8), 155 | 1202382736: BigInt(4), 156 | 1202382829: BigInt(4), 157 | }, 158 | }, 159 | WETH: { 160 | appId: 1067289481, 161 | assetId: 887406851, 162 | fAssetId: 1067295558, 163 | frAssetId: 1067295559, 164 | assetDecimals: 8, 165 | poolManagerIndex: 9, 166 | loans: { 167 | 971388781: BigInt(9), 168 | 1202382736: BigInt(5), 169 | 1202382829: BigInt(5), 170 | }, 171 | }, 172 | WAVAX: { 173 | appId: 1166977433, 174 | assetId: 893309613, 175 | fAssetId: 1166979636, 176 | frAssetId: 1166979637, 177 | assetDecimals: 8, 178 | poolManagerIndex: 10, 179 | loans: { 180 | 971388781: BigInt(10), 181 | }, 182 | }, 183 | WSOL: { 184 | appId: 1166980669, 185 | assetId: 887648583, 186 | fAssetId: 1166980820, 187 | frAssetId: 1166980821, 188 | assetDecimals: 8, 189 | poolManagerIndex: 11, 190 | loans: { 191 | 971388781: BigInt(11), 192 | }, 193 | }, 194 | WLINK: { 195 | appId: 1216434571, 196 | assetId: 1200094857, 197 | fAssetId: 1216437148, 198 | frAssetId: 1216437149, 199 | assetDecimals: 8, 200 | poolManagerIndex: 13, 201 | loans: { 202 | 971388781: BigInt(13), 203 | }, 204 | }, 205 | GOLD: { 206 | appId: 1258515734, 207 | assetId: 246516580, 208 | fAssetId: 1258524377, 209 | frAssetId: 1258524378, 210 | assetDecimals: 6, 211 | poolManagerIndex: 15, 212 | loans: { 213 | 971388781: BigInt(15), 214 | }, 215 | }, 216 | SILVER: { 217 | appId: 1258524099, 218 | assetId: 246519683, 219 | fAssetId: 1258524381, 220 | frAssetId: 1258524382, 221 | assetDecimals: 6, 222 | poolManagerIndex: 16, 223 | loans: { 224 | 971388781: BigInt(16), 225 | }, 226 | }, 227 | OPUL: { 228 | appId: 1044267181, 229 | assetId: 287867876, 230 | fAssetId: 1044269355, 231 | frAssetId: 1044269356, 232 | assetDecimals: 10, 233 | poolManagerIndex: 6, 234 | loans: { 235 | 971388781: BigInt(6), 236 | }, 237 | }, 238 | WMPL: { 239 | appId: 1166982094, 240 | assetId: 1163259470, 241 | fAssetId: 1166982296, 242 | frAssetId: 1166982297, 243 | assetDecimals: 8, 244 | poolManagerIndex: 12, 245 | loans: { 246 | 971388781: BigInt(12), 247 | }, 248 | }, 249 | }; 250 | 251 | const MainnetLoans: Partial> = { 252 | [LoanType.GENERAL]: 971388781, 253 | [LoanType.STABLECOIN_EFFICIENCY]: 971388977, 254 | [LoanType.ALGO_EFFICIENCY]: 971389489, 255 | [LoanType.ULTRASWAP_UP]: 1202382736, 256 | [LoanType.ULTRASWAP_DOWN]: 1202382829, 257 | }; 258 | 259 | const MainnetTinymanAppId = 1002541853; 260 | 261 | type MainnetLendingPoolKey = 262 | | "ALGOgALGOPLP" 263 | | "ALGOgALGOTM" 264 | | "ALGOUSDCPLP" 265 | | "ALGOUSDCTM" 266 | | "ALGOEURSPLP" 267 | | "ALGOgoBTCPLP" 268 | | "ALGOgoBTCTM" 269 | | "ALGOgoETHPLP" 270 | | "ALGOgoETHTM" 271 | | "ALGOwBTCPLP" 272 | | "ALGOwBTCTM" 273 | | "ALGOwETHPLP" 274 | | "ALGOwETHTM" 275 | | "ALGOwAVAXPLP" 276 | | "ALGOwSOLPLP" 277 | | "ALGOwLINKPLP" 278 | | "ALGOGOLDPLP" 279 | | "ALGOGOLDTM" 280 | | "ALGOSILVERPLP" 281 | | "ALGOSILVERTM" 282 | | "ALGOwMPLPLP" 283 | | "gALGOUSDCPLP" 284 | | "gALGOUSDCTM" 285 | | "xALGOUSDCPLP" 286 | | "xALGOUSDCTM" 287 | | "USDCUSDtPLP" 288 | | "USDCUSDtTM" 289 | | "USDCEURSPLP" 290 | | "USDCEURSTM" 291 | | "USDCwBTCTM" 292 | | "USDCwETHTM" 293 | | "USDCwAVAXTM" 294 | | "USDCwLINKTM" 295 | | "USDCwSOLTM" 296 | | "USDCGOLDPLP" 297 | | "USDCSILVERPLP"; 298 | const MainnetLendingPools: Record = { 299 | ALGOgALGOPLP: { 300 | provider: LPTokenProvider.PACT, 301 | lpPoolAppId: 1116366345, 302 | lpAssetId: 1116366351, 303 | pool0AppId: 971368268, 304 | pool1AppId: 971370097, 305 | asset0Id: 0, 306 | asset1Id: 793124631, 307 | feeScale: ONE_4_DP, 308 | }, 309 | ALGOgALGOTM: { 310 | provider: LPTokenProvider.TINYMAN, 311 | lpPoolAppAddress: "R5Y6PRR2NEOS27HB2HGQFUMKUUMPXAYUBU4BHDXY4TCEYNSWGPOKGCV66Q", 312 | lpAssetId: 1332971358, 313 | pool0AppId: 971368268, 314 | pool1AppId: 971370097, 315 | asset0Id: 0, 316 | asset1Id: 793124631, 317 | feeScale: ONE_4_DP, 318 | }, 319 | ALGOUSDCPLP: { 320 | provider: LPTokenProvider.PACT, 321 | lpPoolAppId: 1116363704, 322 | lpAssetId: 1116363710, 323 | pool0AppId: 971368268, 324 | pool1AppId: 971372237, 325 | asset0Id: 0, 326 | asset1Id: 31566704, 327 | feeScale: ONE_4_DP, 328 | }, 329 | ALGOUSDCTM: { 330 | provider: LPTokenProvider.TINYMAN, 331 | lpPoolAppAddress: "ZA42RCTLUMWUUB6SXEUNTI72LVSGV3TJIUTCGLNQF3KNOXFBMEPXNST3MA", 332 | lpAssetId: 1256805381, 333 | pool0AppId: 971368268, 334 | pool1AppId: 971372237, 335 | asset0Id: 0, 336 | asset1Id: 31566704, 337 | feeScale: ONE_4_DP, 338 | }, 339 | ALGOEURSPLP: { 340 | provider: LPTokenProvider.PACT, 341 | lpPoolAppId: 1247810099, 342 | lpAssetId: 1247810105, 343 | pool0AppId: 971368268, 344 | pool1AppId: 1247053569, 345 | asset0Id: 0, 346 | asset1Id: 227855942, 347 | feeScale: ONE_4_DP, 348 | }, 349 | ALGOgoBTCPLP: { 350 | provider: LPTokenProvider.PACT, 351 | lpPoolAppId: 2161677283, 352 | lpAssetId: 2161677289, 353 | pool0AppId: 971368268, 354 | pool1AppId: 971373361, 355 | asset0Id: 0, 356 | asset1Id: 386192725, 357 | feeScale: ONE_4_DP, 358 | }, 359 | ALGOgoETHPLP: { 360 | provider: LPTokenProvider.PACT, 361 | lpPoolAppId: 2161681928, 362 | lpAssetId: 2161681934, 363 | pool0AppId: 971368268, 364 | pool1AppId: 971373611, 365 | asset0Id: 0, 366 | asset1Id: 386195940, 367 | feeScale: ONE_4_DP, 368 | }, 369 | ALGOgoBTCTM: { 370 | provider: LPTokenProvider.TINYMAN, 371 | lpPoolAppAddress: "RB6SQMZINE5SEEYH6PZNSOGEUTC6BSYTOJ2YGEAHSE6MOPCLEUTCA6MNLM", 372 | lpAssetId: 2169397535, 373 | pool0AppId: 971368268, 374 | pool1AppId: 971373361, 375 | asset0Id: 0, 376 | asset1Id: 386192725, 377 | feeScale: ONE_4_DP, 378 | }, 379 | ALGOgoETHTM: { 380 | provider: LPTokenProvider.TINYMAN, 381 | lpPoolAppAddress: "DU4NAE2N6FQLTYFRURJVTOC7Y7GOTZO4C7BQHHVJ2B5NZU6EDZLK7G6HKU", 382 | lpAssetId: 2169399904, 383 | pool0AppId: 971368268, 384 | pool1AppId: 971373611, 385 | asset0Id: 0, 386 | asset1Id: 386195940, 387 | feeScale: ONE_4_DP, 388 | }, 389 | ALGOwBTCPLP: { 390 | provider: LPTokenProvider.PACT, 391 | lpPoolAppId: 1116367260, 392 | lpAssetId: 1116367266, 393 | pool0AppId: 971368268, 394 | pool1AppId: 1067289273, 395 | asset0Id: 0, 396 | asset1Id: 1058926737, 397 | feeScale: ONE_4_DP, 398 | }, 399 | ALGOwBTCTM: { 400 | provider: LPTokenProvider.TINYMAN, 401 | lpPoolAppAddress: "IVKGUV5LF7BKJ5CAX6YXYF67743FZEZ2Z5ZFGHQQ5ZF7YJVCGAT2MQJ46Y", 402 | lpAssetId: 1385309142, 403 | pool0AppId: 971368268, 404 | pool1AppId: 1067289273, 405 | asset0Id: 0, 406 | asset1Id: 1058926737, 407 | feeScale: ONE_4_DP, 408 | }, 409 | ALGOwETHPLP: { 410 | provider: LPTokenProvider.PACT, 411 | lpPoolAppId: 1116369904, 412 | lpAssetId: 1116369910, 413 | pool0AppId: 971368268, 414 | pool1AppId: 1067289481, 415 | asset0Id: 0, 416 | asset1Id: 887406851, 417 | feeScale: ONE_4_DP, 418 | }, 419 | ALGOwETHTM: { 420 | provider: LPTokenProvider.TINYMAN, 421 | lpPoolAppAddress: "QHFMCKBXVLZCAXCZV36WCQL6GTVK6AGCP4ZI5GYGM7S7FUPWECBFYPNHCE", 422 | lpAssetId: 1385320489, 423 | pool0AppId: 971368268, 424 | pool1AppId: 1067289481, 425 | asset0Id: 0, 426 | asset1Id: 887406851, 427 | feeScale: ONE_4_DP, 428 | }, 429 | ALGOwAVAXPLP: { 430 | provider: LPTokenProvider.PACT, 431 | lpPoolAppId: 1168319565, 432 | lpAssetId: 1168319571, 433 | pool0AppId: 971368268, 434 | pool1AppId: 1166977433, 435 | asset0Id: 0, 436 | asset1Id: 893309613, 437 | feeScale: ONE_4_DP, 438 | }, 439 | ALGOwSOLPLP: { 440 | provider: LPTokenProvider.PACT, 441 | lpPoolAppId: 1168322128, 442 | lpAssetId: 1168322134, 443 | pool0AppId: 971368268, 444 | pool1AppId: 1166980669, 445 | asset0Id: 0, 446 | asset1Id: 887648583, 447 | feeScale: ONE_4_DP, 448 | }, 449 | ALGOwLINKPLP: { 450 | provider: LPTokenProvider.PACT, 451 | lpPoolAppId: 1217112826, 452 | lpAssetId: 1217112832, 453 | pool0AppId: 971368268, 454 | pool1AppId: 1216434571, 455 | asset0Id: 0, 456 | asset1Id: 1200094857, 457 | feeScale: ONE_4_DP, 458 | }, 459 | ALGOGOLDPLP: { 460 | provider: LPTokenProvider.PACT, 461 | lpPoolAppId: 1258807438, 462 | lpAssetId: 1258807444, 463 | pool0AppId: 971368268, 464 | pool1AppId: 1258515734, 465 | asset0Id: 0, 466 | asset1Id: 246516580, 467 | feeScale: ONE_4_DP, 468 | }, 469 | ALGOGOLDTM: { 470 | provider: LPTokenProvider.TINYMAN, 471 | lpPoolAppAddress: "N44TFF4OLWLUTJS3ZA67LODV2DQR3ZBTEXKGAQ6ZIWXBDLN7J4KBLIXNIQ", 472 | lpAssetId: 2169404223, 473 | pool0AppId: 971368268, 474 | pool1AppId: 1258515734, 475 | asset0Id: 0, 476 | asset1Id: 246516580, 477 | feeScale: ONE_4_DP, 478 | }, 479 | ALGOSILVERPLP: { 480 | provider: LPTokenProvider.PACT, 481 | lpPoolAppId: 1258808812, 482 | lpAssetId: 1258808818, 483 | pool0AppId: 971368268, 484 | pool1AppId: 1258524099, 485 | asset0Id: 0, 486 | asset1Id: 246519683, 487 | feeScale: ONE_4_DP, 488 | }, 489 | ALGOSILVERTM: { 490 | provider: LPTokenProvider.TINYMAN, 491 | lpPoolAppAddress: "4LJMARM7FXLYLOUERA74QHUA4SIX2YMCTHGXBW7A75BOVGV6RXJYZTQSH4", 492 | lpAssetId: 2169402187, 493 | pool0AppId: 971368268, 494 | pool1AppId: 1258524099, 495 | asset0Id: 0, 496 | asset1Id: 246519683, 497 | feeScale: ONE_4_DP, 498 | }, 499 | ALGOwMPLPLP: { 500 | provider: LPTokenProvider.PACT, 501 | lpPoolAppId: 1168322907, 502 | lpAssetId: 1168322913, 503 | pool0AppId: 971368268, 504 | pool1AppId: 1166982094, 505 | asset0Id: 0, 506 | asset1Id: 1163259470, 507 | feeScale: ONE_4_DP, 508 | }, 509 | gALGOUSDCPLP: { 510 | provider: LPTokenProvider.PACT, 511 | lpPoolAppId: 1736210826, 512 | lpAssetId: 1736210832, 513 | pool0AppId: 971370097, 514 | pool1AppId: 971372237, 515 | asset0Id: 793124631, 516 | asset1Id: 31566704, 517 | feeScale: ONE_4_DP, 518 | }, 519 | gALGOUSDCTM: { 520 | provider: LPTokenProvider.TINYMAN, 521 | lpPoolAppAddress: "XGD2PZVQLKRG5GFRSA2WG7VCHQYI7ATCN5ZF46UH2EHYBYUVXSMNFZNJYQ", 522 | lpAssetId: 1734417671, 523 | pool0AppId: 971370097, 524 | pool1AppId: 971372237, 525 | asset0Id: 793124631, 526 | asset1Id: 31566704, 527 | feeScale: ONE_4_DP, 528 | }, 529 | xALGOUSDCPLP: { 530 | provider: LPTokenProvider.PACT, 531 | lpPoolAppId: 2649980181, 532 | lpAssetId: 2649980187, 533 | pool0AppId: 2611131944, 534 | pool1AppId: 971372237, 535 | asset0Id: 1134696561, 536 | asset1Id: 31566704, 537 | feeScale: ONE_4_DP, 538 | }, 539 | xALGOUSDCTM: { 540 | provider: LPTokenProvider.TINYMAN, 541 | lpPoolAppAddress: "EHYUE2VAI22PB5DID4EHUD5YH7UIOE2NG5XPQZ7NZPIHFUQ334C7J5OSTY", 542 | lpAssetId: 2649938842, 543 | pool0AppId: 2611131944, 544 | pool1AppId: 971372237, 545 | asset0Id: 1134696561, 546 | asset1Id: 31566704, 547 | feeScale: ONE_4_DP, 548 | }, 549 | USDCUSDtPLP: { 550 | provider: LPTokenProvider.PACT, 551 | lpPoolAppId: 1116364721, 552 | lpAssetId: 1116364727, 553 | pool0AppId: 971372237, 554 | pool1AppId: 971372700, 555 | asset0Id: 31566704, 556 | asset1Id: 312769, 557 | feeScale: ONE_4_DP, 558 | }, 559 | USDCUSDtTM: { 560 | provider: LPTokenProvider.TINYMAN, 561 | lpPoolAppAddress: "JADZYEIDHPHZAFSD45M7GOQSAEMETOMTUSHPHLXIMYJPSLKPADF52Y245I", 562 | lpAssetId: 1332995647, 563 | pool0AppId: 971372237, 564 | pool1AppId: 971372700, 565 | asset0Id: 31566704, 566 | asset1Id: 312769, 567 | feeScale: ONE_4_DP, 568 | }, 569 | USDCEURSPLP: { 570 | provider: LPTokenProvider.PACT, 571 | lpPoolAppId: 1247811167, 572 | lpAssetId: 1247811173, 573 | pool0AppId: 971372237, 574 | pool1AppId: 1247053569, 575 | asset0Id: 31566704, 576 | asset1Id: 227855942, 577 | feeScale: ONE_4_DP, 578 | }, 579 | USDCEURSTM: { 580 | provider: LPTokenProvider.TINYMAN, 581 | lpPoolAppAddress: "MSQ46LA7UBKA2JPG5MAMSKWJ5FO35PSKIFGTCKJ6YSL5NLZMZH4HAXMPKU", 582 | lpAssetId: 1394310065, 583 | pool0AppId: 971372237, 584 | pool1AppId: 1247053569, 585 | asset0Id: 31566704, 586 | asset1Id: 227855942, 587 | feeScale: ONE_4_DP, 588 | }, 589 | USDCwBTCTM: { 590 | provider: LPTokenProvider.TINYMAN, 591 | lpPoolAppAddress: "IT7H47FAYPVWHHYF23KIPNFDIOF36MIUI2T54R47WCX6MLMTVVQZ3UK5GI", 592 | lpAssetId: 1394237139, 593 | pool0AppId: 971372237, 594 | pool1AppId: 1067289273, 595 | asset0Id: 31566704, 596 | asset1Id: 1058926737, 597 | feeScale: ONE_4_DP, 598 | }, 599 | USDCwETHTM: { 600 | provider: LPTokenProvider.TINYMAN, 601 | lpPoolAppAddress: "KPG7LTBPFTEZYXBT47TAR56X6QC5BFCEFAATXJH2CRHSYAGE66X5FLQKEY", 602 | lpAssetId: 1734424720, 603 | pool0AppId: 971372237, 604 | pool1AppId: 1067289481, 605 | asset0Id: 31566704, 606 | asset1Id: 887406851, 607 | feeScale: ONE_4_DP, 608 | }, 609 | USDCwAVAXTM: { 610 | provider: LPTokenProvider.TINYMAN, 611 | lpPoolAppAddress: "MJN7SKRYHIJ3I5BIO3U2LVS44FAMHBWR3LDEMVYA47BPQJ4HIBXIWKEF4M", 612 | lpAssetId: 1734427105, 613 | pool0AppId: 971372237, 614 | pool1AppId: 1166977433, 615 | asset0Id: 31566704, 616 | asset1Id: 893309613, 617 | feeScale: ONE_4_DP, 618 | }, 619 | USDCwSOLTM: { 620 | provider: LPTokenProvider.TINYMAN, 621 | lpPoolAppAddress: "U6XAS3L5KRCOD32OI3LEDRVCMHN42E5YCZBFGJTGOWCE35LFVPS4QKE5W4", 622 | lpAssetId: 1734429522, 623 | pool0AppId: 971372237, 624 | pool1AppId: 1166980669, 625 | asset0Id: 31566704, 626 | asset1Id: 887648583, 627 | feeScale: ONE_4_DP, 628 | }, 629 | USDCwLINKTM: { 630 | provider: LPTokenProvider.TINYMAN, 631 | lpPoolAppAddress: "ULHYOPZ5DQJM3QYQ3RXWRWSGDGJ4WEBNGSFCFYI3PRXSINAL7GHV4DZO5Q", 632 | lpAssetId: 1734433423, 633 | pool0AppId: 971372237, 634 | pool1AppId: 1216434571, 635 | asset0Id: 31566704, 636 | asset1Id: 1200094857, 637 | feeScale: ONE_4_DP, 638 | }, 639 | USDCGOLDPLP: { 640 | provider: LPTokenProvider.PACT, 641 | lpPoolAppId: 1736289878, 642 | lpAssetId: 1736289884, 643 | pool0AppId: 971372237, 644 | pool1AppId: 1258515734, 645 | asset0Id: 31566704, 646 | asset1Id: 246516580, 647 | feeScale: ONE_4_DP, 648 | }, 649 | USDCSILVERPLP: { 650 | provider: LPTokenProvider.PACT, 651 | lpPoolAppId: 1736326581, 652 | lpAssetId: 1736326587, 653 | pool0AppId: 971372237, 654 | pool1AppId: 1258524099, 655 | asset0Id: 31566704, 656 | asset1Id: 246519683, 657 | feeScale: ONE_4_DP, 658 | }, 659 | }; 660 | 661 | const MainnetReserveAddress: ReserveAddress = "Q5Q5FC5PTYQIUX5PGNTEW22UJHJHVVUEMMWV2LSG6MGT33YQ54ST7FEIGA"; 662 | 663 | const MainnetOracle: Oracle = { 664 | oracle0AppId: 1040271396, 665 | oracle1AppId: 971323141, 666 | oracleAdapterAppId: 971333964, 667 | decimals: 14, 668 | }; 669 | 670 | const MainnetOpUp: OpUp = { 671 | callerAppId: 1167143153, 672 | baseAppId: 971335616, 673 | }; 674 | 675 | export { 676 | MainnetPoolManagerAppId, 677 | MainnetDepositsAppId, 678 | MainnetDepositStakingAppId, 679 | MainnetPoolKey, 680 | MainnetPools, 681 | MainnetLoans, 682 | MainnetTinymanAppId, 683 | MainnetLendingPoolKey, 684 | MainnetLendingPools, 685 | MainnetReserveAddress, 686 | MainnetOracle, 687 | MainnetOpUp, 688 | }; 689 | -------------------------------------------------------------------------------- /src/lend/constants/testnet-constants.ts: -------------------------------------------------------------------------------- 1 | import { LoanType } from "../types"; 2 | 3 | import type { OpUp, Oracle, Pool, ReserveAddress } from "../types"; 4 | 5 | const TestnetPoolManagerAppId = 147157634; 6 | 7 | const TestnetDepositsAppId = 147157692; 8 | 9 | type TestnetPoolKey = "ALGO" | "gALGO" | "xALGO" | "USDC" | "USDt" | "goBTC" | "goETH"; 10 | const TestnetPools: Record = { 11 | ALGO: { 12 | appId: 147169673, 13 | assetId: 0, 14 | fAssetId: 147171698, 15 | frAssetId: 147171699, 16 | assetDecimals: 6, 17 | poolManagerIndex: 0, 18 | loans: { 19 | 147173131: BigInt(0), 20 | 168153622: BigInt(0), 21 | 397181473: BigInt(1), 22 | 397181998: BigInt(1), 23 | }, 24 | }, 25 | gALGO: { 26 | appId: 168152517, 27 | assetId: 167184545, 28 | fAssetId: 168153084, 29 | frAssetId: 168153085, 30 | assetDecimals: 6, 31 | poolManagerIndex: 5, 32 | loans: { 33 | 147173131: BigInt(5), 34 | 168153622: BigInt(1), 35 | }, 36 | }, 37 | xALGO: { 38 | appId: 730786369, 39 | assetId: 730430700, 40 | fAssetId: 730786397, 41 | frAssetId: 730786398, 42 | assetDecimals: 6, 43 | poolManagerIndex: 6, 44 | loans: { 45 | 147173131: BigInt(6), 46 | 168153622: BigInt(2), 47 | }, 48 | }, 49 | USDC: { 50 | appId: 147170678, 51 | assetId: 67395862, 52 | fAssetId: 147171826, 53 | frAssetId: 147171827, 54 | assetDecimals: 6, 55 | poolManagerIndex: 1, 56 | loans: { 57 | 147173131: BigInt(1), 58 | 147173190: BigInt(0), 59 | 397181473: BigInt(0), 60 | 397181998: BigInt(0), 61 | }, 62 | }, 63 | USDt: { 64 | appId: 147171033, 65 | assetId: 67396430, 66 | fAssetId: 147172417, 67 | frAssetId: 147172418, 68 | assetDecimals: 6, 69 | poolManagerIndex: 2, 70 | loans: { 71 | 147173131: BigInt(2), 72 | 147173190: BigInt(1), 73 | }, 74 | }, 75 | goBTC: { 76 | appId: 147171314, 77 | assetId: 67396528, 78 | fAssetId: 147172646, 79 | frAssetId: 147172647, 80 | assetDecimals: 8, 81 | poolManagerIndex: 3, 82 | loans: { 83 | 147173131: BigInt(3), 84 | 397181473: BigInt(2), 85 | 397181998: BigInt(2), 86 | }, 87 | }, 88 | goETH: { 89 | appId: 147171345, 90 | assetId: 76598897, 91 | fAssetId: 147172881, 92 | frAssetId: 147172882, 93 | assetDecimals: 8, 94 | poolManagerIndex: 4, 95 | loans: { 96 | 147173131: BigInt(4), 97 | 397181473: BigInt(3), 98 | 397181998: BigInt(3), 99 | }, 100 | }, 101 | }; 102 | 103 | const TestnetLoans: Partial> = { 104 | [LoanType.GENERAL]: 147173131, 105 | [LoanType.STABLECOIN_EFFICIENCY]: 147173190, 106 | [LoanType.ALGO_EFFICIENCY]: 168153622, 107 | [LoanType.ULTRASWAP_UP]: 397181473, 108 | [LoanType.ULTRASWAP_DOWN]: 397181998, 109 | }; 110 | 111 | const TestnetReserveAddress: ReserveAddress = "KLF3MEIIHMTA7YHNPLBDVHLN2MVC27X5M7ULTDZLMEX5XO5XCUP7HGBHMQ"; 112 | 113 | const TestnetOracle: Oracle = { 114 | oracle0AppId: 159512493, 115 | oracleAdapterAppId: 147153711, 116 | decimals: 14, 117 | }; 118 | 119 | const TestnetOpUp: OpUp = { 120 | callerAppId: 397104542, 121 | baseAppId: 118186203, 122 | }; 123 | 124 | export { 125 | TestnetPoolManagerAppId, 126 | TestnetDepositsAppId, 127 | TestnetPoolKey, 128 | TestnetPools, 129 | TestnetLoans, 130 | TestnetReserveAddress, 131 | TestnetOracle, 132 | TestnetOpUp, 133 | }; 134 | -------------------------------------------------------------------------------- /src/lend/deposit-staking.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AtomicTransactionComposer, 3 | generateAccount, 4 | getApplicationAddress, 5 | getMethodByName, 6 | makeApplicationCloseOutTxn, 7 | OnApplicationComplete, 8 | } from "algosdk"; 9 | 10 | import { maximum } from "../math-lib"; 11 | import { 12 | addEscrowNoteTransaction, 13 | fromIntToByteHex, 14 | getAccountApplicationLocalState, 15 | getAccountDetails, 16 | getApplicationGlobalState, 17 | getParsedValueFromState, 18 | removeEscrowNoteTransaction, 19 | signer, 20 | unixTime, 21 | } from "../utils"; 22 | 23 | import { depositStakingABIContract } from "./abi-contracts"; 24 | import { depositStakingLocalState, depositStakingProgramsInfo, getEscrows, userDepositStakingInfo } from "./utils"; 25 | 26 | import type { DepositStakingInfo, Pool, UserDepositStakingLocalState } from "./types"; 27 | import type { Account, Algodv2, Indexer, SuggestedParams, Transaction } from "algosdk"; 28 | 29 | /** 30 | * 31 | * Returns information regarding the given deposit staking application. 32 | * 33 | * @param client - Algorand client to query 34 | * @param depositStakingAppId - deposit staking application to query about 35 | * @returns Promise pool info 36 | */ 37 | async function retrieveDepositStakingInfo( 38 | client: Algodv2 | Indexer, 39 | depositStakingAppId: number, 40 | ): Promise { 41 | const { currentRound, globalState: state } = await getApplicationGlobalState(client, depositStakingAppId); 42 | if (state === undefined) throw Error("Could not find Deposit Staking"); 43 | 44 | // initialise staking program 45 | const stakingPrograms = []; 46 | for (let i = 0; i <= 5; i++) { 47 | const prefix = "S".charCodeAt(0).toString(16); 48 | const stakeBase64Value = String(getParsedValueFromState(state, prefix + fromIntToByteHex(i), "hex")); 49 | const stakeValue = Buffer.from(stakeBase64Value, "base64").toString("hex"); 50 | 51 | for (let j = 0; j <= 4; j++) { 52 | const basePos = j * 46; 53 | 54 | const rewards: { 55 | rewardAssetId: number; 56 | endTimestamp: bigint; 57 | rewardRate: bigint; 58 | rewardPerToken: bigint; 59 | }[] = []; 60 | stakingPrograms.push({ 61 | poolAppId: Number("0x" + stakeValue.slice(basePos, basePos + 12)), 62 | totalStaked: BigInt("0x" + stakeValue.slice(basePos + 12, basePos + 28)), 63 | minTotalStaked: BigInt("0x" + stakeValue.slice(basePos + 28, basePos + 44)), 64 | stakeIndex: i * 5 + j, 65 | numRewards: Number("0x" + stakeValue.slice(basePos + 44, basePos + 46)), 66 | rewards, 67 | }); 68 | } 69 | } 70 | 71 | // add rewards 72 | for (let i = 0; i <= 22; i++) { 73 | const prefix = "R".charCodeAt(0).toString(16); 74 | const rewardBase64Value = String(getParsedValueFromState(state, prefix + fromIntToByteHex(i), "hex")); 75 | const rewardValue = Buffer.from(rewardBase64Value, "base64").toString("hex"); 76 | for (let j = 0; j <= (i !== 22 ? 3 : 1); j++) { 77 | const basePos = j * 60; 78 | 79 | const stakeIndex = Number(BigInt(i * 4 + j) / BigInt(3)); 80 | const localRewardIndex = Number(BigInt(i * 4 + j) % BigInt(3)); 81 | const { totalStaked, minTotalStaked, rewards, numRewards } = stakingPrograms[stakeIndex]; 82 | if (localRewardIndex >= numRewards) continue; 83 | 84 | const ts = maximum(totalStaked, minTotalStaked); 85 | const endTimestamp = BigInt("0x" + rewardValue.slice(basePos + 12, basePos + 20)); 86 | const lu = BigInt("0x" + rewardValue.slice(basePos + 20, basePos + 28)); 87 | const rewardRate = BigInt("0x" + rewardValue.slice(basePos + 28, basePos + 44)); 88 | const rpt = BigInt("0x" + rewardValue.slice(basePos + 44, basePos + 60)); 89 | const currTime = BigInt(unixTime()); 90 | const dt = currTime <= endTimestamp ? currTime - lu : lu <= endTimestamp ? endTimestamp - lu : BigInt(0); 91 | const rewardPerToken = rpt + (rewardRate * dt) / ts; 92 | 93 | rewards.push({ 94 | rewardAssetId: Number("0x" + rewardValue.slice(basePos, basePos + 12)), 95 | endTimestamp, 96 | rewardRate, 97 | rewardPerToken, 98 | }); 99 | } 100 | } 101 | 102 | // combine 103 | return { currentRound, stakingPrograms }; 104 | } 105 | 106 | /** 107 | * 108 | * Returns local state regarding the deposit staking escrows of a given user. 109 | * Use for advanced use cases where optimising number of network request. 110 | * 111 | * @param indexerClient - Algorand indexer client to query 112 | * @param depositStakingAppId - deposit staking application to query about 113 | * @param userAddr - account address for the user 114 | * @returns Promise deposit staking escrows' local state 115 | */ 116 | async function retrieveUserDepositStakingsLocalState( 117 | indexerClient: Indexer, 118 | depositStakingAppId: number, 119 | userAddr: string, 120 | ): Promise { 121 | const depositStakingsLocalState: UserDepositStakingLocalState[] = []; 122 | 123 | const escrows = await getEscrows(indexerClient, userAddr, depositStakingAppId, "fa ", "fr "); 124 | 125 | // get all remaining deposit stakings' local state 126 | for (const escrowAddr of escrows) { 127 | const [{ holdings }, { currentRound, localState: state }] = await Promise.all([ 128 | getAccountDetails(indexerClient, escrowAddr), 129 | getAccountApplicationLocalState(indexerClient, depositStakingAppId, escrowAddr), 130 | ]); 131 | 132 | const optedIntoAssets: Set = new Set(holdings.keys()); 133 | if (state === undefined) 134 | throw Error(`Could not find deposit staking ${depositStakingAppId} in escrow ${escrowAddr}`); 135 | 136 | depositStakingsLocalState.push({ 137 | ...depositStakingLocalState(state, depositStakingAppId, escrowAddr), 138 | currentRound, 139 | optedIntoAssets, 140 | }); 141 | } 142 | 143 | return depositStakingsLocalState; 144 | } 145 | 146 | /** 147 | * 148 | * Returns local state regarding the deposit staking escrows of a given user. 149 | * Use for advanced use cases where optimising number of network request. 150 | * 151 | * @param indexerClient - Algorand indexer client to query 152 | * @param depositStakingAppId - deposit staking application to query about 153 | * @param escrowAddr - account address for the deposit staking escrow 154 | * @returns Promise deposit staking escrows' local state 155 | */ 156 | async function retrieveUserDepositStakingLocalState( 157 | indexerClient: Indexer, 158 | depositStakingAppId: number, 159 | escrowAddr: string, 160 | ): Promise { 161 | const { currentRound, localState: state } = await getAccountApplicationLocalState( 162 | indexerClient, 163 | depositStakingAppId, 164 | escrowAddr, 165 | ); 166 | if (state === undefined) throw Error(`Could not find deposit staking ${depositStakingAppId} in escrow ${escrowAddr}`); 167 | return { currentRound, ...depositStakingLocalState(state, depositStakingAppId, escrowAddr) }; 168 | } 169 | 170 | /** 171 | * 172 | * Returns a group transaction to deposit staking escrow. 173 | * 174 | * @param depositStakingAppId - deposit staking application to query about 175 | * @param userAddr - account address for the user 176 | * @param params - suggested params for the transactions with the fees overwritten 177 | * @returns { txns: Transaction[], escrow: Account } object containing group transaction and generated escrow account 178 | */ 179 | function prepareAddDepositStakingEscrow( 180 | depositStakingAppId: number, 181 | userAddr: string, 182 | params: SuggestedParams, 183 | ): { txns: Transaction[]; escrow: Account } { 184 | const escrow = generateAccount(); 185 | 186 | const userCall = addEscrowNoteTransaction(userAddr, escrow.addr, depositStakingAppId, "fa ", { 187 | ...params, 188 | flatFee: true, 189 | fee: 2000, 190 | }); 191 | 192 | const atc = new AtomicTransactionComposer(); 193 | atc.addMethodCall({ 194 | sender: escrow.addr, 195 | signer, 196 | appID: depositStakingAppId, 197 | onComplete: OnApplicationComplete.OptInOC, 198 | method: getMethodByName(depositStakingABIContract.methods, "add_f_staking_escrow"), 199 | methodArgs: [{ txn: userCall, signer }], 200 | rekeyTo: getApplicationAddress(depositStakingAppId), 201 | suggestedParams: { ...params, flatFee: true, fee: 0 }, 202 | }); 203 | const txns = atc.buildGroup().map(({ txn }) => { 204 | txn.group = undefined; 205 | return txn; 206 | }); 207 | return { txns, escrow }; 208 | } 209 | 210 | /** 211 | * 212 | * Returns a transaction to opt deposit staking escrow into asset so that it can hold a given pool's f asset. 213 | * 214 | * @param depositStakingAppId - deposit staking application of escrow 215 | * @param poolManagerAppId - pool manager application 216 | * @param userAddr - account address for the user 217 | * @param escrowAddr - account address for the deposit staking escrow 218 | * @param pool - pool to add f asset of 219 | * @param stakeIndex - staking program index 220 | * @param params - suggested params for the transactions with the fees overwritten 221 | * @returns Transaction opt deposit staking escrow into asset transaction 222 | */ 223 | function prepareOptDepositStakingEscrowIntoAsset( 224 | depositStakingAppId: number, 225 | poolManagerAppId: number, 226 | userAddr: string, 227 | escrowAddr: string, 228 | pool: Pool, 229 | stakeIndex: number, 230 | params: SuggestedParams, 231 | ): Transaction { 232 | const { appId: poolAppId, fAssetId } = pool; 233 | 234 | const atc = new AtomicTransactionComposer(); 235 | atc.addMethodCall({ 236 | sender: userAddr, 237 | signer, 238 | appID: depositStakingAppId, 239 | method: getMethodByName(depositStakingABIContract.methods, "opt_escrow_into_asset"), 240 | methodArgs: [escrowAddr, poolAppId, fAssetId, stakeIndex], 241 | suggestedParams: { ...params, flatFee: true, fee: 2000 }, 242 | }); 243 | const txns = atc.buildGroup().map(({ txn }) => { 244 | txn.group = undefined; 245 | return txn; 246 | }); 247 | return txns[0]; 248 | } 249 | 250 | /** 251 | * 252 | * Returns a transaction to sync stake of deposit staking escrow 253 | * 254 | * @param depositStakingAppId - deposit staking application of escrow 255 | * @param pool - pool to sync stake of 256 | * @param userAddr - account address for the user 257 | * @param escrowAddr - account address for the deposit staking escrow 258 | * @param stakeIndex - staking program index 259 | * @param params - suggested params for the transactions with the fees overwritten 260 | * @returns Transaction sync stake of deposit staking escrow transaction 261 | */ 262 | function prepareSyncStakeInDepositStakingEscrow( 263 | depositStakingAppId: number, 264 | pool: Pool, 265 | userAddr: string, 266 | escrowAddr: string, 267 | stakeIndex: number, 268 | params: SuggestedParams, 269 | ): Transaction { 270 | const { appId: poolAppId, fAssetId } = pool; 271 | 272 | const atc = new AtomicTransactionComposer(); 273 | atc.addMethodCall({ 274 | sender: userAddr, 275 | signer, 276 | appID: depositStakingAppId, 277 | method: getMethodByName(depositStakingABIContract.methods, "sync_stake"), 278 | methodArgs: [escrowAddr, poolAppId, fAssetId, stakeIndex], 279 | suggestedParams: { ...params, flatFee: true, fee: 2000 }, 280 | }); 281 | const txns = atc.buildGroup().map(({ txn }) => { 282 | txn.group = undefined; 283 | return txn; 284 | }); 285 | return txns[0]; 286 | } 287 | 288 | /** 289 | * 290 | * Returns a transaction to claim the rewards of a deposit staking escrow 291 | * 292 | * @param depositStakingAppId - deposit staking application of escrow 293 | * @param userAddr - account address for the user 294 | * @param escrowAddr - account address for the deposit staking escrow 295 | * @param receiverAddr - account address to receive the withdrawal (typically the same as the user address) 296 | * @param stakeIndex - staking program index 297 | * @param rewardAssetIds - the asset ids of all the rewards assets claiming 298 | * @param params - suggested params for the transactions with the fees overwritten 299 | * @returns Transaction withdraw from deposit staking escrow transaction 300 | */ 301 | function prepareClaimRewardsOfDepositStakingEscrow( 302 | depositStakingAppId: number, 303 | userAddr: string, 304 | escrowAddr: string, 305 | receiverAddr: string, 306 | stakeIndex: number, 307 | rewardAssetIds: number[], 308 | params: SuggestedParams, 309 | ): Transaction { 310 | const atc = new AtomicTransactionComposer(); 311 | atc.addMethodCall({ 312 | sender: userAddr, 313 | signer, 314 | appID: depositStakingAppId, 315 | method: getMethodByName(depositStakingABIContract.methods, "claim_rewards"), 316 | methodArgs: [escrowAddr, receiverAddr, stakeIndex], 317 | suggestedParams: { ...params, flatFee: true, fee: 4000 }, 318 | }); 319 | const txns = atc.buildGroup().map(({ txn }) => { 320 | txn.group = undefined; 321 | return txn; 322 | }); 323 | txns[0].appForeignAssets = rewardAssetIds; 324 | return txns[0]; 325 | } 326 | 327 | /** 328 | * 329 | * Returns a transaction to withdraw from a deposit staking escrow 330 | * 331 | * @param depositStakingAppId - deposit staking application of escrow 332 | * @param pool - pool to withdraw from 333 | * @param poolManagerAppId - pool manager application 334 | * @param userAddr - account address for the user 335 | * @param escrowAddr - account address for the deposit staking escrow 336 | * @param receiverAddr - account address to receive the withdrawal (typically the same as the user address) 337 | * @param amount - the amount of asset / f asset to send to withdraw from escrow. 338 | * @param isfAssetAmount - whether the amount to withdraw is expressed in terms of f asset or asset 339 | * @param remainDeposited - whether receiver should get f asset or asset (cannot remain deposited and use asset amount) 340 | * @param stakeIndex - staking program index 341 | * @param params - suggested params for the transactions with the fees overwritten 342 | * @returns Transaction withdraw from deposit staking escrow transaction 343 | */ 344 | function prepareWithdrawFromDepositStakingEscrow( 345 | depositStakingAppId: number, 346 | pool: Pool, 347 | poolManagerAppId: number, 348 | userAddr: string, 349 | escrowAddr: string, 350 | receiverAddr: string, 351 | amount: number | bigint, 352 | isfAssetAmount: boolean, 353 | remainDeposited: boolean, 354 | stakeIndex: number, 355 | params: SuggestedParams, 356 | ): Transaction { 357 | const { appId: poolAppId, assetId, fAssetId } = pool; 358 | 359 | const atc = new AtomicTransactionComposer(); 360 | atc.addMethodCall({ 361 | sender: userAddr, 362 | signer, 363 | appID: depositStakingAppId, 364 | method: getMethodByName(depositStakingABIContract.methods, "withdraw_stake"), 365 | methodArgs: [ 366 | escrowAddr, 367 | receiverAddr, 368 | poolManagerAppId, 369 | poolAppId, 370 | assetId, 371 | fAssetId, 372 | amount, 373 | isfAssetAmount, 374 | remainDeposited, 375 | stakeIndex, 376 | ], 377 | suggestedParams: { ...params, flatFee: true, fee: 6000 }, 378 | }); 379 | const txns = atc.buildGroup().map(({ txn }) => { 380 | txn.group = undefined; 381 | return txn; 382 | }); 383 | return txns[0]; 384 | } 385 | 386 | /** 387 | * 388 | * Returns a transaction to remove asset from deposit staking escrow 389 | * 390 | * @param depositStakingAppId - deposit staking application of escrow 391 | * @param userAddr - account address for the user 392 | * @param escrowAddr - account address for the deposit staking escrow 393 | * @param pool - pool to remove f asset of 394 | * @param params - suggested params for the transactions with the fees overwritten 395 | * @returns Transaction opt out deposit staking escrow from asset transaction 396 | */ 397 | function prepareOptOutDepositStakingEscrowFromAsset( 398 | depositStakingAppId: number, 399 | userAddr: string, 400 | escrowAddr: string, 401 | pool: Pool, 402 | params: SuggestedParams, 403 | ): Transaction { 404 | const { appId: poolAppId, fAssetId } = pool; 405 | 406 | const atc = new AtomicTransactionComposer(); 407 | atc.addMethodCall({ 408 | sender: userAddr, 409 | signer, 410 | appID: depositStakingAppId, 411 | method: getMethodByName(depositStakingABIContract.methods, "close_out_escrow_from_asset"), 412 | methodArgs: [escrowAddr, getApplicationAddress(poolAppId), fAssetId], 413 | suggestedParams: { ...params, flatFee: true, fee: 3000 }, 414 | }); 415 | const txns = atc.buildGroup().map(({ txn }) => { 416 | txn.group = undefined; 417 | return txn; 418 | }); 419 | return txns[0]; 420 | } 421 | 422 | /** 423 | * 424 | * Returns a group transaction to remove a user's deposit staking escrow and return its minimum balance to the user. 425 | * 426 | * @param depositStakingAppId - deposit staking application of escrow 427 | * @param userAddr - account address for the user 428 | * @param escrowAddr - account address for the deposit staking escrow 429 | * @param params - suggested params for the transactions with the fees overwritten 430 | * @returns Transaction[] remove and close out deposit staking escrow group transaction 431 | */ 432 | function prepareRemoveDepositStakingEscrow( 433 | depositStakingAppId: number, 434 | userAddr: string, 435 | escrowAddr: string, 436 | params: SuggestedParams, 437 | ) { 438 | const atc = new AtomicTransactionComposer(); 439 | atc.addMethodCall({ 440 | sender: userAddr, 441 | signer, 442 | appID: depositStakingAppId, 443 | method: getMethodByName(depositStakingABIContract.methods, "remove_f_staking_escrow"), 444 | methodArgs: [escrowAddr], 445 | suggestedParams: { ...params, flatFee: true, fee: 2000 }, 446 | }); 447 | const txns = atc.buildGroup().map(({ txn }) => { 448 | txn.group = undefined; 449 | return txn; 450 | }); 451 | const optOutTx = makeApplicationCloseOutTxn(escrowAddr, params, depositStakingAppId); 452 | const closeToTx = removeEscrowNoteTransaction(escrowAddr, userAddr, "fr ", params); 453 | return [txns[0], optOutTx, closeToTx]; 454 | } 455 | 456 | export { 457 | retrieveDepositStakingInfo, 458 | depositStakingProgramsInfo, 459 | retrieveUserDepositStakingsLocalState, 460 | retrieveUserDepositStakingLocalState, 461 | userDepositStakingInfo, 462 | prepareAddDepositStakingEscrow, 463 | prepareOptDepositStakingEscrowIntoAsset, 464 | prepareSyncStakeInDepositStakingEscrow, 465 | prepareClaimRewardsOfDepositStakingEscrow, 466 | prepareWithdrawFromDepositStakingEscrow, 467 | prepareOptOutDepositStakingEscrowFromAsset, 468 | prepareRemoveDepositStakingEscrow, 469 | }; 470 | -------------------------------------------------------------------------------- /src/lend/formulae.ts: -------------------------------------------------------------------------------- 1 | import { 2 | divScale, 3 | divScaleRoundUp, 4 | expBySquaring, 5 | mulScale, 6 | mulScaleRoundUp, 7 | ONE_10_DP, 8 | ONE_14_DP, 9 | ONE_16_DP, 10 | ONE_4_DP, 11 | SECONDS_IN_YEAR, 12 | sqrt, 13 | } from "../math-lib"; 14 | import { unixTime } from "../utils"; 15 | 16 | /** 17 | * Calculates the dollar value of a given asset amount 18 | * @param amount (0dp) 19 | * @param price (14dp) 20 | * @return value (0dp) 21 | */ 22 | function calcAssetDollarValue(amount: bigint, price: bigint): bigint { 23 | return mulScaleRoundUp(amount, price, ONE_14_DP); 24 | } 25 | 26 | /** 27 | * Calculates the total debt of a pool 28 | * @param totalVarDebt (0dp) 29 | * @param totalStblDebt (0dp) 30 | * @return totalDebt (0dp) 31 | */ 32 | function calcTotalDebt(totalVarDebt: bigint, totalStblDebt: bigint): bigint { 33 | return totalVarDebt + totalStblDebt; 34 | } 35 | 36 | /** 37 | * Calculates the total debt of a pool 38 | * @param totalDebt (0dp) 39 | * @param totalDeposits (0dp) 40 | * @return availableLiquidity (0dp) 41 | */ 42 | function calcAvailableLiquidity(totalDebt: bigint, totalDeposits: bigint): bigint { 43 | return totalDeposits - totalDebt; 44 | } 45 | 46 | /** 47 | * Calculates the ratio of the available liquidity that is being stable borrowed 48 | * @param stblBorAmount (0dp) 49 | * @param availableLiquidity (0dp) 50 | * @return stableBorrowRatio (16dp) 51 | */ 52 | function calcStableBorrowRatio(stblBorAmount: bigint, availableLiquidity: bigint): bigint { 53 | return divScale(stblBorAmount, availableLiquidity, ONE_16_DP); 54 | } 55 | 56 | /** 57 | * Calculates the maximum stable borrow amount a user can make in one go 58 | * @param availableLiquidity (0dp) 59 | * @param sbpc (0dp) 60 | * @return stableBorrowRatio (16dp) 61 | */ 62 | function calcMaxSingleStableBorrow(availableLiquidity: bigint, sbpc: bigint): bigint { 63 | return mulScale(availableLiquidity, sbpc, ONE_16_DP); 64 | } 65 | 66 | /** 67 | * Calculates the utilisation ratio of a pool 68 | * @param totalDebt (0dp) 69 | * @param totalDeposits (0dp) 70 | * @return utilisationRatio (16dp) 71 | */ 72 | function calcUtilisationRatio(totalDebt: bigint, totalDeposits: bigint): bigint { 73 | if (totalDeposits === BigInt(0)) return BigInt(0); 74 | return divScale(totalDebt, totalDeposits, ONE_16_DP); 75 | } 76 | 77 | /** 78 | * Calculates the stable debt to total debt ratio of a pool 79 | * @param totalStblDebt (0dp) 80 | * @param totalDebt (0dp) 81 | * @return stableDebtToTotalDebtRatio (16dp) 82 | */ 83 | function calcStableDebtToTotalDebtRatio(totalStblDebt: bigint, totalDebt: bigint): bigint { 84 | if (totalDebt === BigInt(0)) return BigInt(0); 85 | return divScale(totalStblDebt, totalDebt, ONE_16_DP); 86 | } 87 | 88 | /** 89 | * Calculate the variable borrow interest rate of a pool 90 | * @param vr0 (16dp) 91 | * @param vr1 (16dp) 92 | * @param vr2 (16dp) 93 | * @param ut (16dp) 94 | * @param uopt (16dp) 95 | * @return variableBorrowInterestRate (16dp) 96 | */ 97 | function calcVariableBorrowInterestRate(vr0: bigint, vr1: bigint, vr2: bigint, ut: bigint, uopt: bigint): bigint { 98 | return ut < uopt 99 | ? vr0 + divScale(mulScale(ut, vr1, ONE_16_DP), uopt, ONE_16_DP) 100 | : vr0 + vr1 + divScale(mulScale(ut - uopt, vr2, ONE_16_DP), ONE_16_DP - uopt, ONE_16_DP); 101 | } 102 | 103 | /** 104 | * Calculate the stable borrow interest rate of a pool 105 | * @param vr1 (16dp) 106 | * @param sr0 (16dp) 107 | * @param sr1 (16dp) 108 | * @param sr2 (16dp) 109 | * @param sr3 (16dp) 110 | * @param ut (16dp) 111 | * @param uopt (16dp) 112 | * @param ratiot (16dp) 113 | * @param ratioopt (16dp) 114 | * @return stableBorrowInterestRate (16dp) 115 | */ 116 | function calcStableBorrowInterestRate( 117 | vr1: bigint, 118 | sr0: bigint, 119 | sr1: bigint, 120 | sr2: bigint, 121 | sr3: bigint, 122 | ut: bigint, 123 | uopt: bigint, 124 | ratiot: bigint, 125 | ratioopt: bigint, 126 | ): bigint { 127 | const base = 128 | ut <= uopt 129 | ? vr1 + sr0 + divScale(mulScale(ut, sr1, ONE_16_DP), uopt, ONE_16_DP) 130 | : vr1 + sr0 + sr1 + divScale(mulScale(ut - uopt, sr2, ONE_16_DP), ONE_16_DP - uopt, ONE_16_DP); 131 | const extra = 132 | ratiot <= ratioopt 133 | ? BigInt(0) 134 | : divScale(mulScale(sr3, ratiot - ratioopt, ONE_16_DP), ONE_16_DP - ratioopt, ONE_16_DP); 135 | return base + extra; 136 | } 137 | 138 | /** 139 | * Calculate the overall borrow interest rate of a pool 140 | * @param totalVarDebt (0dp) 141 | * @param totalDebt (0dp) 142 | * @param vbirt (16dp) 143 | * @param osbiat (16dp) 144 | * @return overallBorrowInterestRate (16dp) 145 | */ 146 | function calcOverallBorrowInterestRate(totalVarDebt: bigint, totalDebt: bigint, vbirt: bigint, osbiat: bigint): bigint { 147 | if (totalDebt === BigInt(0)) return BigInt(0); 148 | return (totalVarDebt * vbirt + osbiat) / totalDebt; 149 | } 150 | 151 | /** 152 | * Calculate the deposit interest rate of a pool 153 | * @param obirt (16dp) 154 | * @param ut (16dp) 155 | * @param rr (16dp) 156 | * @return overallBorrowInterestRate (16dp) 157 | */ 158 | function calcDepositInterestRate(obirt: bigint, rr: bigint, ut: bigint): bigint { 159 | return mulScale(mulScale(ut, obirt, ONE_16_DP), ONE_16_DP - rr, ONE_16_DP); 160 | } 161 | 162 | /** 163 | * Calculate the borrow interest index of pool 164 | * @param birt1 (16dp) 165 | * @param biit1 (16dp) 166 | * @param latestUpdate (0dp) 167 | * @return borrowInterestIndex (14dp) 168 | */ 169 | function calcBorrowInterestIndex(birt1: bigint, biit1: bigint, latestUpdate: bigint): bigint { 170 | const dt = BigInt(unixTime()) - latestUpdate; 171 | return mulScale(biit1, expBySquaring(ONE_16_DP + birt1 / SECONDS_IN_YEAR, dt, ONE_16_DP), ONE_16_DP); 172 | } 173 | 174 | /** 175 | * Calculate the deposit interest index of pool 176 | * @param dirt1 (16dp) 177 | * @param diit1 (16dp) 178 | * @param latestUpdate (0dp) 179 | * @return depositInterestIndex (14dp) 180 | */ 181 | function calcDepositInterestIndex(dirt1: bigint, diit1: bigint, latestUpdate: bigint): bigint { 182 | const dt = BigInt(unixTime()) - latestUpdate; 183 | return mulScale(diit1, ONE_16_DP + (dirt1 * dt) / SECONDS_IN_YEAR, ONE_16_DP); 184 | } 185 | 186 | /** 187 | * Calculates the fAsset received from a deposit 188 | * @param depositAmount (0dp) 189 | * @param diit (14dp) 190 | * @return depositReturn (0dp) 191 | */ 192 | function calcDepositReturn(depositAmount: bigint, diit: bigint): bigint { 193 | return divScale(depositAmount, diit, ONE_14_DP); 194 | } 195 | 196 | /** 197 | * Calculates the asset received from a withdraw 198 | * @param withdrawAmount (0dp) 199 | * @param diit (14dp) 200 | * @return withdrawReturn (0dp) 201 | */ 202 | function calcWithdrawReturn(withdrawAmount: bigint, diit: bigint): bigint { 203 | return mulScale(withdrawAmount, diit, ONE_14_DP); 204 | } 205 | 206 | /** 207 | * Calculates the collateral asset loan value 208 | * @param amount (0dp) 209 | * @param price (14dp) 210 | * @param factor (4dp) 211 | * @return loanValue (4dp) 212 | */ 213 | function calcCollateralAssetLoanValue(amount: bigint, price: bigint, factor: bigint): bigint { 214 | return mulScale(mulScale(amount, price, ONE_10_DP), factor, ONE_4_DP); 215 | } 216 | 217 | /** 218 | * Calculates the borrow asset loan value 219 | * @param amount (0dp) 220 | * @param price (14dp) 221 | * @param factor (4dp) 222 | * @return loanValue (4dp) 223 | */ 224 | function calcBorrowAssetLoanValue(amount: bigint, price: bigint, factor: bigint): bigint { 225 | return mulScaleRoundUp(mulScaleRoundUp(amount, price, ONE_10_DP), factor, ONE_4_DP); 226 | } 227 | 228 | /** 229 | * Calculates the loan's LTV ratio 230 | * @param totalBorrowBalanceValue (4dp) 231 | * @param totalCollateralBalanceValue (4dp) 232 | * @return LTVRatio (4dp) 233 | */ 234 | function calcLTVRatio(totalBorrowBalanceValue: bigint, totalCollateralBalanceValue: bigint): bigint { 235 | if (totalCollateralBalanceValue === BigInt(0)) return BigInt(0); 236 | return divScale(totalBorrowBalanceValue, totalCollateralBalanceValue, ONE_4_DP); 237 | } 238 | 239 | /** 240 | * Calculates the loan's borrow utilisation ratio 241 | * @param totalEffectiveBorrowBalanceValue (4dp) 242 | * @param totalEffectiveCollateralBalanceValue (4dp) 243 | * @return borrowUtilisationRatio (4dp) 244 | */ 245 | function calcBorrowUtilisationRatio( 246 | totalEffectiveBorrowBalanceValue: bigint, 247 | totalEffectiveCollateralBalanceValue: bigint, 248 | ): bigint { 249 | if (totalEffectiveCollateralBalanceValue === BigInt(0)) return BigInt(0); 250 | return divScale(totalEffectiveBorrowBalanceValue, totalEffectiveCollateralBalanceValue, ONE_4_DP); 251 | } 252 | 253 | /** 254 | * Calculates the loan's liquidation margin 255 | * @param totalEffectiveBorrowBalanceValue (4dp) 256 | * @param totalEffectiveCollateralBalanceValue (4dp) 257 | * @return liquidationMargin (4dp) 258 | */ 259 | function calcLiquidationMargin( 260 | totalEffectiveBorrowBalanceValue: bigint, 261 | totalEffectiveCollateralBalanceValue: bigint, 262 | ): bigint { 263 | if (totalEffectiveCollateralBalanceValue === BigInt(0)) return BigInt(0); 264 | return divScale( 265 | totalEffectiveCollateralBalanceValue - totalEffectiveBorrowBalanceValue, 266 | totalEffectiveCollateralBalanceValue, 267 | ONE_4_DP, 268 | ); 269 | } 270 | 271 | /** 272 | * Calculates the borrow balance of the loan at time t 273 | * @param bbtn1 (0dp) 274 | * @param biit (14dp) 275 | * @param biitn1 (14dp) 276 | * @return borrowBalance (0dp) 277 | */ 278 | function calcBorrowBalance(bbtn1: bigint, biit: bigint, biitn1: bigint): bigint { 279 | return mulScaleRoundUp(bbtn1, divScaleRoundUp(biit, biitn1, ONE_14_DP), ONE_14_DP); 280 | } 281 | 282 | /** 283 | * Calculates the stable borrow interest rate of the loan after a borrow increase 284 | * @param bbt (0dp) 285 | * @param amount (0dp) 286 | * @param sbirtn1 (16dp) 287 | * @param sbirt1 (16dp) 288 | * @return stableInterestRate (16dp) 289 | */ 290 | function calcLoanStableInterestRate(bbt: bigint, amount: bigint, sbirtn1: bigint, sbirt1: bigint): bigint { 291 | return (bbt * sbirtn1 + amount * sbirt1) / (bbt + amount); 292 | } 293 | 294 | /** 295 | * Calculates the deposit interest rate condition required to rebalance up stable borrow. 296 | * Note that there is also a second condition on the pool utilisation ratio. 297 | * @param rudir (16dp) 298 | * @param vr0 (16dp) 299 | * @param vr1 (16dp) 300 | * @param vr2 (16dp) 301 | * @return rebalanceUpThreshold (16dp) 302 | */ 303 | function calcRebalanceUpThreshold(rudir: bigint, vr0: bigint, vr1: bigint, vr2: bigint): bigint { 304 | return mulScale(rudir, vr0 + vr1 + vr2, ONE_16_DP); 305 | } 306 | 307 | /** 308 | * Calculates the stable interest rate condition required to rebalance down stable borrow 309 | * @param rdd (16dp) 310 | * @param sbirt1 (16dp) 311 | * @return rebalanceDownThreshold (16dp) 312 | */ 313 | function calcRebalanceDownThreshold(rdd: bigint, sbirt1: bigint): bigint { 314 | return mulScale(ONE_16_DP + rdd, sbirt1, ONE_16_DP); 315 | } 316 | 317 | /** 318 | * Calculates the flash loan repayment amount for a given borrow amount and fee 319 | * @param borrowAmount (0dp) 320 | * @param fee (16dp) 321 | * @return repaymentAmount (0dp) 322 | */ 323 | function calcFlashLoanRepayment(borrowAmount: bigint, fee: bigint): bigint { 324 | return borrowAmount + mulScaleRoundUp(borrowAmount, fee, ONE_16_DP); 325 | } 326 | 327 | /** 328 | * Calculates the LP price 329 | * @param r0 pool supply of asset 0 330 | * @param r1 pool supply of asset 1 331 | * @param p0 price of asset 0 332 | * @param p1 price of asset 1 333 | * @param lts circulating supply of liquidity token 334 | * @return bigint LP price 335 | */ 336 | function calcLPPrice(r0: bigint, r1: bigint, p0: bigint, p1: bigint, lts: bigint): bigint { 337 | return BigInt(2) * (sqrt(r0 * p0 * r1 * p1) / lts); 338 | } 339 | 340 | export { 341 | calcAssetDollarValue, 342 | calcTotalDebt, 343 | calcAvailableLiquidity, 344 | calcStableBorrowRatio, 345 | calcMaxSingleStableBorrow, 346 | calcUtilisationRatio, 347 | calcStableDebtToTotalDebtRatio, 348 | calcVariableBorrowInterestRate, 349 | calcStableBorrowInterestRate, 350 | calcOverallBorrowInterestRate, 351 | calcDepositInterestRate, 352 | calcBorrowInterestIndex, 353 | calcDepositInterestIndex, 354 | calcDepositReturn, 355 | calcWithdrawReturn, 356 | calcCollateralAssetLoanValue, 357 | calcBorrowAssetLoanValue, 358 | calcLTVRatio, 359 | calcBorrowUtilisationRatio, 360 | calcLiquidationMargin, 361 | calcBorrowBalance, 362 | calcLoanStableInterestRate, 363 | calcRebalanceUpThreshold, 364 | calcRebalanceDownThreshold, 365 | calcFlashLoanRepayment, 366 | calcLPPrice, 367 | }; 368 | -------------------------------------------------------------------------------- /src/lend/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./abi-contracts"; 2 | export * from "./constants/mainnet-constants"; 3 | export * from "./constants/testnet-constants"; 4 | export * from "./amm"; 5 | export * from "./deposit"; 6 | export * from "./deposit-staking"; 7 | export * from "./formulae"; 8 | export * from "./loan"; 9 | export * from "./opup"; 10 | export * from "./oracle"; 11 | export * from "./types"; 12 | -------------------------------------------------------------------------------- /src/lend/opup.ts: -------------------------------------------------------------------------------- 1 | import { encodeUint64, makeApplicationNoOpTxn } from "algosdk"; 2 | 3 | import type { OpUp } from "./types"; 4 | import type { SuggestedParams, Transaction } from "algosdk"; 5 | 6 | /** 7 | * 8 | * Given a transaction or transaction group, prefixes it with appl call to increase opcode budget and returns the new 9 | * transaction group. 10 | * A lot of the lending operations require additional opcode cost so use this to increase the budget. 11 | * 12 | * @param opup - opup applications 13 | * @param userAddr - account address for the user 14 | * @param transactions - transaction(s) to prefix opup to 15 | * @param numInnerTransactions - number of inner transactions to issue (remaining opcode is 691 + 689 * num.inner.txns) 16 | * @param params - suggested params for the transactions with the fees overwritten 17 | * @returns Transaction[] transaction group with opup prefixed 18 | */ 19 | function prefixWithOpUp( 20 | opup: OpUp, 21 | userAddr: string, 22 | transactions: Transaction | Transaction[], 23 | numInnerTransactions: number, 24 | params: SuggestedParams, 25 | ): Transaction[] { 26 | if (!Number.isInteger(numInnerTransactions) || numInnerTransactions > 256) { 27 | throw Error("Invalid number of inner transactions"); 28 | } 29 | 30 | const { callerAppId, baseAppId } = opup; 31 | const fee = (numInnerTransactions + 1) * 1000; 32 | const prefix = makeApplicationNoOpTxn( 33 | userAddr, 34 | { ...params, flatFee: true, fee }, 35 | callerAppId, 36 | [encodeUint64(numInnerTransactions)], 37 | undefined, 38 | [baseAppId], 39 | ); 40 | const txns = Array.isArray(transactions) ? transactions : [transactions]; 41 | return [prefix, ...txns].map((txn) => { 42 | txn.group = undefined; 43 | return txn; 44 | }); 45 | } 46 | 47 | export { prefixWithOpUp }; 48 | -------------------------------------------------------------------------------- /src/lend/oracle.ts: -------------------------------------------------------------------------------- 1 | import { AtomicTransactionComposer, decodeAddress, decodeUint64, encodeAddress, getMethodByName } from "algosdk"; 2 | 3 | import { minimum } from "../math-lib"; 4 | import { 5 | fromIntToBytes8Hex, 6 | getAccountApplicationLocalState, 7 | getApplicationGlobalState, 8 | getParsedValueFromState, 9 | signer, 10 | } from "../utils"; 11 | 12 | import { lpTokenOracleABIContract, oracleAdapterABIContract } from "./abi-contracts"; 13 | import { calcLPPrice } from "./formulae"; 14 | import { LPTokenProvider } from "./types"; 15 | 16 | import type { LPToken, Oracle, OraclePrice, OraclePrices, PactLPToken, TinymanLPToken } from "./types"; 17 | import type { Algodv2, Indexer, SuggestedParams, Transaction } from "algosdk"; 18 | import type { TealKeyValue } from "algosdk/dist/types/client/v2/algod/models/types"; 19 | 20 | function parseOracleValue(base64Value: string) { 21 | const value = Buffer.from(base64Value, "base64").toString("hex"); 22 | 23 | // [price (uint64), latest_update (uint64), ...] 24 | const price = BigInt("0x" + value.slice(0, 16)); 25 | const timestamp = BigInt("0x" + value.slice(16, 32)); 26 | return { price, timestamp }; 27 | } 28 | 29 | function parseLPTokenOracleValue(lpAssetId: number, base64Value: string): LPToken { 30 | const value = Buffer.from(base64Value, "base64").toString("hex"); 31 | 32 | // [type (uint8), a0_id (uint64), a1_id, (uint64), _ (uint64), _ (uint64), _ (uint64), pool (address/uint64)] 33 | const provider = Number("0x" + value.slice(0, 2)); 34 | const asset0Id = Number(`0x${value.slice(2, 34)}`); 35 | const asset1Id = Number(`0x${value.slice(34, 50)}`); 36 | 37 | // check if LP token is Tinyman or Pact 38 | if (provider == LPTokenProvider.TINYMAN) { 39 | const poolAddress = encodeAddress(Buffer.from(value.slice(82, 146), "hex")); 40 | return { provider, lpAssetId, asset0Id, asset1Id, lpPoolAddress: poolAddress }; 41 | } else if (provider == LPTokenProvider.PACT) { 42 | const poolAppId = Number("0x" + value.slice(82, 98)); 43 | return { provider, lpAssetId, asset0Id, asset1Id, lpPoolAppId: poolAppId }; 44 | } else { 45 | throw Error("Unknown LP Token type"); 46 | } 47 | } 48 | 49 | async function getTinymanLPPrice( 50 | client: Algodv2 | Indexer, 51 | validatorAppId: number, 52 | poolAddress: string, 53 | p0: bigint, 54 | p1: bigint, 55 | ): Promise { 56 | const { localState: state } = await getAccountApplicationLocalState(client, validatorAppId, poolAddress); 57 | if (state === undefined) 58 | throw new Error(`Unable to find Tinyman Pool: ${poolAddress} for validator app ${validatorAppId}.`); 59 | 60 | const r0 = BigInt(getParsedValueFromState(state, "s1") || 0); 61 | const r1 = BigInt(getParsedValueFromState(state, "s2") || 0); 62 | const lts = BigInt(getParsedValueFromState(state, "ilt") || 0); 63 | 64 | return calcLPPrice(r0, r1, p0, p1, lts); 65 | } 66 | 67 | async function getPactLPPrice(client: Algodv2 | Indexer, poolAppId: number, p0: bigint, p1: bigint): Promise { 68 | const { globalState: state } = await getApplicationGlobalState(client, poolAppId); 69 | if (state === undefined) throw new Error(`Unable to find Pact Pool: ${poolAppId}.`); 70 | 71 | const r0 = BigInt(getParsedValueFromState(state, "A") || 0); 72 | const r1 = BigInt(getParsedValueFromState(state, "B") || 0); 73 | const lts = BigInt(getParsedValueFromState(state, "L") || 0); 74 | 75 | return calcLPPrice(r0, r1, p0, p1, lts); 76 | } 77 | 78 | /** 79 | * 80 | * Returns oracle prices for given oracle and provided assets. 81 | * 82 | * @param client - Algorand client to query 83 | * @param oracle - oracle to query 84 | * @param assetIds - assets ids to get prices for, if undefined then returns all prices 85 | * @returns OraclePrices oracle prices 86 | */ 87 | async function getOraclePrices(client: Algodv2 | Indexer, oracle: Oracle, assetIds?: number[]): Promise { 88 | const { oracle0AppId, lpTokenOracle } = oracle; 89 | 90 | const lookupApps = [getApplicationGlobalState(client, oracle0AppId)]; 91 | if (lpTokenOracle) lookupApps.push(getApplicationGlobalState(client, lpTokenOracle.appId)); 92 | const [oracleRes, lpTokenOracleRes] = await Promise.all(lookupApps); 93 | 94 | const { currentRound, globalState: oracleState } = oracleRes; 95 | const lpTokenOracleState = lpTokenOracleRes?.globalState; 96 | 97 | if (oracleState === undefined) throw Error("Could not find Oracle"); 98 | if (lpTokenOracle && lpTokenOracleState === undefined) throw Error("Could not find LP Token Oracle"); 99 | 100 | const prices: Record = {}; 101 | 102 | // get the assets for which we need to retrieve their prices 103 | const allAssetIds: number[] = oracleState 104 | .concat(lpTokenOracleState || []) 105 | .filter(({ key }: TealKeyValue) => { 106 | // remove non asset ids global state 107 | key = Buffer.from(key, "base64").toString("utf8"); 108 | return key !== "updater_addr" && key !== "admin" && key !== "tinyman_validator_app_id" && key !== "td"; 109 | }) 110 | .map(({ key }: TealKeyValue) => { 111 | // convert key to asset id 112 | return decodeUint64(Buffer.from(key, "base64"), "safe"); 113 | }); 114 | const assets = assetIds ? assetIds : allAssetIds; 115 | 116 | // retrieve asset prices 117 | const retrievePrices = assets.map(async (assetId) => { 118 | let assetPrice: OraclePrice; 119 | const lpTokenBase64Value = lpTokenOracle 120 | ? getParsedValueFromState(lpTokenOracleState!, fromIntToBytes8Hex(assetId), "hex") 121 | : undefined; 122 | 123 | // lpTokenBase64Value defined iff asset is lp token in given lpTokenOracle 124 | if (lpTokenBase64Value !== undefined) { 125 | const lpTokenOracleValue = parseLPTokenOracleValue(assetId, lpTokenBase64Value as string); 126 | const { price: p0, timestamp: t0 } = parseOracleValue( 127 | String(getParsedValueFromState(oracleState, fromIntToBytes8Hex(lpTokenOracleValue.asset0Id), "hex")), 128 | ); 129 | const { price: p1, timestamp: t1 } = parseOracleValue( 130 | String(getParsedValueFromState(oracleState, fromIntToBytes8Hex(lpTokenOracleValue.asset1Id), "hex")), 131 | ); 132 | 133 | let price: bigint; 134 | switch (lpTokenOracleValue.provider) { 135 | case LPTokenProvider.TINYMAN: 136 | price = await getTinymanLPPrice( 137 | client, 138 | lpTokenOracle!.tinymanValidatorAppId, 139 | lpTokenOracleValue.lpPoolAddress, 140 | p0, 141 | p1, 142 | ); 143 | break; 144 | case LPTokenProvider.PACT: 145 | price = await getPactLPPrice(client, lpTokenOracleValue.lpPoolAppId, p0, p1); 146 | break; 147 | default: 148 | throw Error("Unknown LP Token provider"); 149 | } 150 | assetPrice = { price, timestamp: minimum(t0, t1) }; 151 | } else { 152 | assetPrice = parseOracleValue(String(getParsedValueFromState(oracleState, fromIntToBytes8Hex(assetId), "hex"))); 153 | } 154 | 155 | prices[assetId] = assetPrice; 156 | }); 157 | 158 | await Promise.all(retrievePrices); 159 | return { currentRound, prices }; 160 | } 161 | 162 | /** 163 | * 164 | * Returns a group transaction to refresh the given assets. 165 | * 166 | * @param oracle - oracle applications to use 167 | * @param userAddr - account address for the user 168 | * @param lpAssets - list of lp assets 169 | * @param baseAssetIds - list of base asset ids (non-lp assets) 170 | * @param params - suggested params for the transactions with the fees overwritten 171 | * @returns Transaction[] refresh prices group transaction 172 | */ 173 | function prepareRefreshPricesInOracleAdapter( 174 | oracle: Oracle, 175 | userAddr: string, 176 | lpAssets: LPToken[], 177 | baseAssetIds: number[], 178 | params: SuggestedParams, 179 | ): Transaction[] { 180 | const { oracleAdapterAppId, lpTokenOracle, oracle0AppId } = oracle; 181 | 182 | if (lpTokenOracle === undefined && lpAssets.length > 0) 183 | throw Error("Cannot refresh LP assets without LP Token Oracle"); 184 | 185 | const atc = new AtomicTransactionComposer(); 186 | 187 | // divide lp tokens into Tinyman and Pact 188 | const tinymanLPAssets: TinymanLPToken[] = lpAssets.filter( 189 | ({ provider }) => provider === LPTokenProvider.TINYMAN, 190 | ) as TinymanLPToken[]; 191 | const pactLPAssets: PactLPToken[] = lpAssets.filter( 192 | ({ provider }) => provider === LPTokenProvider.PACT, 193 | ) as PactLPToken[]; 194 | 195 | // update lp tokens 196 | const foreignAccounts: string[][] = []; 197 | const foreignApps: number[][] = []; 198 | 199 | const MAX_TINYMAN_UPDATE = 4; 200 | const MAX_PACT_UPDATE = 8; 201 | const MAX_COMBINATION_UPDATE = 7; 202 | let tinymanIndex = 0; 203 | let pactIndex = 0; 204 | 205 | while (tinymanIndex < tinymanLPAssets.length && pactIndex < pactLPAssets.length) { 206 | // retrieve which lp assets to update 207 | const tinymanLPUpdates = tinymanLPAssets.slice(tinymanIndex, tinymanIndex + MAX_TINYMAN_UPDATE); 208 | const maxPactUpdates = 209 | tinymanLPUpdates.length === 0 ? MAX_PACT_UPDATE : MAX_COMBINATION_UPDATE - tinymanLPUpdates.length; 210 | const pactLPUpdates = pactLPAssets.slice(pactIndex, pactIndex + maxPactUpdates); 211 | 212 | // prepare update lp tokens arguments 213 | const lpAssetIds = [ 214 | ...tinymanLPUpdates.map(({ lpAssetId }) => lpAssetId), 215 | ...pactLPUpdates.map(({ lpAssetId }) => lpAssetId), 216 | ]; 217 | 218 | // foreign arrays 219 | foreignAccounts.push(tinymanLPUpdates.map(({ lpPoolAddress }) => lpPoolAddress)); 220 | const apps: number[] = []; 221 | if (tinymanLPUpdates.length > 0) apps.push(lpTokenOracle!.tinymanValidatorAppId); 222 | for (const { lpPoolAppId } of pactLPUpdates) apps.push(lpPoolAppId); 223 | foreignApps.push(apps); 224 | 225 | // update lp 226 | atc.addMethodCall({ 227 | sender: userAddr, 228 | signer, 229 | appID: lpTokenOracle!.appId, 230 | method: getMethodByName(lpTokenOracleABIContract.methods, "update_lp_tokens"), 231 | methodArgs: [lpAssetIds], 232 | suggestedParams: { ...params, flatFee: true, fee: 1000 }, 233 | }); 234 | 235 | // increase indexes 236 | tinymanIndex += tinymanLPUpdates.length; 237 | pactIndex += pactLPUpdates.length; 238 | } 239 | 240 | // prepare refresh prices arguments 241 | const oracle1AppId = oracle.oracle1AppId || 0; 242 | const lpTokenOracleAppId = lpTokenOracle?.appId || 0; 243 | const lpAssetIds = lpAssets.map(({ lpAssetId }) => lpAssetId); 244 | 245 | // refresh prices 246 | atc.addMethodCall({ 247 | sender: userAddr, 248 | signer, 249 | appID: oracleAdapterAppId, 250 | method: getMethodByName(oracleAdapterABIContract.methods, "refresh_prices"), 251 | methodArgs: [lpAssetIds, baseAssetIds, oracle0AppId, oracle1AppId, lpTokenOracleAppId], 252 | suggestedParams: { ...params, flatFee: true, fee: 1000 }, 253 | }); 254 | 255 | // build 256 | return atc.buildGroup().map(({ txn }, index) => { 257 | if (index < foreignAccounts.length && index < foreignApps.length) { 258 | txn.appAccounts = foreignAccounts[index].map((address) => decodeAddress(address)); 259 | txn.appForeignApps = foreignApps[index]; 260 | } 261 | txn.group = undefined; 262 | return txn; 263 | }); 264 | } 265 | 266 | export { getOraclePrices, prepareRefreshPricesInOracleAdapter }; 267 | -------------------------------------------------------------------------------- /src/lend/types.ts: -------------------------------------------------------------------------------- 1 | enum LPTokenProvider { 2 | TINYMAN = 0, 3 | PACT = 1, 4 | } 5 | 6 | interface BaseLPToken { 7 | provider: LPTokenProvider; 8 | lpAssetId: number; 9 | asset0Id: number; 10 | asset1Id: number; 11 | } 12 | 13 | interface TinymanLPToken extends BaseLPToken { 14 | provider: LPTokenProvider.TINYMAN; 15 | lpPoolAddress: string; 16 | } 17 | 18 | interface PactLPToken extends BaseLPToken { 19 | provider: LPTokenProvider.PACT; 20 | lpPoolAppId: number; 21 | } 22 | 23 | type LPToken = TinymanLPToken | PactLPToken; 24 | 25 | interface BaseLendingPool extends BaseLPToken { 26 | pool0AppId: number; 27 | pool1AppId: number; 28 | feeScale: bigint; 29 | } 30 | 31 | interface PactLendingPool extends BaseLendingPool { 32 | provider: LPTokenProvider.PACT; 33 | lpPoolAppId: number; 34 | } 35 | 36 | interface TinymanLendingPool extends BaseLendingPool { 37 | provider: LPTokenProvider.TINYMAN; 38 | lpPoolAppAddress: string; 39 | } 40 | 41 | type LendingPool = PactLendingPool | TinymanLendingPool; 42 | 43 | interface BaseLendingPoolInfo { 44 | currentRound?: number; 45 | fAsset0Supply: bigint; 46 | fAsset1Supply: bigint; 47 | liquidityTokenCirculatingSupply: bigint; 48 | fee: bigint; 49 | swapFeeInterestRate: bigint; // 16 d.p. 50 | swapFeeInterestYield: bigint; // 16 d.p. 51 | asset0DepositInterestRate: bigint; // 16 d.p. 52 | asset0DepositInterestYield: bigint; // approximation 16 d.p. 53 | asset1DepositInterestRate: bigint; // 16 d.p. 54 | asset1DepositInterestYield: bigint; // approximation 16 d.p. 55 | additionalInterestRate?: bigint; // 16 d.p. 56 | additionalInterestYield?: bigint; // approximation 16 d.p. 57 | tvlUsd: number; 58 | } 59 | 60 | type PactLendingPoolInfo = BaseLendingPoolInfo; 61 | interface TinymanLendingPoolInfo extends BaseLendingPoolInfo { 62 | farmInterestYield: bigint; // 16 d.p. 63 | } 64 | 65 | interface PoolManagerInfo { 66 | currentRound?: number; 67 | pools: Partial< 68 | Record< 69 | number, 70 | { 71 | // poolAppId -> ... 72 | variableBorrowInterestRate: bigint; // 16 d.p. 73 | variableBorrowInterestYield: bigint; // approximation 16 d.p. 74 | variableBorrowInterestIndex: bigint; // 14 d.p. 75 | depositInterestRate: bigint; // 16 d.p. 76 | depositInterestYield: bigint; // approximation 16 d.p. 77 | depositInterestIndex: bigint; // 14 d.p. 78 | metadata: { 79 | oldVariableBorrowInterestIndex: bigint; // 14 d.p. 80 | oldDepositInterestIndex: bigint; // 14 d.p. 81 | oldTimestamp: bigint; 82 | }; 83 | } 84 | > 85 | >; 86 | } 87 | 88 | interface BasePool { 89 | appId: number; 90 | assetId: number; 91 | fAssetId: number; 92 | frAssetId: number; 93 | assetDecimals: number; 94 | poolManagerIndex: number; 95 | loans: Partial>; // loanAppId -> loanIndex 96 | } 97 | 98 | interface LPTokenPool extends BasePool { 99 | lpToken: LPToken; 100 | } 101 | 102 | type Pool = BasePool | LPTokenPool; 103 | 104 | interface PoolInfo { 105 | currentRound?: number; 106 | variableBorrow: { 107 | vr0: bigint; // 16 d.p. 108 | vr1: bigint; // 16 d.p. 109 | vr2: bigint; // 16 d.p. 110 | totalVariableBorrowAmount: bigint; 111 | variableBorrowInterestRate: bigint; // 16 d.p. 112 | variableBorrowInterestYield: bigint; // approximation 16 d.p. 113 | variableBorrowInterestIndex: bigint; // 14 d.p. 114 | }; 115 | stableBorrow: { 116 | sr0: bigint; // 16 d.p. 117 | sr1: bigint; // 16 d.p. 118 | sr2: bigint; // 16 d.p. 119 | sr3: bigint; // 16 d.p. 120 | optimalStableToTotalDebtRatio: bigint; // 16 d.p. 121 | rebalanceUpUtilisationRatio: bigint; // 16 d.p. 122 | rebalanceUpDepositInterestRate: bigint; // 16 d.p. 123 | rebalanceDownDelta: bigint; // 16 d.p. 124 | totalStableBorrowAmount: bigint; 125 | stableBorrowInterestRate: bigint; // 16 d.p. 126 | stableBorrowInterestYield: bigint; // approximation 16 d.p. 127 | overallStableBorrowInterestAmount: bigint; // 16 d.p. 128 | }; 129 | interest: { 130 | retentionRate: bigint; // 16 d.p. 131 | flashLoanFee: bigint; // 16 d.p. 132 | optimalUtilisationRatio: bigint; // 16 d.p. 133 | totalDeposits: bigint; 134 | depositInterestRate: bigint; // 16 d.p. 135 | depositInterestYield: bigint; // approximation 16 d.p. 136 | depositInterestIndex: bigint; // 14 d.p. 137 | latestUpdate: bigint; 138 | }; 139 | caps: { 140 | borrowCap: bigint; // $ value 141 | stableBorrowPercentageCap: bigint; // 16 d.p. 142 | }; 143 | config: { 144 | depreciated: boolean; 145 | rewardsPaused: boolean; 146 | stableBorrowSupported: boolean; 147 | flashLoanSupported: boolean; 148 | }; 149 | } 150 | 151 | type UserDepositInfo = { 152 | currentRound?: number; 153 | escrowAddress: string; 154 | holdings: { 155 | fAssetId: number; 156 | fAssetBalance: bigint; 157 | }[]; 158 | }; 159 | 160 | type UserDepositFullInfo = { 161 | currentRound?: number; 162 | escrowAddress: string; 163 | holdings: { 164 | fAssetId: number; 165 | fAssetBalance: bigint; 166 | poolAppId: number; 167 | assetId: number; 168 | assetPrice: bigint; // 14 d.p. 169 | assetBalance: bigint; 170 | balanceValue: bigint; // in $, 4 d.p. 171 | interestRate: bigint; // 16 d.p. 172 | interestYield: bigint; // approximation 16 d.p. 173 | }[]; 174 | }; 175 | 176 | interface DepositStakingInfo { 177 | currentRound?: number; 178 | stakingPrograms: { 179 | poolAppId: number; 180 | totalStaked: bigint; 181 | minTotalStaked: bigint; 182 | stakeIndex: number; 183 | rewards: { 184 | rewardAssetId: number; 185 | endTimestamp: bigint; 186 | rewardRate: bigint; // 10 d.p. 187 | rewardPerToken: bigint; // 10 d.p. 188 | }[]; 189 | }[]; 190 | } 191 | 192 | interface DepositStakingProgramInfo { 193 | poolAppId: number; 194 | stakeIndex: number; 195 | fAssetId: number; 196 | fAssetTotalStakedAmount: bigint; 197 | assetId: number; 198 | assetPrice: bigint; // 14 d.p. 199 | assetTotalStakedAmount: bigint; 200 | totalStakedAmountValue: bigint; // in $, 4 d.p. 201 | depositInterestRate: bigint; // 16 d.p. 202 | depositInterestYield: bigint; // approximation 16 d.p. 203 | rewards: { 204 | rewardAssetId: number; 205 | endTimestamp: bigint; 206 | rewardRate: bigint; // 10 d.p. 207 | rewardPerToken: bigint; // 10 d.p. 208 | rewardAssetPrice: bigint; // 14 d.p. 209 | rewardInterestRate: bigint; // 0 if past reward end timestamp, 16 d.p. 210 | }[]; 211 | } 212 | 213 | interface UserDepositStakingLocalState { 214 | currentRound?: number; 215 | userAddress: string; 216 | escrowAddress: string; 217 | optedIntoAssets: Set; 218 | stakedAmounts: bigint[]; 219 | rewardPerTokens: bigint[]; // 10 d.p. 220 | unclaimedRewards: bigint[]; 221 | } 222 | 223 | interface UserDepositStakingProgramInfo { 224 | poolAppId: number; 225 | fAssetId: number; 226 | fAssetStakedAmount: bigint; 227 | assetId: number; 228 | assetPrice: bigint; // 14 d.p. 229 | assetStakedAmount: bigint; 230 | stakedAmountValue: bigint; // in $, 4 d.p. 231 | depositInterestRate: bigint; // 16 d.p. 232 | depositInterestYield: bigint; // approximation 16 d.p. 233 | rewards: { 234 | rewardAssetId: number; 235 | endTimestamp: bigint; 236 | rewardAssetPrice: bigint; // 14 d.p. 237 | rewardInterestRate: bigint; // 0 if past reward end timestamp, 16 d.p. 238 | unclaimedReward: bigint; 239 | unclaimedRewardValue: bigint; // in $, 4 d.p. 240 | }[]; 241 | } 242 | 243 | interface UserDepositStakingInfo { 244 | currentRound?: number; 245 | userAddress: string; 246 | escrowAddress: string; 247 | optedIntoAssets: Set; 248 | stakingPrograms: UserDepositStakingProgramInfo[]; 249 | } 250 | 251 | interface PoolLoanInfo { 252 | poolAppId: number; 253 | assetId: number; 254 | collateralCap: bigint; // $ value 255 | collateralUsed: bigint; 256 | collateralFactor: bigint; // 4 d.p. 257 | borrowFactor: bigint; // 4 d.p. 258 | liquidationMax: bigint; // 4 d.p. 259 | liquidationBonus: bigint; // 4 d.p. 260 | liquidationFee: bigint; // 4 d.p. 261 | } 262 | 263 | enum LoanType { 264 | "GENERAL" = "GENERAL", 265 | "STABLECOIN_EFFICIENCY" = "STABLECOIN_EFFICIENCY", 266 | "ALGO_EFFICIENCY" = "ALGO_EFFICIENCY", 267 | "ULTRASWAP_UP" = "ULTRASWAP_UP", 268 | "ULTRASWAP_DOWN" = "ULTRASWAP_DOWN", 269 | } 270 | 271 | interface LoanInfo { 272 | currentRound?: number; 273 | canSwapCollateral: boolean; 274 | pools: Partial>; // poolAppId -> PoolLoanInfo 275 | } 276 | 277 | interface LoanLocalState { 278 | currentRound?: number; 279 | userAddress: string; 280 | escrowAddress: string; 281 | collaterals: { 282 | poolAppId: number; 283 | fAssetBalance: bigint; 284 | }[]; 285 | borrows: { 286 | poolAppId: number; 287 | borrowedAmount: bigint; 288 | borrowBalance: bigint; 289 | latestBorrowInterestIndex: bigint; // 14 d.p. 290 | stableBorrowInterestRate: bigint; // 16 d.p. 291 | latestStableChange: bigint; 292 | }[]; 293 | } 294 | 295 | interface UserLoanInfoCollateral { 296 | poolAppId: number; 297 | assetId: number; 298 | assetPrice: bigint; // 14 d.p. 299 | collateralFactor: bigint; // 4 d.p. 300 | depositInterestIndex: bigint; // 14 d.p. 301 | fAssetBalance: bigint; 302 | assetBalance: bigint; 303 | balanceValue: bigint; // in $, 4 d.p. 304 | effectiveBalanceValue: bigint; // in $, 4 d.p. 305 | interestRate: bigint; // 16 d.p. 306 | interestYield: bigint; // approximation 16 d.p. 307 | } 308 | 309 | interface UserLoanInfoBorrow { 310 | poolAppId: number; 311 | assetId: number; 312 | assetPrice: bigint; // 14 d.p. 313 | isStable: boolean; 314 | borrowFactor: bigint; // 4 d.p. 315 | borrowedAmount: bigint; 316 | borrowedAmountValue: bigint; // in $, 4 d.p. 317 | borrowBalance: bigint; 318 | borrowBalanceValue: bigint; // in $, 4 d.p. 319 | effectiveBorrowBalanceValue: bigint; // in $, 4 d.p. 320 | accruedInterest: bigint; 321 | accruedInterestValue: bigint; // in $, 4 d.p. 322 | interestRate: bigint; // 16 d.p. 323 | interestYield: bigint; // approximation 16 d.p. 324 | } 325 | 326 | interface UserLoanInfo { 327 | currentRound?: number; 328 | userAddress: string; 329 | escrowAddress: string; 330 | collaterals: UserLoanInfoCollateral[]; 331 | borrows: UserLoanInfoBorrow[]; 332 | netRate: bigint; // 16 d.p. - negative indicates losing more on borrows than gaining on collaterals 333 | netYield: bigint; // 16 d.p. - negative indicates losing more on borrows than gaining on collaterals 334 | totalCollateralBalanceValue: bigint; // in $, 4 d.p. 335 | totalBorrowedAmountValue: bigint; // in $, 4 d.p. 336 | totalBorrowBalanceValue: bigint; // in $, 4 d.p. 337 | totalEffectiveCollateralBalanceValue: bigint; // in $, 4 d.p. - used to determine if liquidatable 338 | totalEffectiveBorrowBalanceValue: bigint; // in $, 4 d.p. - used to determine if liquidatable 339 | loanToValueRatio: bigint; // 4 d.p. 340 | borrowUtilisationRatio: bigint; // 4 d.p. 341 | liquidationMargin: bigint; // 4 d.p. 342 | } 343 | 344 | interface AssetAdditionalInterest { 345 | rateBps: bigint; // 4 d.p. 346 | yieldBps: bigint; // 4 d.p. 347 | } 348 | 349 | type AssetsAdditionalInterest = Partial>; // assetId -> interest 350 | 351 | interface Oracle { 352 | oracle0AppId: number; 353 | oracle1AppId?: number; 354 | lpTokenOracle?: { 355 | appId: number; 356 | tinymanValidatorAppId: number; 357 | }; 358 | oracleAdapterAppId: number; 359 | decimals: number; 360 | } 361 | 362 | interface OraclePrice { 363 | price: bigint; // price in USD for amount 1 of asset in lowest denomination 364 | timestamp: bigint; 365 | } 366 | 367 | interface OraclePrices { 368 | currentRound?: number; 369 | prices: Partial>; // assetId -> OraclePrice 370 | } 371 | 372 | interface OpUp { 373 | callerAppId: number; 374 | baseAppId: number; 375 | } 376 | 377 | type ReserveAddress = string; 378 | 379 | export { 380 | LPTokenProvider, 381 | TinymanLPToken, 382 | PactLPToken, 383 | LPToken, 384 | PactLendingPool, 385 | TinymanLendingPool, 386 | LendingPool, 387 | BaseLendingPoolInfo, 388 | PactLendingPoolInfo, 389 | TinymanLendingPoolInfo, 390 | PoolManagerInfo, 391 | BasePool, 392 | LPTokenPool, 393 | Pool, 394 | PoolInfo, 395 | UserDepositInfo, 396 | UserDepositFullInfo, 397 | DepositStakingInfo, 398 | DepositStakingProgramInfo, 399 | UserDepositStakingLocalState, 400 | UserDepositStakingProgramInfo, 401 | UserDepositStakingInfo, 402 | PoolLoanInfo, 403 | LoanType, 404 | LoanLocalState, 405 | LoanInfo, 406 | UserLoanInfoCollateral, 407 | UserLoanInfoBorrow, 408 | UserLoanInfo, 409 | AssetAdditionalInterest, 410 | AssetsAdditionalInterest, 411 | Oracle, 412 | OraclePrice, 413 | OraclePrices, 414 | OpUp, 415 | ReserveAddress, 416 | }; 417 | -------------------------------------------------------------------------------- /src/math-lib.ts: -------------------------------------------------------------------------------- 1 | const SECONDS_IN_YEAR = BigInt(365 * 24 * 60 * 60); 2 | const HOURS_IN_YEAR = BigInt(365 * 24); 3 | 4 | const ONE_2_DP = BigInt(1e2); 5 | const ONE_4_DP = BigInt(1e4); 6 | const ONE_10_DP = BigInt(1e10); 7 | const ONE_12_DP = BigInt(1e12); 8 | const ONE_14_DP = BigInt(1e14); 9 | const ONE_16_DP = BigInt(1e16); 10 | 11 | const UINT64 = BigInt(2) << BigInt(63); 12 | const UINT128 = BigInt(2) << BigInt(127); 13 | 14 | function maximum(n1: bigint, n2: bigint): bigint { 15 | return n1 > n2 ? n1 : n2; 16 | } 17 | 18 | function minimum(n1: bigint, n2: bigint): bigint { 19 | return n1 < n2 ? n1 : n2; 20 | } 21 | 22 | function mulScale(n1: bigint, n2: bigint, scale: bigint): bigint { 23 | return (n1 * n2) / scale; 24 | } 25 | 26 | function mulScaleRoundUp(n1: bigint, n2: bigint, scale: bigint): bigint { 27 | return mulScale(n1, n2, scale) + BigInt(1); 28 | } 29 | 30 | function divScale(n1: bigint, n2: bigint, scale: bigint): bigint { 31 | return (n1 * scale) / n2; 32 | } 33 | 34 | function divScaleRoundUp(n1: bigint, n2: bigint, scale: bigint): bigint { 35 | return divScale(n1, n2, scale) + BigInt(1); 36 | } 37 | 38 | function expBySquaring(x: bigint, n: bigint, scale: bigint): bigint { 39 | if (n === BigInt(0)) return scale; 40 | 41 | let y = scale; 42 | while (n > BigInt(1)) { 43 | if (n % BigInt(2)) { 44 | y = mulScale(x, y, scale); 45 | n = (n - BigInt(1)) / BigInt(2); 46 | } else { 47 | n = n / BigInt(2); 48 | } 49 | x = mulScale(x, x, scale); 50 | } 51 | return mulScale(x, y, scale); 52 | } 53 | 54 | function compound(rate: bigint, scale: bigint, period: bigint): bigint { 55 | return expBySquaring(scale + rate / period, period, scale) - scale; 56 | } 57 | 58 | function compoundEverySecond(rate: bigint, scale: bigint): bigint { 59 | return compound(rate, scale, SECONDS_IN_YEAR); 60 | } 61 | 62 | function compoundEveryHour(rate: bigint, scale: bigint): bigint { 63 | return compound(rate, scale, HOURS_IN_YEAR); 64 | } 65 | 66 | function sqrt(value: bigint): bigint { 67 | if (value < BigInt(0)) throw Error("square root of negative numbers is not supported"); 68 | 69 | if (value < BigInt(2)) return value; 70 | 71 | function newtonIteration(n: bigint, x0: bigint): bigint { 72 | const x1 = (n / x0 + x0) >> BigInt(1); 73 | if (x0 === x1 || x0 === x1 - BigInt(1)) return x0; 74 | return newtonIteration(n, x1); 75 | } 76 | 77 | return newtonIteration(value, BigInt(1)); 78 | } 79 | 80 | export { 81 | SECONDS_IN_YEAR, 82 | HOURS_IN_YEAR, 83 | ONE_16_DP, 84 | ONE_14_DP, 85 | ONE_12_DP, 86 | ONE_10_DP, 87 | ONE_4_DP, 88 | ONE_2_DP, 89 | UINT64, 90 | UINT128, 91 | maximum, 92 | minimum, 93 | mulScale, 94 | mulScaleRoundUp, 95 | divScale, 96 | divScaleRoundUp, 97 | expBySquaring, 98 | compoundEverySecond, 99 | compoundEveryHour, 100 | sqrt, 101 | }; 102 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Algodv2, 3 | decodeAddress, 4 | getApplicationAddress, 5 | makeAssetTransferTxnWithSuggestedParamsFromObject, 6 | makePaymentTxnWithSuggestedParamsFromObject, 7 | } from "algosdk"; 8 | 9 | import type { Indexer, SuggestedParams, Transaction } from "algosdk"; 10 | import type { Box, TealKeyValue } from "algosdk/dist/types/client/v2/algod/models/types"; 11 | 12 | const enc = new TextEncoder(); 13 | 14 | /** 15 | * Transfer algo or asset. 0 assetId indicates algo transfer, else asset transfer. 16 | */ 17 | function transferAlgoOrAsset( 18 | assetId: number, 19 | from: string, 20 | to: string, 21 | amount: number | bigint, 22 | params: SuggestedParams, 23 | ): Transaction { 24 | return assetId !== 0 25 | ? makeAssetTransferTxnWithSuggestedParamsFromObject({ 26 | from, 27 | to, 28 | amount, 29 | suggestedParams: params, 30 | assetIndex: assetId, 31 | }) 32 | : makePaymentTxnWithSuggestedParamsFromObject({ from, to, amount, suggestedParams: params }); 33 | } 34 | 35 | const signer = async () => []; 36 | 37 | function unixTime(): number { 38 | return Math.floor(Date.now() / 1000); 39 | } 40 | 41 | const PAYOUTS_GO_ONLINE_FEE = BigInt(2e6); 42 | 43 | /** 44 | * Wraps a call to Algorand client (algod/indexer) and returns global state 45 | */ 46 | async function getApplicationGlobalState( 47 | client: Algodv2 | Indexer, 48 | appId: number, 49 | ): Promise<{ 50 | currentRound?: number; 51 | globalState?: TealKeyValue[]; 52 | }> { 53 | const res = await ( 54 | client instanceof Algodv2 ? client.getApplicationByID(appId) : client.lookupApplications(appId) 55 | ).do(); 56 | 57 | // algod https://developer.algorand.org/docs/rest-apis/algod/#application 58 | // indexer https://developer.algorand.org/docs/rest-apis/indexer/#lookupapplicationbyid-response-200 59 | const app = client instanceof Algodv2 ? res : res["application"]; 60 | 61 | return { 62 | currentRound: res["current-round"], 63 | globalState: app["params"]["global-state"], 64 | }; 65 | } 66 | 67 | /** 68 | * Wraps a call to Algorand client (algod/indexer) and returns local state 69 | */ 70 | async function getAccountApplicationLocalState( 71 | client: Algodv2 | Indexer, 72 | appId: number, 73 | addr: string, 74 | ): Promise<{ 75 | currentRound?: number; 76 | localState?: TealKeyValue[]; 77 | }> { 78 | const res = await ( 79 | client instanceof Algodv2 80 | ? client.accountApplicationInformation(addr, appId) 81 | : client.lookupAccountAppLocalStates(addr).applicationID(appId) 82 | ).do(); 83 | 84 | // algod https://developer.algorand.org/docs/rest-apis/algod/#accountapplicationinformation-response-200 85 | // indexer https://developer.algorand.org/docs/rest-apis/indexer/#lookupaccountapplocalstates-response-200 86 | const localState = 87 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 88 | client instanceof Algodv2 ? res["app-local-state"] : res["apps-local-states"]?.find(({ id }: any) => id === appId); 89 | 90 | return { 91 | currentRound: res["current-round"], 92 | localState: localState["key-value"], 93 | }; 94 | } 95 | 96 | /** 97 | * Wraps a call to Algorand client (algod/indexer) and returns box value 98 | */ 99 | async function getApplicationBox(client: Algodv2 | Indexer, appId: number, boxName: Uint8Array): Promise { 100 | return await ( 101 | client instanceof Algodv2 102 | ? client.getApplicationBoxByName(appId, boxName) 103 | : client.lookupApplicationBoxByIDandName(appId, boxName) 104 | ).do(); 105 | } 106 | 107 | /** 108 | * Wraps a call to Algorand client (algod/indexer) and returns account details 109 | */ 110 | async function getAccountDetails( 111 | client: Algodv2 | Indexer, 112 | addr: string, 113 | ): Promise<{ 114 | currentRound?: number; 115 | isOnline: boolean; 116 | holdings: Map; 117 | }> { 118 | const holdings: Map = new Map(); 119 | 120 | try { 121 | const res = await ( 122 | client instanceof Algodv2 123 | ? client.accountInformation(addr) 124 | : client.lookupAccountByID(addr).exclude("apps-local-state,created-apps") 125 | ).do(); 126 | 127 | // algod https://developer.algorand.org/docs/rest-apis/algod/#account 128 | // indexer https://developer.algorand.org/docs/rest-apis/indexer/#lookupaccountbyid-response-200 129 | const account = client instanceof Algodv2 ? res : res["account"]; 130 | const assets = account["assets"] || []; 131 | 132 | holdings.set(0, BigInt(account["amount"])); // includes min balance 133 | 134 | for (const asset of assets) { 135 | holdings.set(asset["asset-id"], BigInt(asset["amount"])); 136 | } 137 | 138 | return { 139 | currentRound: res["current-round"], 140 | isOnline: account["status"] === "Online", 141 | holdings, 142 | }; 143 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 144 | } catch (e: any) { 145 | if (e.status === 404 && e.response?.text?.includes("no accounts found for address")) { 146 | holdings.set(0, BigInt(0)); 147 | 148 | return { 149 | isOnline: false, 150 | holdings, 151 | }; 152 | } 153 | throw e; 154 | } 155 | } 156 | 157 | /** 158 | * Convert an int to its hex representation with a fixed length of 8 bytes. 159 | */ 160 | function fromIntToBytes8Hex(num: number | bigint) { 161 | return num.toString(16).padStart(16, "0"); 162 | } 163 | 164 | /** 165 | * Convert an int to its hex representation with a fixed length of 1 byte. 166 | */ 167 | function fromIntToByteHex(num: number | bigint) { 168 | return num.toString(16).padStart(2, "0"); 169 | } 170 | 171 | function encodeToBase64(str: string, encoding: BufferEncoding = "utf8") { 172 | return Buffer.from(str, encoding).toString("base64"); 173 | } 174 | 175 | function getParsedValueFromState( 176 | state: TealKeyValue[], 177 | key: string, 178 | encoding: BufferEncoding = "utf8", 179 | ): string | bigint | undefined { 180 | const encodedKey: string = encoding ? encodeToBase64(key, encoding) : key; 181 | const keyValue: TealKeyValue | undefined = state.find((entry) => entry.key === encodedKey); 182 | if (keyValue === undefined) return; 183 | const { value } = keyValue; 184 | if (value.type === 1) return value.bytes; 185 | if (value.type === 2) return BigInt(value.uint); 186 | return; 187 | } 188 | 189 | function parseUint64s(base64Value: string): bigint[] { 190 | const value = Buffer.from(base64Value, "base64").toString("hex"); 191 | 192 | // uint64s are 8 bytes each 193 | const uint64s: bigint[] = []; 194 | for (let i = 0; i < value.length; i += 16) { 195 | uint64s.push(BigInt("0x" + value.slice(i, i + 16))); 196 | } 197 | return uint64s; 198 | } 199 | 200 | function parseUint8s(base64Value: string): bigint[] { 201 | const value = Buffer.from(base64Value, "base64").toString("hex"); 202 | // uint8s are 1 byte each 203 | const uint8s: bigint[] = []; 204 | for (let i = 0; i < value.length; i += 2) { 205 | uint8s.push(BigInt("0x" + value.slice(i, i + 2))); 206 | } 207 | return uint8s; 208 | } 209 | 210 | function parseBitsAsBooleans(base64Value: string): boolean[] { 211 | const value = Buffer.from(base64Value, "base64").toString("hex"); 212 | const bits = ("00000000" + Number("0x" + value).toString(2)).slice(-8); 213 | const bools: boolean[] = []; 214 | for (const bit of bits) { 215 | bools.push(Boolean(parseInt(bit))); 216 | } 217 | return bools; 218 | } 219 | 220 | function addEscrowNoteTransaction( 221 | userAddr: string, 222 | escrowAddr: string, 223 | appId: number, 224 | notePrefix: string, 225 | params: SuggestedParams, 226 | ): Transaction { 227 | const note = Uint8Array.from([...enc.encode(notePrefix), ...decodeAddress(escrowAddr).publicKey]); 228 | return makePaymentTxnWithSuggestedParamsFromObject({ 229 | from: userAddr, 230 | to: getApplicationAddress(appId), 231 | amount: 0, 232 | note, 233 | suggestedParams: params, 234 | }); 235 | } 236 | 237 | function removeEscrowNoteTransaction( 238 | escrowAddr: string, 239 | userAddr: string, 240 | notePrefix: string, 241 | params: SuggestedParams, 242 | ): Transaction { 243 | const note = Uint8Array.from([...enc.encode(notePrefix), ...decodeAddress(escrowAddr).publicKey]); 244 | return makePaymentTxnWithSuggestedParamsFromObject({ 245 | from: escrowAddr, 246 | to: userAddr, 247 | amount: 0, 248 | closeRemainderTo: userAddr, 249 | note, 250 | suggestedParams: params, 251 | }); 252 | } 253 | 254 | export { 255 | enc, 256 | transferAlgoOrAsset, 257 | signer, 258 | PAYOUTS_GO_ONLINE_FEE, 259 | unixTime, 260 | getApplicationGlobalState, 261 | getAccountApplicationLocalState, 262 | getApplicationBox, 263 | getAccountDetails, 264 | fromIntToBytes8Hex, 265 | fromIntToByteHex, 266 | getParsedValueFromState, 267 | parseUint64s, 268 | parseUint8s, 269 | parseBitsAsBooleans, 270 | addEscrowNoteTransaction, 271 | removeEscrowNoteTransaction, 272 | }; 273 | -------------------------------------------------------------------------------- /src/xalgo/abi-contracts/index.ts: -------------------------------------------------------------------------------- 1 | import { ABIContract } from "algosdk"; 2 | 3 | import stakeAndDepositABI from "./stake_and_deposit.json"; 4 | import xAlgoABI from "./xalgo.json"; 5 | 6 | export const xAlgoABIContract = new ABIContract(xAlgoABI); 7 | export const stakeAndDepositABIContract = new ABIContract(stakeAndDepositABI); 8 | -------------------------------------------------------------------------------- /src/xalgo/abi-contracts/stake_and_deposit.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StakeAndDeposit", 3 | "methods": [ 4 | { 5 | "name": "stake_and_deposit", 6 | "args": [ 7 | { 8 | "type": "pay", 9 | "name": "send_algo" 10 | }, 11 | { 12 | "type": "application", 13 | "name": "consensus" 14 | }, 15 | { 16 | "type": "application", 17 | "name": "pool" 18 | }, 19 | { 20 | "type": "application", 21 | "name": "pool_manager" 22 | }, 23 | { 24 | "type": "asset", 25 | "name": "x_algo" 26 | }, 27 | { 28 | "type": "asset", 29 | "name": "f_x_algo" 30 | }, 31 | { 32 | "type": "account", 33 | "name": "receiver" 34 | }, 35 | { 36 | "type": "uint64", 37 | "name": "min_x_algo_received" 38 | } 39 | ], 40 | "returns": { 41 | "type": "void" 42 | } 43 | } 44 | ], 45 | "networks": {} 46 | } 47 | -------------------------------------------------------------------------------- /src/xalgo/abi-contracts/xalgo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XAlgo", 3 | "desc": "Allows users to participate in consensus and receive a liquid staking token", 4 | "methods": [ 5 | { 6 | "name": "claim_fee", 7 | "desc": "Send the unclaimed fees to the admin.", 8 | "args": [], 9 | "returns": { 10 | "type": "void" 11 | } 12 | }, 13 | { 14 | "name": "set_proposer_admin", 15 | "desc": "Privileged operation to set the proposer admin.", 16 | "args": [ 17 | { 18 | "type": "uint8", 19 | "name": "proposer_index", 20 | "desc": "The index of proposer to set the admin of" 21 | }, 22 | { 23 | "type": "address", 24 | "name": "new_proposer_admin", 25 | "desc": "The new proposer admin" 26 | } 27 | ], 28 | "returns": { 29 | "type": "void" 30 | } 31 | }, 32 | { 33 | "name": "register_online", 34 | "desc": "Privileged operation to register a proposer online", 35 | "args": [ 36 | { 37 | "type": "pay", 38 | "name": "send_algo", 39 | "desc": "Send ALGO to the proposer to pay for the register online fee" 40 | }, 41 | { 42 | "type": "uint8", 43 | "name": "proposer_index", 44 | "desc": "The index of proposer to register online with" 45 | }, 46 | { 47 | "type": "address", 48 | "name": "vote_key", 49 | "desc": "The root participation public key (if any) currently registered for this round" 50 | }, 51 | { 52 | "type": "address", 53 | "name": "sel_key", 54 | "desc": "The selection public key (if any) currently registered for this round" 55 | }, 56 | { 57 | "type": "byte[64]", 58 | "name": "state_proof_key", 59 | "desc": "The root of the state proof key (if any)" 60 | }, 61 | { 62 | "type": "uint64", 63 | "name": "vote_first", 64 | "desc": "The first round for which this participation is valid" 65 | }, 66 | { 67 | "type": "uint64", 68 | "name": "vote_last", 69 | "desc": "The last round for which this participation is valid" 70 | }, 71 | { 72 | "type": "uint64", 73 | "name": "vote_key_dilution", 74 | "desc": "The number of subkeys in each batch of participation keys" 75 | } 76 | ], 77 | "returns": { 78 | "type": "void" 79 | } 80 | }, 81 | { 82 | "name": "register_offline", 83 | "desc": "Privileged operation to register a proposer offline", 84 | "args": [ 85 | { 86 | "type": "uint8", 87 | "name": "proposer_index", 88 | "desc": "The index of proposer to register offline with" 89 | } 90 | ], 91 | "returns": { 92 | "type": "void" 93 | } 94 | }, 95 | { 96 | "name": "immediate_mint", 97 | "desc": "Send ALGO to the app and receive xALGO immediately", 98 | "args": [ 99 | { 100 | "type": "pay", 101 | "name": "send_algo", 102 | "desc": "Send ALGO to the app to mint" 103 | }, 104 | { 105 | "type": "address", 106 | "name": "receiver", 107 | "desc": "The address to receiver the xALGO at" 108 | }, 109 | { 110 | "type": "uint64", 111 | "name": "min_received", 112 | "desc": "The minimum amount of xALGO to receive in return" 113 | } 114 | ], 115 | "returns": { 116 | "type": "void" 117 | } 118 | }, 119 | { 120 | "name": "delayed_mint", 121 | "desc": "Send ALGO to the app and receive xALGO after 320 rounds", 122 | "args": [ 123 | { 124 | "type": "pay", 125 | "name": "send_algo", 126 | "desc": "Send ALGO to the app to mint" 127 | }, 128 | { 129 | "type": "address", 130 | "name": "receiver", 131 | "desc": "The address to receiver the xALGO at" 132 | }, 133 | { 134 | "type": "byte[2]", 135 | "name": "nonce", 136 | "desc": "The nonce used to create the box to store the delayed mint" 137 | } 138 | ], 139 | "returns": { 140 | "type": "void" 141 | } 142 | }, 143 | { 144 | "name": "claim_delayed_mint", 145 | "desc": "Claim delayed mint after 320 rounds", 146 | "args": [ 147 | { 148 | "type": "address", 149 | "name": "minter", 150 | "desc": "The address which submitted the delayed mint" 151 | }, 152 | { 153 | "type": "byte[2]", 154 | "name": "nonce", 155 | "desc": "The nonce used to create the box which stores the delayed mint" 156 | } 157 | ], 158 | "returns": { 159 | "type": "void" 160 | } 161 | }, 162 | { 163 | "name": "burn", 164 | "desc": "Send xALGO to the app and receive ALGO", 165 | "args": [ 166 | { 167 | "type": "axfer", 168 | "name": "send_xalgo", 169 | "desc": "Send xALGO to the app to burn" 170 | }, 171 | { 172 | "type": "address", 173 | "name": "receiver", 174 | "desc": "The address to receiver the ALGO at" 175 | }, 176 | { 177 | "type": "uint64", 178 | "name": "min_received", 179 | "desc": "The minimum amount of ALGO to receive in return" 180 | } 181 | ], 182 | "returns": { 183 | "type": "void" 184 | } 185 | }, 186 | { 187 | "name": "get_xalgo_rate", 188 | "desc": "Get the conversion rate between xALGO and ALGO", 189 | "args": [], 190 | "returns": { 191 | "type": "(uint64,uint64,byte[])", 192 | "desc": "Array of [algo_balance, x_algo_circulating_supply, proposers_balances]" 193 | } 194 | }, 195 | { 196 | "name": "dummy", 197 | "desc": "Dummy call to the app to bypass foreign accounts limit", 198 | "args": [], 199 | "returns": { 200 | "type": "void" 201 | } 202 | } 203 | ], 204 | "networks": {} 205 | } 206 | -------------------------------------------------------------------------------- /src/xalgo/constants/mainnet-constants.ts: -------------------------------------------------------------------------------- 1 | import type { ConsensusConfig } from "../types"; 2 | 3 | const MainnetConsensusConfig: ConsensusConfig = { 4 | consensusAppId: 1134695678, 5 | xAlgoId: 1134696561, 6 | stakeAndDepositAppId: 2633147490, 7 | }; 8 | 9 | export { MainnetConsensusConfig }; 10 | -------------------------------------------------------------------------------- /src/xalgo/constants/testnet-constants.ts: -------------------------------------------------------------------------------- 1 | import type { ConsensusConfig } from "../types"; 2 | 3 | const TestnetConsensusConfig: ConsensusConfig = { 4 | consensusAppId: 730430673, 5 | xAlgoId: 730430700, 6 | stakeAndDepositAppId: 731190793, 7 | }; 8 | 9 | export { TestnetConsensusConfig }; 10 | -------------------------------------------------------------------------------- /src/xalgo/formulae.ts: -------------------------------------------------------------------------------- 1 | import { mulScale, ONE_16_DP } from "../math-lib"; 2 | 3 | import type { ConsensusState } from "./types"; 4 | 5 | function convertAlgoToXAlgoWhenImmediate(algoAmount: bigint, consensusState: ConsensusState): bigint { 6 | const { algoBalance, xAlgoCirculatingSupply, premium } = consensusState; 7 | return mulScale(mulScale(algoAmount, xAlgoCirculatingSupply, algoBalance), ONE_16_DP - premium, ONE_16_DP); 8 | } 9 | 10 | function convertAlgoToXAlgoWhenDelay(algoAmount: bigint, consensusState: ConsensusState): bigint { 11 | const { algoBalance, xAlgoCirculatingSupply } = consensusState; 12 | return mulScale(algoAmount, xAlgoCirculatingSupply, algoBalance); 13 | } 14 | 15 | function convertXAlgoToAlgo(xAlgoAmount: bigint, consensusState: ConsensusState): bigint { 16 | const { algoBalance, xAlgoCirculatingSupply } = consensusState; 17 | return mulScale(xAlgoAmount, algoBalance, xAlgoCirculatingSupply); 18 | } 19 | 20 | export { convertAlgoToXAlgoWhenImmediate, convertAlgoToXAlgoWhenDelay, convertXAlgoToAlgo }; 21 | -------------------------------------------------------------------------------- /src/xalgo/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./abi-contracts"; 2 | export * from "./constants/mainnet-constants"; 3 | export * from "./constants/testnet-constants"; 4 | export * from "./consensus"; 5 | export * from "./formulae"; 6 | export * from "./types"; 7 | -------------------------------------------------------------------------------- /src/xalgo/types.ts: -------------------------------------------------------------------------------- 1 | interface ConsensusConfig { 2 | consensusAppId: number; 3 | xAlgoId: number; 4 | stakeAndDepositAppId: number; 5 | } 6 | 7 | interface ConsensusState { 8 | currentRound: number; // round the data was read at 9 | algoBalance: bigint; 10 | xAlgoCirculatingSupply: bigint; 11 | proposersBalances: { 12 | address: string; 13 | algoBalance: bigint; 14 | }[]; 15 | timeDelay: bigint; 16 | numProposers: bigint; 17 | maxProposerBalance: bigint; 18 | fee: bigint; // 4 d.p. 19 | premium: bigint; // 16 d.p. 20 | lastProposersActiveBalance: bigint; 21 | totalPendingStake: bigint; 22 | totalUnclaimedFees: bigint; 23 | canImmediateStake: boolean; 24 | canDelayStake: boolean; 25 | } 26 | 27 | type ProposerAllocations = bigint[]; 28 | 29 | export { ConsensusConfig, ConsensusState, ProposerAllocations }; 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ES6", "DOM"], 4 | "target": "ES6", 5 | "module": "CommonJS", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "skipLibCheck": true, 12 | "checkJs": true, 13 | "allowJs": true, 14 | "downlevelIteration": true, 15 | "allowSyntheticDefaultImports": true, 16 | "outDir": "dist", 17 | "declaration": true, 18 | "declarationMap": true, 19 | "declarationDir": "dist/types", 20 | "sourceMap": true 21 | }, 22 | "include": ["src/**/*"] 23 | } 24 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://typedoc.org/schema.json", 3 | "entryPoints": ["src/index.ts"], 4 | "excludeExternals": true, 5 | "out": "docs", 6 | "plugin": ["@typhonjs-typedoc/typedoc-theme-dmt"], 7 | "theme": "default-modern" 8 | } 9 | --------------------------------------------------------------------------------