├── .cspell.json ├── .editorconfig ├── .eslintrc.json ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── publish.yml ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── amm │ ├── common │ │ ├── constants.ts │ │ ├── contracts │ │ │ ├── n2dexyGOLDPoolContracts.ts │ │ │ ├── n2dexyGOLDtemplates.ts │ │ │ ├── n2tPoolContracts.ts │ │ │ ├── n2tTemplates.ts │ │ │ ├── t2tPoolContracts.spec.ts │ │ │ ├── t2tPoolContracts.ts │ │ │ └── t2tTemplates.ts │ │ ├── entities │ │ │ ├── ammPool.spec.ts │ │ │ ├── ammPool.ts │ │ │ └── swap.ts │ │ ├── errors │ │ │ ├── invalidParam.ts │ │ │ ├── invalidParams.ts │ │ │ └── poolSetupFailed.ts │ │ ├── interpreters │ │ │ ├── mins.ts │ │ │ ├── n2dexyPoolSetupAction.ts │ │ │ ├── n2tPoolSetupAction.ts │ │ │ ├── poolActions.ts │ │ │ ├── poolSetupAction.ts │ │ │ ├── refunds.ts │ │ │ └── t2tPoolSetupAction.ts │ │ ├── math │ │ │ ├── swap.spec.ts │ │ │ └── swap.ts │ │ ├── models │ │ │ ├── ammOperation.ts │ │ │ ├── ammOrderInfo.ts │ │ │ ├── ammPoolInfo.ts │ │ │ ├── depositParams.ts │ │ │ ├── operationSummary.ts │ │ │ ├── operations.ts │ │ │ ├── poolSetupParams.ts │ │ │ ├── redeemParams.ts │ │ │ ├── refundOperation.ts │ │ │ └── swapParams.ts │ │ ├── parsers │ │ │ ├── ammOrdersParser.spec.ts │ │ │ ├── ammOrdersParser.ts │ │ │ ├── ammPoolsInfoParser.ts │ │ │ └── ammPoolsParser.ts │ │ ├── services │ │ │ ├── history.ts │ │ │ └── pools.ts │ │ ├── types.ts │ │ └── validation │ │ │ └── ammPoolValidation.ts │ ├── nativeFee │ │ ├── contracts │ │ │ ├── n2dexyPoolContracts.ts │ │ │ ├── n2dexyTemplates.ts │ │ │ ├── n2tPoolContracts.ts │ │ │ ├── n2tTemplates.ts │ │ │ ├── t2tPoolContracts.ts │ │ │ └── t2tTemplates.ts │ │ └── interpreters │ │ │ ├── n2DexyPoolActions.ts │ │ │ ├── n2tPoolActions.ts │ │ │ ├── poolActions.ts │ │ │ └── t2tPoolActions.ts │ └── spfFee │ │ ├── contracts │ │ ├── n2tPoolContracts.ts │ │ ├── n2tTemplates.ts │ │ ├── t2tPoolContracts.ts │ │ └── t2tTemplates.ts │ │ └── interpreters │ │ ├── n2tPoolActions.ts │ │ ├── poolActions.ts │ │ └── t2tPoolActions.ts ├── constants.ts ├── contracts │ └── poolContracts.ts ├── entities │ └── price.ts ├── fromBox.ts ├── index.ts ├── lqmining │ ├── contracts │ │ ├── poolValidator.ts │ │ ├── proxyValidators.ts │ │ └── templates.ts │ ├── entities │ │ ├── lmPool.spec.ts │ │ └── lmPool.ts │ ├── interpreters │ │ └── poolActions.ts │ ├── models │ │ ├── actionContext.ts │ │ ├── poolOpParams.ts │ │ └── stake.ts │ ├── parsers │ │ ├── lmPoolFromBox.ts │ │ └── stakeFromBox.ts │ ├── services │ │ ├── pools.ts │ │ └── stakes.ts │ ├── types.ts │ └── validation │ │ └── lmPoolValidation.ts ├── models │ └── refundParams.ts ├── security │ ├── contracts │ │ ├── lockingContracts.ts │ │ └── lockingTemplates.ts │ ├── entities.ts │ ├── interpreters │ │ └── lockActions.ts │ ├── models.ts │ ├── parsers │ │ ├── lockParser.spec.ts │ │ └── lockParser.ts │ └── services │ │ └── locksHistory.ts ├── services │ └── pools.ts ├── types.ts ├── utils │ ├── arrays.ts │ ├── blake2b256.ts │ ├── blocks.ts │ ├── contract.ts │ ├── hex.ts │ ├── json.ts │ ├── makeTarget.ts │ ├── math.ts │ ├── notImplemented.ts │ ├── sqrt.ts │ └── utf8.ts └── validation │ └── poolValidation.ts └── tsconfig.json /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json", 4 | "language": "en", 5 | "words": [ 6 | "bitjson", 7 | "bitauth", 8 | "cimg", 9 | "circleci", 10 | "codecov", 11 | "commitlint", 12 | "dependabot", 13 | "editorconfig", 14 | "esnext", 15 | "execa", 16 | "exponentiate", 17 | "globby", 18 | "libauth", 19 | "mkdir", 20 | "prettierignore", 21 | "sandboxed", 22 | "transpiled", 23 | "typedoc", 24 | "untracked" 25 | ], 26 | "flagWords": [], 27 | "ignorePaths": ["package.json", "package-lock.json", "yarn.lock", "tsconfig.json", "node_modules/**"] 28 | } 29 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 110 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 100 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": {"project": "./tsconfig.json"}, 5 | "env": {"es6": true}, 6 | "ignorePatterns": ["node_modules", "build", "coverage"], 7 | "plugins": ["import", "eslint-comments", "functional"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:eslint-comments/recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:import/typescript", 13 | "prettier", 14 | "prettier/@typescript-eslint" 15 | ], 16 | "globals": {"BigInt": true, "console": true, "WebAssembly": true}, 17 | "rules": { 18 | "@typescript-eslint/explicit-module-boundary-types": "off", 19 | "@typescript-eslint/no-unused-vars": "warn", 20 | "@typescript-eslint/no-empty-function": "warn", 21 | "@typescript-eslint/ban-ts-comment": "warn", 22 | "eslint-comments/disable-enable-pair": ["error", {"allowWholeFile": true}], 23 | "eslint-comments/no-unused-disable": "error", 24 | "import/order": ["error", {"newlines-between": "never", "alphabetize": {"order": "asc"}}], 25 | "sort-imports": ["error", {"ignoreDeclarationSort": true, "ignoreCase": true}] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Example Contributing Guidelines 2 | 3 | This is an example of GitHub's contributing guidelines file. Check out GitHub's [CONTRIBUTING.md help center article](https://help.github.com/articles/setting-guidelines-for-repository-contributors/) for more information. 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **I'm submitting a ...** 2 | [ ] bug report 3 | [ ] feature request 4 | [ ] question about the decisions made in the repository 5 | [ ] question about how to use this project 6 | 7 | - **Summary** 8 | 9 | - **Other information** (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.) 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | 3 | - **What is the current behavior?** (You can also link to an open issue here) 4 | 5 | - **What is the new behavior (if this is a feature change)?** 6 | 7 | - **Other information**: 8 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish packages to NPM 2 | on: 3 | push: 4 | tags: 5 | - '[0-9]+.[0-9]+.[0-9]+' 6 | jobs: 7 | publish: 8 | name: Publish package. 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | packages: write 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: '16.x' 18 | registry-url: 'https://registry.npmjs.org' 19 | - run: npm ci 20 | - run: npm run build --scripts-prepend-node-path=auto 21 | - run: npm publish --access=public 22 | env: 23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | .nyc_output 3 | build 4 | node_modules 5 | test 6 | src/**.js 7 | coverage 8 | *.log 9 | yarn.lock 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # package.json is formatted by package managers, so we ignore it here 2 | package.json 3 | 4 | build 5 | node_modules 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ergo DEX SDK 2 | 3 | [![npm version](https://badge.fury.io/js/@ergolabs%2Fergo-dex-sdk.svg)](https://badge.fury.io/js/@ergolabs%2Fergo-dex-sdk) 4 | 5 | This SDK includes: 6 | * ErgoDEX protocol data model 7 | * AMM DEX transaction assemblers (aka interpreters) 8 | * Ergo Network API wrappers allowing to pull history of wallet operations 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ergolabs/ergo-dex-sdk", 3 | "version": "1.9.67", 4 | "description": "ErgoDEX SDK", 5 | "main": "build/main/index.js", 6 | "typings": "build/main/index.d.ts", 7 | "module": "build/main/index.js", 8 | "keywords": [], 9 | "scripts": { 10 | "build": "run-p build:*", 11 | "build:main": "tsc -p tsconfig.json", 12 | "build+test:unit": "run-p build:* && nyc --silent ava", 13 | "fix": "run-s fix:*", 14 | "fix:prettier": "prettier \"src/**/*.ts\" --write", 15 | "fix:lint": "eslint src --ext .ts --fix", 16 | "test": "run-s build test:*", 17 | "test:lint": "eslint src --ext .ts", 18 | "test:prettier": "prettier \"src/**/*.ts\" --list-different", 19 | "test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"", 20 | "test:unit": "nyc --silent ava", 21 | "check-cli": "run-s test diff-integration-tests check-integration-tests", 22 | "check-integration-tests": "run-s check-integration-test:*", 23 | "diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'", 24 | "watch:build": "tsc -p tsconfig.json -w", 25 | "watch:test": "nyc --silent ava --watch", 26 | "cov": "run-s build test:unit cov:html cov:lcov && open-cli coverage/index.html", 27 | "cov:html": "nyc report --reporter=html", 28 | "cov:lcov": "nyc report --reporter=lcov", 29 | "cov:send": "run-s cov:lcov && codecov", 30 | "cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100", 31 | "doc": "run-s doc:html && open-cli build/docs/index.html", 32 | "doc:html": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --out build/docs", 33 | "doc:json": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --json build/docs/typedoc.json", 34 | "doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs", 35 | "version": "standard-version", 36 | "reset-hard": "git clean -dfx && git reset --hard && npm i", 37 | "prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish" 38 | }, 39 | "engines": { 40 | "node": ">=10" 41 | }, 42 | "dependencies": { 43 | "@ergolabs/ergo-sdk": "^0.5.7", 44 | "axios": "^0.21.1", 45 | "blakejs": "^1.1.0", 46 | "bs58": "^4.0.1", 47 | "crypto-js": "^4.0.0", 48 | "ergo-lib-wasm-browser": "0.20.1", 49 | "ergo-lib-wasm-nodejs": "0.20.1", 50 | "esm-wallaby": "^3.2.25", 51 | "json-bigint": "^1.0.0", 52 | "mathjs": "^9.4.4", 53 | "ramda": "0.27.1" 54 | }, 55 | "devDependencies": { 56 | "@ava/babel": "^2.0.0", 57 | "@ava/typescript": "^1.1.1", 58 | "@babel/plugin-proposal-optional-chaining": "^7.14.5", 59 | "@babel/polyfill": "^7.12.1", 60 | "@babel/preset-env": "^7.15.6", 61 | "@istanbuljs/nyc-config-typescript": "^1.0.1", 62 | "@types/bs58": "^4.0.1", 63 | "@types/crypto-js": "^4.0.1", 64 | "@types/json-bigint": "^1.0.1", 65 | "@types/node": "^15.14.9", 66 | "@types/ramda": "0.27.32", 67 | "@typescript-eslint/eslint-plugin": "^4.0.1", 68 | "@typescript-eslint/parser": "^4.0.1", 69 | "ava": "^3.12.1", 70 | "codecov": "^3.5.0", 71 | "cspell": "^4.1.0", 72 | "cz-conventional-changelog": "^3.3.0", 73 | "eslint": "^7.8.0", 74 | "eslint-config-prettier": "^6.11.0", 75 | "eslint-plugin-eslint-comments": "^3.2.0", 76 | "eslint-plugin-functional": "^3.0.2", 77 | "eslint-plugin-import": "^2.22.0", 78 | "esm": "^3.2.25", 79 | "gh-pages": "^3.1.0", 80 | "npm-run-all": "^4.1.5", 81 | "nyc": "^15.1.0", 82 | "open-cli": "^6.0.1", 83 | "prettier": "^2.1.1", 84 | "standard-version": "^9.0.0", 85 | "ts-node": "^9.0.0", 86 | "typedoc": "^0.19.0", 87 | "typescript": "^4.0.8" 88 | }, 89 | "files": [ 90 | "build/main", 91 | "!**/*.spec.*", 92 | "!**/*.json", 93 | "CHANGELOG.md", 94 | "LICENSE", 95 | "README.md" 96 | ], 97 | "ava": { 98 | "failFast": true, 99 | "timeout": "60s", 100 | "typescript": { 101 | "rewritePaths": { 102 | "src/": "build/main/" 103 | } 104 | }, 105 | "require": [ 106 | "esm-wallaby", 107 | "@babel/polyfill" 108 | ], 109 | "babel": true 110 | }, 111 | "config": { 112 | "commitizen": { 113 | "path": "cz-conventional-changelog" 114 | } 115 | }, 116 | "prettier": { 117 | "singleQuote": false, 118 | "semi": false, 119 | "tabWidth": 2, 120 | "bracketSpacing": false, 121 | "arrowParens": "avoid", 122 | "endOfLine": "lf", 123 | "trailingComma": "none" 124 | }, 125 | "nyc": { 126 | "extends": "@istanbuljs/nyc-config-typescript", 127 | "exclude": [ 128 | "**/*.spec.js" 129 | ] 130 | }, 131 | "repository": { 132 | "type": "git", 133 | "url": "git+https://github.com/ergolabs/ergo-sdk-js.git" 134 | }, 135 | "author": "Ilya Oskin (https://ergoplatfrom.org/)", 136 | "license": "SEE LICENSE IN LICENSE", 137 | "bugs": { 138 | "url": "https://github.com/ergolabs/ergo-sdk-js/issues" 139 | }, 140 | "homepage": "https://github.com/ergolabs/ergo-sdk-js#readme" 141 | } 142 | -------------------------------------------------------------------------------- /src/amm/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const PoolFeeMaxDecimals = 3 2 | export const PoolFeeScale = 1000 3 | 4 | export const MinPoolBoxValue = 10_000_000n 5 | export const EmissionLP = 9223372036854775807n 6 | export const BurnLP = 1000n 7 | 8 | export const SigmaPropConstPrefixHex = "08cd" 9 | 10 | export const SigmaFalseHex = "0100" 11 | export const SigmaTrueHex = "0101" 12 | 13 | export const ErgoTreePrefixHex = "00" 14 | 15 | export const I64Max = 9223372036854775807n 16 | 17 | // TODO: should update ID for mainnet 18 | export const DexyGOLDAssetId = "0d69a552b30df9be519099ec07682039b0610267aaee48d2a1d3dad398287ef5" 19 | export const DexyGOLDFeeNum = 997 20 | -------------------------------------------------------------------------------- /src/amm/common/contracts/n2dexyGOLDPoolContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, HexString, RustModule} from "@ergolabs/ergo-sdk" 2 | import * as crypto from "crypto-js" 3 | import {PoolContracts} from "../../../contracts/poolContracts" 4 | import {toHex} from "../../../utils/hex" 5 | import {AmmPool} from "../entities/ammPool" 6 | import * as N2DEXY from "./n2dexyGOLDtemplates" 7 | 8 | export function pool(): ErgoTree { 9 | return N2DEXY.PoolSample 10 | } 11 | 12 | export function poolTemplateHash(): HexString { 13 | const template = RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2DEXY.PoolSample).template_bytes() 14 | return crypto.SHA256(crypto.enc.Hex.parse(toHex(template))).toString(crypto.enc.Hex) 15 | } 16 | 17 | export function poolBundle(): PoolContracts { 18 | return { 19 | poolTree: pool(), 20 | poolTemplateHash: poolTemplateHash() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/amm/common/contracts/n2dexyGOLDtemplates.ts: -------------------------------------------------------------------------------- 1 | // TODO: should update according mainnet data 2 | export const PoolSample = 3 | "1013040004020402040404020400040004000404040404060e201290ef3c02310d7dd110a9cc6b0a0aa3b4d669ed4c75b3ed9b25652b9189e50d0e2012a1cfce7a3ad46b3d7d8a78bf1ec29c210f1ab8216a5670ed6ea0af8abd19b40e2012e5820470d332344c73ad21f5b1249c40f2ecbdd8d51db3f912944c279db909040004000e20102a5f871dd4f3b051dc3295068a5f69af148b5fe9d182353798b67c013f9d9e0e2012f11de6defb4478423b08c01a50affa63d4c4ba7a070c87fb66f58ea7c0e4db0500d807d601b2a5730000d602db6308a7d603db63087201d604b27203730100d605b27202730200d606db6308b2a4730300d6078cb2db6308b2a473040073050001d1ededededed93c27201c2a793b27202730600b27203730700938c7204018c720501938cb27203730800018cb272027309000193b17203730aececec937207730b937207730c937207730dedeced91b17206730e938cb27206730f00017310937207731193998c7205028c7204027312" 4 | 5 | export const PoolTemplate = 6 | "d807d601b2a5730000d602db6308a7d603db63087201d604b27203730100d605b27202730200d606db6308b2a4730300d6078cb2db6308b2a473040073050001d1ededededed93c27201c2a793b27202730600b27203730700938c7204018c720501938cb27203730800018cb272027309000193b17203730aececec937207730b937207730c937207730dedeced91b17206730e938cb27206730f00017310937207731193998c7205028c7204027312" 7 | -------------------------------------------------------------------------------- /src/amm/common/contracts/n2tPoolContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, HexString, RustModule} from "@ergolabs/ergo-sdk" 2 | import * as crypto from "crypto-js" 3 | import {PoolContracts} from "../../../contracts/poolContracts" 4 | import {toHex} from "../../../utils/hex" 5 | import {AmmPool} from "../entities/ammPool" 6 | import * as N2T from "./n2tTemplates" 7 | 8 | export function pool(): ErgoTree { 9 | return N2T.PoolSample 10 | } 11 | 12 | export function poolTemplateHash(): HexString { 13 | const template = RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2T.PoolSample).template_bytes() 14 | return crypto.SHA256(crypto.enc.Hex.parse(toHex(template))).toString(crypto.enc.Hex) 15 | } 16 | 17 | export function poolBundle(): PoolContracts { 18 | return { 19 | poolTree: pool(), 20 | poolTemplateHash: poolTemplateHash() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/amm/common/contracts/n2tTemplates.ts: -------------------------------------------------------------------------------- 1 | export const PoolSample = 2 | "1999030f0400040204020404040405feffffffffffffffff0105feffffffffffffffff01050004d00f040004000406050005000580dac409d819d601b2a5730000d602e4c6a70404d603db63087201d604db6308a7d605b27203730100d606b27204730200d607b27203730300d608b27204730400d6099973058c720602d60a999973068c7205027209d60bc17201d60cc1a7d60d99720b720cd60e91720d7307d60f8c720802d6107e720f06d6117e720d06d612998c720702720fd6137e720c06d6147308d6157e721206d6167e720a06d6177e720906d6189c72117217d6199c72157217d1ededededededed93c27201c2a793e4c672010404720293b27203730900b27204730a00938c7205018c720601938c7207018c72080193b17203730b9593720a730c95720e929c9c721072117e7202069c7ef07212069a9c72137e7214067e9c720d7e72020506929c9c721372157e7202069c7ef0720d069a9c72107e7214067e9c72127e7202050695ed720e917212730d907216a19d721872139d72197210ed9272189c721672139272199c7216721091720b730e" 3 | 4 | export const PoolTemplate = 5 | "d819d601b2a5730000d602e4c6a70404d603db63087201d604db6308a7d605b27203730100d606b27204730200d607b27203730300d608b27204730400d6099973058c720602d60a999973068c7205027209d60bc17201d60cc1a7d60d99720b720cd60e91720d7307d60f8c720802d6107e720f06d6117e720d06d612998c720702720fd6137e720c06d6147308d6157e721206d6167e720a06d6177e720906d6189c72117217d6199c72157217d1ededededededed93c27201c2a793e4c672010404720293b27203730900b27204730a00938c7205018c720601938c7207018c72080193b17203730b9593720a730c95720e929c9c721072117e7202069c7ef07212069a9c72137e7214067e9c720d7e72020506929c9c721372157e7202069c7ef0720d069a9c72107e7214067e9c72127e7202050695ed720e917212730d907216a19d721872139d72197210ed9272189c721672139272199c7216721091720b730e" 6 | -------------------------------------------------------------------------------- /src/amm/common/contracts/t2tPoolContracts.spec.ts: -------------------------------------------------------------------------------- 1 | import {RustModule} from "@ergolabs/ergo-sdk" 2 | import test from "ava" 3 | import {decimalToFractional} from "../../../utils/math" 4 | import * as T2T from "./t2tPoolContracts" 5 | 6 | test.before(async () => { 7 | await RustModule.load(true) 8 | }) 9 | 10 | test("Contract template hash calculation: Pool", async t => { 11 | t.deepEqual(T2T.poolTemplateHash(), "3c09deff3b5f49329149d18e02aab675ef6957bf6559a5c7dba817fee883fb3e") 12 | }) 13 | 14 | test("decimals to fractional", async t => { 15 | const numbers = [0.1, 0.01, 1.1, 1.01, 11.01, 1.0, 1, 121212.12121212122, 7.464261489131828e-7] 16 | for (const i of numbers) { 17 | const [n, d] = decimalToFractional(i) 18 | t.deepEqual(i, Number(n) / Number(d)) 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /src/amm/common/contracts/t2tPoolContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, HexString, RustModule} from "@ergolabs/ergo-sdk" 2 | import * as crypto from "crypto-js" 3 | import {PoolContracts} from "../../../contracts/poolContracts" 4 | import {toHex} from "../../../utils/hex" 5 | import {AmmPool} from "../entities/ammPool" 6 | import * as T2T from "./t2tTemplates" 7 | 8 | export function pool(): ErgoTree { 9 | return T2T.PoolSample 10 | } 11 | 12 | export function poolTemplateHash(): HexString { 13 | const template = RustModule.SigmaRust.ErgoTree.from_base16_bytes(T2T.PoolSample).template_bytes() 14 | return crypto.SHA256(crypto.enc.Hex.parse(toHex(template))).toString(crypto.enc.Hex) 15 | } 16 | 17 | export function poolBundle(): PoolContracts { 18 | return { 19 | poolTree: pool(), 20 | poolTemplateHash: poolTemplateHash() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/amm/common/contracts/t2tTemplates.ts: -------------------------------------------------------------------------------- 1 | export const PoolSample = 2 | "19a9030f040004020402040404040406040605feffffffffffffffff0105feffffffffffffffff01050004d00f0400040005000500d81ad601b2a5730000d602e4c6a70404d603db63087201d604db6308a7d605b27203730100d606b27204730200d607b27203730300d608b27204730400d609b27203730500d60ab27204730600d60b9973078c720602d60c999973088c720502720bd60d8c720802d60e998c720702720dd60f91720e7309d6108c720a02d6117e721006d6127e720e06d613998c7209027210d6147e720d06d615730ad6167e721306d6177e720c06d6187e720b06d6199c72127218d61a9c72167218d1edededededed93c27201c2a793e4c672010404720292c17201c1a793b27203730b00b27204730c00938c7205018c720601ed938c7207018c720801938c7209018c720a019593720c730d95720f929c9c721172127e7202069c7ef07213069a9c72147e7215067e9c720e7e72020506929c9c721472167e7202069c7ef0720e069a9c72117e7215067e9c72137e7202050695ed720f917213730e907217a19d721972149d721a7211ed9272199c7217721492721a9c72177211" 3 | 4 | export const PoolTemplate = 5 | "d81ad601b2a5730000d602e4c6a70404d603db63087201d604db6308a7d605b27203730100d606b27204730200d607b27203730300d608b27204730400d609b27203730500d60ab27204730600d60b9973078c720602d60c999973088c720502720bd60d8c720802d60e998c720702720dd60f91720e7309d6108c720a02d6117e721006d6127e720e06d613998c7209027210d6147e720d06d615730ad6167e721306d6177e720c06d6187e720b06d6199c72127218d61a9c72167218d1edededededed93c27201c2a793e4c672010404720292c17201c1a793b27203730b00b27204730c00938c7205018c720601ed938c7207018c720801938c7209018c720a019593720c730d95720f929c9c721172127e7202069c7ef07213069a9c72147e7215067e9c720e7e72020506929c9c721472167e7202069c7ef0720e069a9c72117e7215067e9c72137e7202050695ed720f917213730e907217a19d721972149d721a7211ed9272199c7217721492721a9c72177211" 6 | -------------------------------------------------------------------------------- /src/amm/common/entities/ammPool.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | import {AssetAmount} from "@ergolabs/ergo-sdk" 3 | import {AmmPool} from "./ammPool" 4 | import {sqrt} from "../../../utils/sqrt" 5 | 6 | export class PoolEmulation { 7 | constructor(public readonly pool: AmmPool) {} 8 | 9 | swapPossible(input: AssetAmount, minOutput: AssetAmount): boolean { 10 | const [baseR, quoteR] = 11 | input.asset.id === this.pool.assetX.id 12 | ? [this.pool.x.amount, this.pool.y.amount] 13 | : [this.pool.y.amount, this.pool.x.amount] 14 | const deltaQuoteStrict = this.pool.outputAmount(input, 0) 15 | 16 | const poolRequirementsSatisfied = 17 | quoteR * input.amount * this.pool.feeNum >= 18 | deltaQuoteStrict.amount * (baseR * this.pool.feeDenom + input.amount * this.pool.feeNum) 19 | 20 | const swapRequirementSatisfied = 21 | quoteR * input.amount * this.pool.feeNum <= 22 | (deltaQuoteStrict.amount + 1n) * (baseR * this.pool.feeDenom + input.amount * this.pool.feeNum) && 23 | deltaQuoteStrict >= minOutput 24 | 25 | return poolRequirementsSatisfied && swapRequirementSatisfied 26 | } 27 | } 28 | 29 | const pool = initPool(1000000n, 5000000n) 30 | const emul0 = new PoolEmulation(pool) 31 | 32 | test("Pool math (depositAmount, X -> Y)", t => { 33 | const inputX = new AssetAmount({id: "x"}, 1000n) 34 | const inputY = pool.depositAmount(inputX) 35 | const expectedY = new AssetAmount({id: "y"}, 5000n) 36 | t.deepEqual(inputY, expectedY) 37 | t.deepEqual(pool.depositAmount(inputY), inputX) 38 | }) 39 | 40 | test("Pool math (depositAmount, Y -> X)", t => { 41 | const inputY = new AssetAmount({id: "y"}, 1000n) 42 | const inputX = pool.depositAmount(inputY) 43 | const expectedY = new AssetAmount({id: "x"}, 200n) 44 | t.deepEqual(inputX, expectedY) 45 | t.deepEqual(pool.depositAmount(inputX), inputY) 46 | }) 47 | 48 | test("Pool math (outputAmount, X -> Y, 0% slippage)", t => { 49 | const inputX = new AssetAmount({id: "x"}, 1000n) 50 | const outputY = pool.outputAmount(inputX, 0) 51 | const expectedY = new AssetAmount({id: "y"}, 4980n) 52 | t.deepEqual(outputY, expectedY) 53 | t.true(emul0.swapPossible(inputX, outputY)) 54 | }) 55 | 56 | test("Pool math (inputAmount, X -> Y, 0% slippage)", t => { 57 | const outputX = new AssetAmount({id: "x"}, 1000n) 58 | const inputY = pool.inputAmount(outputX, 0) 59 | const expectedY = new AssetAmount({id: "y"}, 5021n) 60 | t.deepEqual(inputY, expectedY) 61 | t.true(emul0.swapPossible(inputY!, outputX)) 62 | }) 63 | 64 | test("Pool math (outputAmount, Y -> X, 0% slippage)", t => { 65 | const inputY = new AssetAmount({id: "y"}, 1000n) 66 | const outputX = pool.outputAmount(inputY, 0) 67 | const expectedX = new AssetAmount({id: "x"}, 199n) 68 | t.deepEqual(outputX, expectedX) 69 | t.true(emul0.swapPossible(inputY, outputX)) 70 | }) 71 | 72 | test("Pool math (outputAmount, X -> Y, 5% slippage)", t => { 73 | const inputX = new AssetAmount({id: "x"}, 1000n) 74 | const outputY = pool.outputAmount(inputX, 5) 75 | const expectedY = new AssetAmount({id: "y"}, 4743n) 76 | t.deepEqual(outputY, expectedY) 77 | t.true(emul0.swapPossible(inputX, outputY)) 78 | }) 79 | 80 | test("Pool math (outputAmount, Y -> X, 5% slippage)", t => { 81 | const inputY = new AssetAmount({id: "y"}, 1000n) 82 | const outputX = pool.outputAmount(inputY, 5) 83 | const expectedX = new AssetAmount({id: "x"}, 189n) 84 | t.deepEqual(outputX, expectedX) 85 | t.true(emul0.swapPossible(inputY, outputX)) 86 | }) 87 | 88 | function initPool(inX: bigint, inY: bigint): AmmPool { 89 | const share = sqrt(inX * inY) 90 | const lp = new AssetAmount({id: "lp"}, share) 91 | const x = new AssetAmount({id: "x"}, BigInt(inX)) 92 | const y = new AssetAmount({id: "y"}, BigInt(inY)) 93 | return new AmmPool("0x", lp, x, y, 997) 94 | } 95 | -------------------------------------------------------------------------------- /src/amm/common/entities/ammPool.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, AssetInfo} from "@ergolabs/ergo-sdk" 2 | import {Price} from "../../../entities/price" 3 | import {math} from "../../../utils/math" 4 | import {EmissionLP} from "../constants" 5 | import {PoolId} from "../types" 6 | 7 | export class AmmPool { 8 | constructor( 9 | public readonly id: PoolId, 10 | public readonly lp: AssetAmount, 11 | public readonly x: AssetAmount, 12 | public readonly y: AssetAmount, 13 | public readonly poolFeeNum: number 14 | ) {} 15 | 16 | readonly feeDenom: bigint = 1000n 17 | readonly feeNum: bigint = BigInt(this.poolFeeNum) 18 | 19 | get supplyLP(): bigint { 20 | return EmissionLP - this.lp.amount 21 | } 22 | 23 | get assetX(): AssetInfo { 24 | return this.x.asset 25 | } 26 | 27 | get assetY(): AssetInfo { 28 | return this.y.asset 29 | } 30 | 31 | /** @return Price of tokenX in tokenY units. 32 | */ 33 | get priceX(): Price { 34 | return new Price(this.y.amount, this.x.amount) 35 | } 36 | 37 | /** @return Price of tokenY in tokenX units. 38 | */ 39 | get priceY(): Price { 40 | return new Price(this.x.amount, this.y.amount) 41 | } 42 | 43 | /** @return proportional amount of one token to a given input of the other 44 | */ 45 | depositAmount(input: AssetAmount): AssetAmount { 46 | if (input.asset.id === this.assetX.id) 47 | return this.y.withAmount((input.amount * this.priceX.numerator) / this.priceX.denominator) 48 | else return this.x.withAmount((input.amount * this.priceY.numerator) / this.priceY.denominator) 49 | } 50 | 51 | /** @return pair of asset amounts proportional to a given input of LP tokens. 52 | */ 53 | shares(input: AssetAmount): [AssetAmount, AssetAmount] { 54 | if (input.asset.id === this.lp.asset.id) { 55 | return [ 56 | this.x.withAmount((input.amount * this.x.amount) / this.supplyLP), 57 | this.y.withAmount((input.amount * this.y.amount) / this.supplyLP) 58 | ] 59 | } else { 60 | return [this.x.withAmount(0n), this.y.withAmount(0n)] 61 | } 62 | } 63 | 64 | /** @return amount of LP asset proportional to the amounts of assets deposited. 65 | */ 66 | rewardLP(inputX: AssetAmount, inputY: AssetAmount): AssetAmount { 67 | if (inputX.asset.id === this.x.asset.id && inputY.asset.id === this.y.asset.id) { 68 | const rewardXWise = (inputX.amount * this.supplyLP) / this.x.amount 69 | const rewardYWise = (inputY.amount * this.supplyLP) / this.y.amount 70 | return this.lp.withAmount(rewardXWise <= rewardYWise ? rewardXWise : rewardYWise) 71 | } else { 72 | return this.lp.withAmount(0n) 73 | } 74 | } 75 | 76 | /** @return Input amount of one token for a given output amount of the other 77 | */ 78 | inputAmount(output: AssetAmount, maxSlippage?: number): AssetAmount | undefined { 79 | const slippage = BigInt((maxSlippage || 0) * 100) 80 | const minimalOutput = this.outputAmount(output).amount 81 | if (output.asset.id === this.assetX.id && minimalOutput > 0 && output.amount <= this.x.amount) { 82 | return this.y.withAmount( 83 | BigInt( 84 | math 85 | .evaluate!(`(${this.y.amount} * ${output.amount} * ${this.feeDenom} * (${10000n} + ${slippage})) / (${10000n} * (${this.x.amount} - ${output.amount}) * ${this.feeNum})`) 86 | .toFixed(0) 87 | ) 88 | ) 89 | } else if (output.asset.id === this.assetY.id && minimalOutput > 0 && output.amount <= this.y.amount) { 90 | return this.x.withAmount( 91 | BigInt( 92 | math 93 | .evaluate!(`(${this.x.amount} * ${output.amount} * ${this.feeDenom} * (${10000n} + ${slippage})) / (${10000n} * (${this.y.amount} - ${output.amount}) * ${this.feeNum})`) 94 | .toFixed(0) 95 | ) 96 | ) 97 | } else { 98 | return undefined 99 | } 100 | } 101 | 102 | /** @param input - swap input 103 | * @param maxSlippage - max price slippage allowed % (0 - 100) 104 | * @return Output amount of one token for a given input amount of the other includes fee 105 | */ 106 | outputAmount(input: AssetAmount, maxSlippage?: number): AssetAmount { 107 | const slippage = BigInt((maxSlippage || 0) * 100) 108 | if (input.asset.id === this.assetX.id) 109 | return this.y.withAmount( 110 | BigInt( 111 | math 112 | .evaluate!(`(${this.y.amount} * ${input.amount} * ${this.feeNum}) / ((${this.x.amount} + (${this.x.amount} * ${slippage}) / ${10000n}) * ${this.feeDenom} + ${input.amount} * ${this.feeNum})`) 113 | .toFixed(0) 114 | ) 115 | ) 116 | else 117 | return this.x.withAmount( 118 | BigInt( 119 | math 120 | .evaluate!(`(${this.x.amount} * ${input.amount} * ${this.feeNum}) / ((${this.y.amount} + (${this.y.amount} * ${slippage}) / ${10000n}) * ${this.feeDenom} + ${input.amount} * ${this.feeNum})`) 121 | .toFixed(0) 122 | ) 123 | ) 124 | } 125 | 126 | /** @param input - swap input 127 | * @return Output amount of one token for a given input amount of the other without fee and slippage 128 | */ 129 | pureOutputAmount(input: AssetAmount): AssetAmount { 130 | if (input.asset.id === this.assetX.id) 131 | return this.y.withAmount((this.y.amount * input.amount) / (this.x.amount + input.amount)) 132 | else return this.x.withAmount((this.x.amount * input.amount) / (this.y.amount + input.amount)) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/amm/common/entities/swap.ts: -------------------------------------------------------------------------------- 1 | import type {Address, TokenId} from "ergo-lib-wasm-browser" 2 | import {AssetAmount, PublicKey} from "@ergolabs/ergo-sdk" 3 | 4 | export type Swap = { 5 | readonly pk: PublicKey 6 | readonly poolAddress: Address 7 | readonly quoteAsset: TokenId 8 | readonly minQuoteOutput: bigint 9 | readonly baseInput: AssetAmount 10 | readonly timestamp: number 11 | } 12 | -------------------------------------------------------------------------------- /src/amm/common/errors/invalidParam.ts: -------------------------------------------------------------------------------- 1 | export type InvalidParam = { 2 | readonly param: string 3 | readonly error: string 4 | } 5 | -------------------------------------------------------------------------------- /src/amm/common/errors/invalidParams.ts: -------------------------------------------------------------------------------- 1 | import {InvalidParam} from "./invalidParam" 2 | 3 | export type InvalidParams = InvalidParam[] 4 | -------------------------------------------------------------------------------- /src/amm/common/errors/poolSetupFailed.ts: -------------------------------------------------------------------------------- 1 | export class PoolSetupFailed { 2 | constructor() {} 3 | } 4 | -------------------------------------------------------------------------------- /src/amm/common/interpreters/mins.ts: -------------------------------------------------------------------------------- 1 | import {MinBoxValue} from "@ergolabs/ergo-sdk" 2 | import {MinPoolBoxValue} from "../constants" 3 | 4 | export function minValueForOrder(minerFee: bigint, uiFee: bigint, exFee: bigint): bigint { 5 | return minerFee + uiFee + exFee + MinBoxValue 6 | } 7 | 8 | export function minValueForSetup(minerFee: bigint, uiFee: bigint): bigint { 9 | return minerFee * 2n + uiFee + MinBoxValue + MinPoolBoxValue 10 | } 11 | -------------------------------------------------------------------------------- /src/amm/common/interpreters/n2dexyPoolSetupAction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoxSelection, 3 | ByteaConstant, 4 | EmptyRegisters, 5 | ErgoBoxCandidate, 6 | ergoTreeFromAddress, 7 | extractOutputsFromTxRequest, 8 | InsufficientInputs, 9 | MinBoxValue, 10 | RegisterId, 11 | registers, 12 | TransactionContext, 13 | TxRequest 14 | } from "@ergolabs/ergo-sdk" 15 | import {prepend} from "ramda" 16 | import {stringToBytea} from "../../../utils/utf8" 17 | import {minValueForSetup} from "./mins" 18 | import {BurnLP, EmissionLP} from "../constants" 19 | import * as N2Dexy from "../contracts/n2dexyGOLDPoolContracts" 20 | import {PoolSetupParams} from "../models/poolSetupParams" 21 | import {PoolSetupAction} from "./poolSetupAction" 22 | 23 | export class N2dexyPoolSetupAction implements PoolSetupAction { 24 | async setup( 25 | params: PoolSetupParams, 26 | ctx: TransactionContext, 27 | uiRewardOut: ErgoBoxCandidate[] 28 | ): Promise { 29 | const [x, y] = [params.x.asset, params.y.asset] 30 | const height = ctx.network.height 31 | const inputs = ctx.inputs 32 | const outputGranted = inputs.totalOutputWithoutChange 33 | const inY = outputGranted.assets.filter(t => t.tokenId === y.id)[0] 34 | 35 | const minNErgs = minValueForSetup(ctx.feeNErgs, params.uiFee) 36 | if (outputGranted.nErgs < minNErgs) 37 | return Promise.reject( 38 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 39 | ) 40 | if (!inY) return Promise.reject(new InsufficientInputs(`Token ${y.name} not provided`)) 41 | 42 | const [tickerX, tickerY] = [x.name || x.id.slice(0, 8), y.name || y.id.slice(0, 8)] 43 | const newTokenLP = {tokenId: inputs.newTokenId, amount: EmissionLP - BurnLP} 44 | const bootOut: ErgoBoxCandidate = { 45 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 46 | ergoTree: ergoTreeFromAddress(ctx.selfAddress), 47 | creationHeight: height, 48 | assets: [newTokenLP, inY], 49 | additionalRegisters: registers([ 50 | [RegisterId.R4, new ByteaConstant(stringToBytea(`${tickerX}_${tickerY}_LP`))] 51 | ]) 52 | } 53 | const txr0: TxRequest = { 54 | inputs: inputs, 55 | dataInputs: [], 56 | outputs: prepend(bootOut, uiRewardOut), 57 | changeAddress: ctx.changeAddress, 58 | feeNErgs: ctx.feeNErgs 59 | } 60 | 61 | const lpP2Pk = ergoTreeFromAddress(ctx.changeAddress) 62 | const lpShares = {tokenId: newTokenLP.tokenId, amount: params.outputShare} 63 | const lpOut: ErgoBoxCandidate = { 64 | value: MinBoxValue, 65 | ergoTree: lpP2Pk, 66 | creationHeight: height, 67 | assets: [lpShares], 68 | additionalRegisters: EmptyRegisters 69 | } 70 | 71 | const poolBootBox = extractOutputsFromTxRequest(txr0, ctx.network)[0] 72 | const tx1Inputs = BoxSelection.safe(poolBootBox) 73 | 74 | const newTokenNFT = {tokenId: tx1Inputs.newTokenId, amount: 1n} 75 | const poolAmountLP = newTokenLP.amount - lpShares.amount 76 | const poolLP = {tokenId: newTokenLP.tokenId, amount: poolAmountLP} 77 | const poolOut: ErgoBoxCandidate = { 78 | value: poolBootBox.value - lpOut.value - ctx.feeNErgs, 79 | ergoTree: N2Dexy.pool(), 80 | creationHeight: height, 81 | assets: [newTokenNFT, poolLP, ...poolBootBox.assets.slice(1)], 82 | additionalRegisters: EmptyRegisters 83 | } 84 | const txr1: TxRequest = { 85 | inputs: tx1Inputs, 86 | dataInputs: [], 87 | outputs: [poolOut, lpOut], 88 | changeAddress: ctx.changeAddress, 89 | feeNErgs: ctx.feeNErgs 90 | } 91 | return Promise.resolve([txr0, txr1]) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/amm/common/interpreters/n2tPoolSetupAction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoxSelection, 3 | ByteaConstant, 4 | EmptyRegisters, 5 | ErgoBoxCandidate, 6 | ergoTreeFromAddress, 7 | extractOutputsFromTxRequest, 8 | InsufficientInputs, 9 | Int32Constant, 10 | MinBoxValue, 11 | RegisterId, 12 | registers, 13 | TransactionContext, 14 | TxRequest 15 | } from "@ergolabs/ergo-sdk" 16 | import {prepend} from "ramda" 17 | import {stringToBytea} from "../../../utils/utf8" 18 | import {minValueForSetup} from "./mins" 19 | import {BurnLP, EmissionLP} from "../constants" 20 | import * as N2T from "../contracts/n2tPoolContracts" 21 | import {PoolSetupParams} from "../models/poolSetupParams" 22 | import {PoolSetupAction} from "./poolSetupAction" 23 | 24 | export class N2tPoolSetupAction implements PoolSetupAction { 25 | async setup( 26 | params: PoolSetupParams, 27 | ctx: TransactionContext, 28 | uiRewardOut: ErgoBoxCandidate[] 29 | ): Promise { 30 | const [x, y] = [params.x.asset, params.y.asset] 31 | const height = ctx.network.height 32 | const inputs = ctx.inputs 33 | const outputGranted = inputs.totalOutputWithoutChange 34 | const inY = outputGranted.assets.filter(t => t.tokenId === y.id)[0] 35 | 36 | const minNErgs = minValueForSetup(ctx.feeNErgs, params.uiFee) 37 | if (outputGranted.nErgs < minNErgs) 38 | return Promise.reject( 39 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 40 | ) 41 | if (!inY) return Promise.reject(new InsufficientInputs(`Token ${y.name} not provided`)) 42 | 43 | const [tickerX, tickerY] = [x.name || x.id.slice(0, 8), y.name || y.id.slice(0, 8)] 44 | const newTokenLP = {tokenId: inputs.newTokenId, amount: EmissionLP - BurnLP} 45 | const bootOut: ErgoBoxCandidate = { 46 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 47 | ergoTree: ergoTreeFromAddress(ctx.selfAddress), 48 | creationHeight: height, 49 | assets: [newTokenLP, inY], 50 | additionalRegisters: registers([ 51 | [RegisterId.R4, new ByteaConstant(stringToBytea(`${tickerX}_${tickerY}_LP`))] 52 | ]) 53 | } 54 | const txr0: TxRequest = { 55 | inputs: inputs, 56 | dataInputs: [], 57 | outputs: prepend(bootOut, uiRewardOut), 58 | changeAddress: ctx.changeAddress, 59 | feeNErgs: ctx.feeNErgs 60 | } 61 | 62 | const lpP2Pk = ergoTreeFromAddress(ctx.changeAddress) 63 | const lpShares = {tokenId: newTokenLP.tokenId, amount: params.outputShare} 64 | const lpOut: ErgoBoxCandidate = { 65 | value: MinBoxValue, 66 | ergoTree: lpP2Pk, 67 | creationHeight: height, 68 | assets: [lpShares], 69 | additionalRegisters: EmptyRegisters 70 | } 71 | 72 | const poolBootBox = extractOutputsFromTxRequest(txr0, ctx.network)[0] 73 | const tx1Inputs = BoxSelection.safe(poolBootBox) 74 | 75 | const newTokenNFT = {tokenId: tx1Inputs.newTokenId, amount: 1n} 76 | const poolAmountLP = newTokenLP.amount - lpShares.amount 77 | const poolLP = {tokenId: newTokenLP.tokenId, amount: poolAmountLP} 78 | const poolOut: ErgoBoxCandidate = { 79 | value: poolBootBox.value - lpOut.value - ctx.feeNErgs, 80 | ergoTree: N2T.pool(), 81 | creationHeight: height, 82 | assets: [newTokenNFT, poolLP, ...poolBootBox.assets.slice(1)], 83 | additionalRegisters: registers([[RegisterId.R4, new Int32Constant(params.feeNumerator)]]) 84 | } 85 | const txr1: TxRequest = { 86 | inputs: tx1Inputs, 87 | dataInputs: [], 88 | outputs: [poolOut, lpOut], 89 | changeAddress: ctx.changeAddress, 90 | feeNErgs: ctx.feeNErgs 91 | } 92 | return Promise.resolve([txr0, txr1]) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/amm/common/interpreters/poolActions.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTx, Prover, TransactionContext, TxAssembler, TxRequest} from "@ergolabs/ergo-sdk" 2 | import {ExFeeType} from "../../../types" 3 | import {AmmPool} from "../entities/ammPool" 4 | import {DepositParams} from "../models/depositParams" 5 | import {PoolSetupParams} from "../models/poolSetupParams" 6 | import {RedeemParams} from "../models/redeemParams" 7 | import {SwapParams} from "../models/swapParams" 8 | 9 | export interface PoolActions { 10 | /** Interpret `setup` operation on a pool to a chain of transactions. 11 | */ 12 | setup(params: PoolSetupParams, ctx: TransactionContext): Promise 13 | 14 | /** Interpret `deposit` operation on a pool to a transaction. 15 | */ 16 | deposit(params: DepositParams, ctx: TransactionContext): Promise 17 | 18 | /** Interpret `redeem` operation on a pool to a transaction. 19 | */ 20 | redeem(params: RedeemParams, ctx: TransactionContext): Promise 21 | 22 | /** Interpret `swap` operation on a pool to a transaction. 23 | */ 24 | swap(params: SwapParams, ctx: TransactionContext): Promise 25 | } 26 | 27 | class PoolActionsWrapper implements PoolActions { 28 | constructor( 29 | public readonly impl: PoolActions, 30 | public readonly prover: Prover, 31 | public readonly txAsm: TxAssembler 32 | ) {} 33 | 34 | async setup(params: PoolSetupParams, ctx: TransactionContext): Promise { 35 | const [txr0, txr1] = await this.impl.setup(params, ctx) 36 | const tx0 = await this.prover.sign(this.txAsm.assemble(txr0, ctx.network)) 37 | const tx1 = await this.prover.sign(this.txAsm.assemble(txr1, ctx.network)) 38 | return [tx0, tx1] 39 | } 40 | 41 | async deposit(params: DepositParams, ctx: TransactionContext): Promise { 42 | return this.prover.sign(this.txAsm.assemble(await this.impl.deposit(params, ctx), ctx.network)) 43 | } 44 | 45 | async redeem(params: RedeemParams, ctx: TransactionContext): Promise { 46 | return this.prover.sign(this.txAsm.assemble(await this.impl.redeem(params, ctx), ctx.network)) 47 | } 48 | 49 | async swap(params: SwapParams, ctx: TransactionContext): Promise { 50 | return this.prover.sign(this.txAsm.assemble(await this.impl.swap(params, ctx), ctx.network)) 51 | } 52 | } 53 | 54 | export type PoolActionsSelector = ( 55 | pool: AmmPool | PoolSetupParams 56 | ) => PoolActions 57 | 58 | export function wrapPoolActions( 59 | actions: PoolActions, 60 | prover: Prover, 61 | txAsm: TxAssembler 62 | ): PoolActions { 63 | return new PoolActionsWrapper(actions, prover, txAsm) 64 | } 65 | -------------------------------------------------------------------------------- /src/amm/common/interpreters/poolSetupAction.ts: -------------------------------------------------------------------------------- 1 | import {ErgoBoxCandidate, TransactionContext, TxRequest} from "@ergolabs/ergo-sdk" 2 | import {PoolSetupParams} from "../models/poolSetupParams" 3 | 4 | export interface PoolSetupAction { 5 | setup( 6 | params: PoolSetupParams, 7 | ctx: TransactionContext, 8 | uiRewardOut: ErgoBoxCandidate[] 9 | ): Promise 10 | } 11 | -------------------------------------------------------------------------------- /src/amm/common/interpreters/refunds.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoxSelection, 3 | DefaultBoxSelector, 4 | EmptyRegisters, 5 | ErgoNetwork, 6 | ergoTreeFromAddress, 7 | ErgoTx, 8 | InsufficientInputs, 9 | MinTransactionContext, 10 | Prover, 11 | treeTemplateFromErgoTree, 12 | TxAssembler, 13 | TxRequest 14 | } from "@ergolabs/ergo-sdk" 15 | import * as LQ_MINING_CONTRACTS from "../../../lqmining/contracts/proxyValidators" 16 | import {RefundParams} from "../../../models/refundParams" 17 | import * as N2T_NATIVE from "../../nativeFee/contracts/n2tTemplates" 18 | import * as N2Dexy_NATIVE from "../../nativeFee/contracts/n2dexyTemplates" 19 | import * as T2T_NATIVE from "../../nativeFee/contracts/t2tTemplates" 20 | import * as N2T_SPF from "../../spfFee/contracts/n2tTemplates" 21 | import * as T2T_SPF from "../../spfFee/contracts/t2tTemplates" 22 | 23 | export interface Refunds { 24 | /** Redeem assets from a proxy order box. 25 | */ 26 | refund(params: RefundParams, ctx: MinTransactionContext): Promise 27 | } 28 | 29 | const RefundableTemplates = [ 30 | T2T_NATIVE.DepositTemplate, 31 | T2T_NATIVE.RedeemTemplate, 32 | T2T_NATIVE.SwapTemplate, 33 | N2T_NATIVE.DepositTemplate, 34 | N2T_NATIVE.RedeemTemplate, 35 | N2T_NATIVE.SwapSellTemplate, 36 | N2T_NATIVE.SwapBuyTemplate, 37 | N2Dexy_NATIVE.DepositTemplate, 38 | N2Dexy_NATIVE.RedeemTemplate, 39 | N2Dexy_NATIVE.SwapSellTemplate, 40 | N2Dexy_NATIVE.SwapBuyTemplate, 41 | 42 | T2T_SPF.DepositTemplate, 43 | T2T_SPF.RedeemTemplate, 44 | T2T_SPF.SwapTemplate, 45 | N2T_SPF.DepositTemplate, 46 | N2T_SPF.RedeemTemplate, 47 | N2T_SPF.SwapSellTemplate, 48 | N2T_SPF.SwapBuyTemplate, 49 | 50 | LQ_MINING_CONTRACTS.depositTemplate, 51 | LQ_MINING_CONTRACTS.redeemTemplate, 52 | 53 | N2T_NATIVE.OldDepositTemplate, 54 | N2T_NATIVE.OldRedeemTemplate, 55 | N2T_NATIVE.OldSwapSellTemplate, 56 | N2T_NATIVE.OldSwapBuyTemplate, 57 | 58 | T2T_NATIVE.OldDepositTemplate, 59 | T2T_NATIVE.OldRedeemTemplate, 60 | T2T_NATIVE.OldSwapTemplate, 61 | ] 62 | 63 | export class AmmOrderRefunds implements Refunds { 64 | constructor(public readonly network: ErgoNetwork) {} 65 | 66 | async refund(params: RefundParams, ctx: MinTransactionContext): Promise { 67 | const tx = await this.network.getTx(params.txId) 68 | const outputToRefund = tx?.outputs.find(o => { 69 | const template = treeTemplateFromErgoTree(o.ergoTree) 70 | return RefundableTemplates.includes(template) 71 | }) 72 | if (!outputToRefund) { 73 | return Promise.reject(`No AMM orders found in the given Tx{id=${params.txId}`) 74 | } 75 | let outputNErg: bigint 76 | let inputs: BoxSelection 77 | 78 | if (outputToRefund.value - ctx.feeNErgs >= 0) { 79 | outputNErg = outputToRefund.value - ctx.feeNErgs 80 | inputs = BoxSelection.safe(outputToRefund) 81 | } else { 82 | if (!params.utxos?.length) { 83 | return Promise.reject("Insufficient Inputs for refund") 84 | } 85 | outputNErg = outputToRefund.value 86 | const userInputs = DefaultBoxSelector.select(params.utxos, {assets: [], nErgs: ctx.feeNErgs}) 87 | if (userInputs instanceof InsufficientInputs) { 88 | return Promise.reject("Insufficient Inputs for refund") 89 | } 90 | inputs = BoxSelection.safe(outputToRefund, userInputs.inputs, userInputs.change) 91 | } 92 | 93 | const refundOut = { 94 | value: outputNErg, 95 | ergoTree: ergoTreeFromAddress(params.recipientAddress), 96 | creationHeight: ctx.network.height, 97 | assets: outputToRefund.assets, 98 | additionalRegisters: EmptyRegisters 99 | } 100 | 101 | return Promise.resolve({ 102 | inputs: inputs, 103 | dataInputs: [], 104 | outputs: [refundOut], 105 | changeAddress: params.recipientAddress, 106 | feeNErgs: ctx.feeNErgs 107 | }) 108 | } 109 | } 110 | 111 | export class AmmOrderRefundsWrapper implements Refunds { 112 | constructor( 113 | public readonly refunds: Refunds, 114 | public readonly prover: Prover, 115 | public readonly txAsm: TxAssembler 116 | ) {} 117 | 118 | async refund(params: RefundParams, ctx: MinTransactionContext): Promise { 119 | return this.refunds 120 | .refund(params, ctx) 121 | .then(txRequest => this.prover.sign(this.txAsm.assemble(txRequest, ctx.network))) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/amm/common/interpreters/t2tPoolSetupAction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoxSelection, 3 | ByteaConstant, 4 | EmptyRegisters, 5 | ErgoBoxCandidate, 6 | ergoTreeFromAddress, 7 | extractOutputsFromTxRequest, 8 | InsufficientInputs, 9 | Int32Constant, 10 | MinBoxValue, 11 | RegisterId, 12 | registers, 13 | TransactionContext, 14 | TxRequest 15 | } from "@ergolabs/ergo-sdk" 16 | import {prepend} from "ramda" 17 | import {stringToBytea} from "../../../utils/utf8" 18 | import {minValueForSetup} from "./mins" 19 | import {BurnLP, EmissionLP} from "../constants" 20 | import * as T2T from "../contracts/t2tPoolContracts" 21 | import {PoolSetupParams} from "../models/poolSetupParams" 22 | import {PoolSetupAction} from "./poolSetupAction" 23 | 24 | export class T2tPoolSetupAction implements PoolSetupAction { 25 | async setup( 26 | params: PoolSetupParams, 27 | ctx: TransactionContext, 28 | uiRewardOut: ErgoBoxCandidate[] 29 | ): Promise { 30 | const [x, y] = [params.x.asset, params.y.asset] 31 | const height = ctx.network.height 32 | const inputs = ctx.inputs 33 | const outputGranted = inputs.totalOutputWithoutChange 34 | const pairIn = [ 35 | outputGranted.assets.filter(t => t.tokenId === x.id), 36 | outputGranted.assets.filter(t => t.tokenId === y.id) 37 | ].flat() 38 | 39 | const minNErgs = minValueForSetup(ctx.feeNErgs, params.uiFee) 40 | if (outputGranted.nErgs < minNErgs) 41 | return Promise.reject( 42 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 43 | ) 44 | if (pairIn.length !== 2) 45 | return Promise.reject(new InsufficientInputs(`Token pair {${x.name}|${y.name}} not provided`)) 46 | 47 | const [tickerX, tickerY] = [x.name || x.id.slice(0, 8), y.name || y.id.slice(0, 8)] 48 | const newTokenLP = {tokenId: inputs.newTokenId, amount: EmissionLP - BurnLP} 49 | const bootOut: ErgoBoxCandidate = { 50 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 51 | ergoTree: ergoTreeFromAddress(ctx.selfAddress), 52 | creationHeight: height, 53 | assets: [newTokenLP, ...pairIn], 54 | additionalRegisters: registers([ 55 | [RegisterId.R4, new ByteaConstant(stringToBytea(`${tickerX}_${tickerY}_LP`))] 56 | ]) 57 | } 58 | const txr0: TxRequest = { 59 | inputs: inputs, 60 | dataInputs: [], 61 | outputs: prepend(bootOut, uiRewardOut), 62 | changeAddress: ctx.changeAddress, 63 | feeNErgs: ctx.feeNErgs 64 | } 65 | 66 | const lpP2Pk = ergoTreeFromAddress(ctx.changeAddress) 67 | const lpShares = {tokenId: newTokenLP.tokenId, amount: params.outputShare} 68 | const lpOut: ErgoBoxCandidate = { 69 | value: MinBoxValue, 70 | ergoTree: lpP2Pk, 71 | creationHeight: height, 72 | assets: [lpShares], 73 | additionalRegisters: EmptyRegisters 74 | } 75 | 76 | const poolBootBox = extractOutputsFromTxRequest(txr0, ctx.network)[0] 77 | const tx1Inputs = BoxSelection.safe(poolBootBox) 78 | 79 | const newTokenNFT = {tokenId: tx1Inputs.newTokenId, amount: 1n} 80 | const poolAmountLP = newTokenLP.amount - lpShares.amount 81 | const poolLP = {tokenId: newTokenLP.tokenId, amount: poolAmountLP} 82 | const poolOut: ErgoBoxCandidate = { 83 | value: poolBootBox.value - lpOut.value - ctx.feeNErgs, 84 | ergoTree: T2T.pool(), 85 | creationHeight: height, 86 | assets: [newTokenNFT, poolLP, ...poolBootBox.assets.slice(1)], 87 | additionalRegisters: registers([[RegisterId.R4, new Int32Constant(params.feeNumerator)]]) 88 | } 89 | const txr1: TxRequest = { 90 | inputs: tx1Inputs, 91 | dataInputs: [], 92 | outputs: [poolOut, lpOut], 93 | changeAddress: ctx.changeAddress, 94 | feeNErgs: ctx.feeNErgs 95 | } 96 | 97 | return Promise.resolve([txr0, txr1]) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/amm/common/math/swap.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | import {AssetAmount} from "@ergolabs/ergo-sdk" 3 | import {swapVars} from "./swap" 4 | 5 | test("Swap math", t => { 6 | const minDexFee = 10000000n 7 | const nitro = 1.2 8 | const minOutput = new AssetAmount({id: "btx"}, 200000000n) 9 | t.deepEqual(swapVars(minDexFee, nitro, minOutput), [ 10 | 0.05, 11 | { 12 | maxExFee: 12000000n, 13 | maxOutput: minOutput.withAmount(240000000n), 14 | minExFee: 10000000n, 15 | minOutput: minOutput 16 | } 17 | ]) 18 | }) 19 | 20 | test("Swap math (adjustment required)", t => { 21 | const minDexFee = 10000000n 22 | const nitro = 1.2 23 | const minOutput = new AssetAmount({id: "btx"}, 20007399322n) 24 | t.deepEqual(swapVars(minDexFee, nitro, minOutput), [ 25 | 0.000499815085362147, 26 | { 27 | maxExFee: 12000000n, 28 | maxOutput: minOutput.withAmount(24008879186n), 29 | minExFee: 9999999n, 30 | minOutput: minOutput 31 | } 32 | ]) 33 | }) 34 | -------------------------------------------------------------------------------- /src/amm/common/math/swap.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount} from "@ergolabs/ergo-sdk" 2 | import {decimalToFractional} from "../../../utils/math" 3 | import {I64Max} from "../constants" 4 | 5 | export type SwapExtremums = { 6 | minExFee: bigint 7 | maxExFee: bigint 8 | minOutput: AssetAmount 9 | maxOutput: AssetAmount 10 | } 11 | 12 | /** @param minExFee - minimal Execution fee 13 | * @param nitro - minimal dex fee multiplier 14 | * @param minOutput - minimal output expected 15 | * @return DEX fee per token, swap extremums 16 | */ 17 | export function swapVars( 18 | minExFee: bigint, 19 | nitro: number, 20 | minOutput: AssetAmount 21 | ): [number, SwapExtremums] | undefined { 22 | if (minOutput.amount > 0) { 23 | let exFeePerToken = Number(minExFee) / Number(minOutput.amount) 24 | while (true) { 25 | const [n, d] = decimalToFractional(exFeePerToken) 26 | if (n <= I64Max && d <= I64Max) break 27 | else { 28 | const feeStr = String(exFeePerToken) 29 | const idx = feeStr.indexOf(".") 30 | const decimalsNum = feeStr.slice(idx + 1).length 31 | exFeePerToken = Number(exFeePerToken.toFixed(decimalsNum - 1)) 32 | } 33 | } 34 | const adjustedMinExFee = Math.floor(exFeePerToken * Number(minOutput.amount)) 35 | const maxExFee = Math.floor(Number(minExFee) * nitro) 36 | const maxOutput = minOutput.withAmount(BigInt(Math.floor(maxExFee / exFeePerToken))) 37 | return [ 38 | exFeePerToken, 39 | {minExFee: BigInt(adjustedMinExFee), maxExFee: BigInt(maxExFee), minOutput, maxOutput} 40 | ] 41 | } else { 42 | return undefined 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/amm/common/models/ammOperation.ts: -------------------------------------------------------------------------------- 1 | import {BoxId, TxId} from "@ergolabs/ergo-sdk" 2 | import {OperationSummary} from "./operationSummary" 3 | 4 | export type OpStatus = "pending" | "submitted" | "executed" | "settled" | "refund" 5 | 6 | export const Pending: OpStatus = "pending" 7 | export const Submitted: OpStatus = "submitted" 8 | export const Executed: OpStatus = "executed" 9 | export const Settled: OpStatus = "settled" 10 | 11 | export type AmmOperationType = "swap" | "deposit" | "redeem" | "setup" 12 | export type RefundableAmmOperationType = Exclude 13 | 14 | export const Swap: AmmOperationType = "swap" 15 | export const Deposit: AmmOperationType = "deposit" 16 | export const Redeem: AmmOperationType = "redeem" 17 | export const Setup: AmmOperationType = "setup" 18 | 19 | export type AmmOperation = { 20 | tag: "order" 21 | txId: TxId 22 | boxId: BoxId 23 | status: OpStatus 24 | summary: OperationSummary 25 | } 26 | -------------------------------------------------------------------------------- /src/amm/common/models/ammOrderInfo.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, AssetInfo} from "@ergolabs/ergo-sdk" 2 | import {PoolId} from "../types" 3 | 4 | export type AmmOrderInfo = {poolId: PoolId} & (Swap | Deposit | Redeem) 5 | 6 | export type Swap = { 7 | type: "swap" 8 | from: AssetAmount 9 | to: AssetInfo 10 | } 11 | 12 | export type Deposit = { 13 | type: "deposit" 14 | inX: AssetAmount 15 | inY: AssetAmount 16 | } 17 | 18 | export type Redeem = { 19 | type: "redeem" 20 | inLP: AssetAmount 21 | } 22 | -------------------------------------------------------------------------------- /src/amm/common/models/ammPoolInfo.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount} from "@ergolabs/ergo-sdk" 2 | import {PoolId} from "../types" 3 | 4 | export type AmmPoolInfo = { 5 | id: PoolId 6 | lp: AssetAmount 7 | reservesX: AssetAmount 8 | reservesY: AssetAmount 9 | } 10 | -------------------------------------------------------------------------------- /src/amm/common/models/depositParams.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, PublicKey} from "@ergolabs/ergo-sdk" 2 | import {ExFee, ExFeeType} from "../../../types" 3 | import {PoolId} from "../types" 4 | 5 | export type DepositParams = { 6 | readonly poolId: PoolId 7 | readonly x: AssetAmount 8 | readonly y: AssetAmount 9 | readonly pk: PublicKey 10 | readonly exFee: ExFee 11 | readonly uiFee: bigint 12 | } 13 | -------------------------------------------------------------------------------- /src/amm/common/models/operationSummary.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, AssetInfo} from "@ergolabs/ergo-sdk" 2 | import {PoolId} from "../types" 3 | import {AmmOperationType} from "./ammOperation" 4 | 5 | export type OperationSummary = {type: AmmOperationType; poolId: PoolId} & ( 6 | | SwapSummary 7 | | DepositSummary 8 | | RedeemSummary 9 | | PoolSetupSummary 10 | ) 11 | 12 | export type SwapSummary = { 13 | from: AssetAmount 14 | to: AssetInfo 15 | } 16 | 17 | export type DepositSummary = { 18 | inX: AssetAmount 19 | inY: AssetAmount 20 | } 21 | 22 | export type RedeemSummary = { 23 | inLP: AssetAmount 24 | } 25 | 26 | export type PoolSetupSummary = { 27 | initX: AssetAmount 28 | initY: AssetAmount 29 | } 30 | -------------------------------------------------------------------------------- /src/amm/common/models/operations.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, AugErgoBox, BoxId, TxId} from "@ergolabs/ergo-sdk" 2 | import {AmmOrderInfo} from "./ammOrderInfo" 3 | import {AmmPoolInfo} from "./ammPoolInfo" 4 | 5 | export type AmmOrderStatus = "pending" | "submitted" | "executed" | "settled" | "refund" | "inProgress" 6 | 7 | export type AmmOrderType = "swap" | "deposit" | "redeem" 8 | 9 | export type AmmDexKind = "n2t" | "t2t" 10 | 11 | export type AmmDexOperationType = "order" | "refund" | "setup" 12 | 13 | export type AmmOrder = { 14 | type: "order" 15 | timestamp?: bigint 16 | txId: TxId 17 | boxId: BoxId 18 | status: AmmOrderStatus 19 | order: AmmOrderInfo 20 | orderInput?: AugErgoBox 21 | } 22 | 23 | export type TxStatus = "pending" | "executed" | "settled" 24 | 25 | export type PoolSetup = { 26 | type: "setup" 27 | timestamp?: bigint 28 | txId: TxId 29 | status: TxStatus 30 | pool: AmmPoolInfo 31 | reward: AssetAmount 32 | } 33 | 34 | export type RefundOperation = { 35 | type: "refund" 36 | timestamp?: bigint 37 | txId: TxId 38 | status: TxStatus 39 | operation: AmmOrderType 40 | } 41 | 42 | export type AmmDexOperation = AmmOrder | RefundOperation | PoolSetup 43 | -------------------------------------------------------------------------------- /src/amm/common/models/poolSetupParams.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, isNative} from "@ergolabs/ergo-sdk" 2 | import {InvalidParams} from "../errors/invalidParams" 3 | import {PoolFeeMaxDecimals, PoolFeeScale} from "../constants" 4 | import {sqrt} from "../../../utils/sqrt" 5 | 6 | export type PoolSetupParams = { 7 | readonly x: AssetAmount 8 | readonly y: AssetAmount 9 | readonly feeNumerator: number 10 | readonly outputShare: bigint 11 | readonly uiFee: bigint 12 | } 13 | 14 | export function makePoolSetupParams( 15 | x: AssetAmount, 16 | y: AssetAmount, 17 | fee: number, 18 | uiFee: bigint 19 | ): PoolSetupParams | InvalidParams { 20 | const invalidPair = x.asset === y.asset ? [{param: "x|y", error: "x|y must contain different tokens"}] : [] 21 | const invalidFeeRange = fee > 1 && fee < 0 ? [{param: "fee", error: "Fee must be in range [0, 1]"}] : [] 22 | const invalidFeeResolution = 23 | fee.toString().split(".")[1].length > PoolFeeMaxDecimals 24 | ? [ 25 | { 26 | param: "fee", 27 | error: `Fee must have <= ${PoolFeeMaxDecimals} decimals` 28 | } 29 | ] 30 | : [] 31 | const errors = [invalidPair, invalidFeeRange, invalidFeeResolution].flat() 32 | 33 | if (errors.length == 0) { 34 | const feeNumerator = (1 - fee) * PoolFeeScale 35 | const outputShare = sqrt(x.amount * y.amount) 36 | return isNative(y.asset) 37 | ? {y, x, feeNumerator, outputShare, uiFee: uiFee} 38 | : {x, y, feeNumerator, outputShare, uiFee: uiFee} 39 | } else { 40 | return errors 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/amm/common/models/redeemParams.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, PublicKey} from "@ergolabs/ergo-sdk" 2 | import {ExFee, ExFeeType} from "../../../types" 3 | import {PoolId} from "../types" 4 | 5 | export type RedeemParams = { 6 | readonly poolId: PoolId 7 | readonly pk: PublicKey 8 | readonly lp: AssetAmount 9 | readonly exFee: ExFee 10 | readonly uiFee: bigint 11 | } 12 | -------------------------------------------------------------------------------- /src/amm/common/models/refundOperation.ts: -------------------------------------------------------------------------------- 1 | import {TxId} from "@ergolabs/ergo-sdk" 2 | import {RefundableAmmOperationType} from "./ammOperation" 3 | 4 | export type RefundStatus = "pending" | "executed" | "settled" 5 | 6 | export const Pending: RefundStatus = "pending" 7 | export const Executed: RefundStatus = "executed" 8 | export const Settled: RefundStatus = "settled" 9 | 10 | export type RefundOperation = { 11 | tag: "refund" 12 | txId: TxId 13 | status: RefundStatus 14 | operation: RefundableAmmOperationType 15 | } 16 | -------------------------------------------------------------------------------- /src/amm/common/models/swapParams.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, PublicKey, TokenId} from "@ergolabs/ergo-sdk" 2 | import {ExFee, ExFeePerToken, ExFeeType} from "../../../types" 3 | import {PoolId} from "../types" 4 | 5 | export type SwapParams = { 6 | readonly pk: PublicKey 7 | readonly poolId: PoolId 8 | readonly poolFeeNum: number 9 | readonly baseInput: AssetAmount 10 | readonly quoteAsset: TokenId 11 | readonly minQuoteOutput: bigint 12 | readonly exFeePerToken: ExFeePerToken 13 | readonly maxExFee: ExFee 14 | readonly uiFee: bigint 15 | } 16 | -------------------------------------------------------------------------------- /src/amm/common/parsers/ammPoolsInfoParser.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, ErgoBox, treeTemplateFromErgoTree} from "@ergolabs/ergo-sdk" 2 | import * as N2DEXY from "../contracts/n2dexyGOLDtemplates" 3 | import * as N2T from "../contracts/n2tTemplates" 4 | import * as T2T from "../contracts/t2tTemplates" 5 | import {AmmPoolInfo} from "../models/ammPoolInfo" 6 | 7 | export interface AmmPoolsInfoParser { 8 | /** Parse AMM pool info from a given box. 9 | */ 10 | parse(bx: ErgoBox): AmmPoolInfo | undefined 11 | } 12 | 13 | export class DefaultAmmPoolsInfoParser implements AmmPoolsInfoParser { 14 | parse(bx: ErgoBox): AmmPoolInfo | undefined { 15 | const template = treeTemplateFromErgoTree(bx.ergoTree) 16 | if (template === T2T.PoolTemplate) return this.parseT2T(bx) 17 | else if (template === N2T.PoolTemplate) return this.parseN2T(bx) 18 | else if (template === N2DEXY.PoolTemplate) return this.parseN2D(bx) 19 | else return undefined 20 | } 21 | 22 | private parseT2T(bx: ErgoBox): AmmPoolInfo | undefined { 23 | const poolId = bx.assets[0]?.tokenId 24 | const lp = bx.assets[1] 25 | const reservesX = bx.assets[2] 26 | const reservesY = bx.assets[3] 27 | return poolId && reservesX && reservesY 28 | ? { 29 | id: poolId, 30 | reservesX: AssetAmount.fromToken(reservesX), 31 | reservesY: AssetAmount.fromToken(reservesY), 32 | lp: AssetAmount.fromToken(lp) 33 | } 34 | : undefined 35 | } 36 | 37 | private parseN2T(bx: ErgoBox): AmmPoolInfo | undefined { 38 | const poolId = bx.assets[0]?.tokenId 39 | const lp = bx.assets[1] 40 | const reservesY = bx.assets[2] 41 | return poolId && reservesY 42 | ? { 43 | id: poolId, 44 | reservesX: AssetAmount.native(bx.value), 45 | reservesY: AssetAmount.fromToken(reservesY), 46 | lp: AssetAmount.fromToken(lp) 47 | } 48 | : undefined 49 | } 50 | 51 | private parseN2D(bx: ErgoBox): AmmPoolInfo | undefined { 52 | const poolId = bx.assets[0]?.tokenId 53 | const lp = bx.assets[1] 54 | const reservesY = bx.assets[2] 55 | return poolId && reservesY 56 | ? { 57 | id: poolId, 58 | reservesX: AssetAmount.native(bx.value), 59 | reservesY: AssetAmount.fromToken(reservesY), 60 | lp: AssetAmount.fromToken(lp) 61 | } 62 | : undefined 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/amm/common/parsers/ammPoolsParser.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, deserializeConstant, ErgoBox, Int32Constant, RegisterId} from "@ergolabs/ergo-sdk" 2 | import {FromBox} from "../../../fromBox" 3 | import {DexyGOLDFeeNum} from "../constants" 4 | import {AmmPool} from "../entities/ammPool" 5 | 6 | export class T2TAmmPoolsParser implements FromBox { 7 | from(box: ErgoBox): AmmPool | undefined { 8 | const r4 = box.additionalRegisters[RegisterId.R4] 9 | if (box.assets.length == 4 && r4) { 10 | const nft = box.assets[0].tokenId 11 | const lp = AssetAmount.fromToken(box.assets[1]) 12 | const assetX = AssetAmount.fromToken(box.assets[2]) 13 | const assetY = AssetAmount.fromToken(box.assets[3]) 14 | const feeNum = deserializeConstant(r4) 15 | if (feeNum instanceof Int32Constant) return new AmmPool(nft, lp, assetX, assetY, feeNum.value) 16 | } 17 | return undefined 18 | } 19 | 20 | fromMany(boxes: ErgoBox[]): AmmPool[] { 21 | const pools = [] 22 | for (const box of boxes) { 23 | const pool = this.from(box) 24 | if (pool) pools.push(pool) 25 | } 26 | return pools 27 | } 28 | } 29 | 30 | export class N2TAmmPoolsParser implements FromBox { 31 | from(box: ErgoBox): AmmPool | undefined { 32 | const r4 = box.additionalRegisters[RegisterId.R4] 33 | if (box.assets.length == 3 && r4) { 34 | const nft = box.assets[0].tokenId 35 | const lp = AssetAmount.fromToken(box.assets[1]) 36 | const assetX = AssetAmount.native(box.value) 37 | const assetY = AssetAmount.fromToken(box.assets[2]) 38 | const feeNum = deserializeConstant(r4) 39 | if (feeNum instanceof Int32Constant) return new AmmPool(nft, lp, assetX, assetY, feeNum.value) 40 | } 41 | return undefined 42 | } 43 | 44 | fromMany(boxes: ErgoBox[]): AmmPool[] { 45 | const pools = [] 46 | for (const box of boxes) { 47 | const pool = this.from(box) 48 | if (pool) pools.push(pool) 49 | } 50 | return pools 51 | } 52 | } 53 | 54 | export class N2DexyAmmPoolsParser implements FromBox { 55 | from(box: ErgoBox): AmmPool | undefined { 56 | if (box.assets.length == 3) { 57 | const nft = box.assets[0].tokenId 58 | const lp = AssetAmount.fromToken(box.assets[1]) 59 | const assetX = AssetAmount.native(box.value) 60 | const assetY = AssetAmount.fromToken(box.assets[2]) 61 | return new AmmPool(nft, lp, assetX, assetY, DexyGOLDFeeNum) 62 | } 63 | return undefined 64 | } 65 | 66 | fromMany(boxes: ErgoBox[]): AmmPool[] { 67 | const pools = [] 68 | for (const box of boxes) { 69 | const pool = this.from(box) 70 | if (pool) pools.push(pool) 71 | } 72 | return pools 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/amm/common/services/history.ts: -------------------------------------------------------------------------------- 1 | import {Address, AssetAmount, AugErgoBox, AugErgoTx, ErgoNetwork} from "@ergolabs/ergo-sdk" 2 | import {AmmOrderInfo} from "../models/ammOrderInfo" 3 | import {AmmDexOperation} from "../models/operations" 4 | import {AmmOrdersParser, DefaultAmmOrdersParser} from "../parsers/ammOrdersParser" 5 | import {AmmPoolsInfoParser, DefaultAmmPoolsInfoParser} from "../parsers/ammPoolsInfoParser" 6 | 7 | export interface History { 8 | /** Get operations by a given address. 9 | * @address - address to fetch operations by 10 | * @displayLatest - number of latest operations to display 11 | */ 12 | getAllByAddress(address: Address, displayLatest: number): Promise 13 | 14 | /** Get operations by a given list of addresses. 15 | * @address - address to fetch operations by 16 | * @displayLatest - number of latest operations to display 17 | */ 18 | getAllByAddresses(addresses: Address[], displayLatest: number): Promise 19 | } 20 | 21 | export function makeHistory(network: ErgoNetwork): History { 22 | const ordersParser = new DefaultAmmOrdersParser() 23 | const poolParser = new DefaultAmmPoolsInfoParser() 24 | return new NetworkHistory(network, ordersParser, poolParser) 25 | } 26 | 27 | export class NetworkHistory implements History { 28 | constructor( 29 | public readonly network: ErgoNetwork, 30 | public readonly ordersParser: AmmOrdersParser, 31 | public readonly poolsParser: AmmPoolsInfoParser 32 | ) {} 33 | 34 | async getAllByAddress(address: Address, displayLatest: number): Promise { 35 | const ops: AmmDexOperation[] = [] 36 | let uOffset = 0 37 | const limit = 100 38 | while (ops.length < displayLatest) { 39 | const [txs, total] = await this.network.getUTxsByAddress(address, {offset: uOffset, limit: 100}) 40 | for (const tx of txs) { 41 | const op = this.parseOp(tx, false, [address]) 42 | if (op) ops.push(op) 43 | } 44 | if (uOffset < total) uOffset += limit 45 | else break 46 | } 47 | let offset = 0 48 | while (ops.length < displayLatest) { 49 | const [txs, total] = await this.network.getTxsByAddress(address, {offset, limit: 100}) 50 | for (const tx of txs) { 51 | const op = this.parseOp(tx, true, [address]) 52 | if (op) ops.push(op) 53 | } 54 | if (offset < total) offset += limit 55 | else break 56 | } 57 | return ops 58 | } 59 | 60 | async getAllByAddresses(addresses: Address[], displayLatest: number): Promise { 61 | const ops: AmmDexOperation[] = [] 62 | for (const addr of addresses) { 63 | let uOffset = 0 64 | const limit = 100 65 | while (ops.length < displayLatest) { 66 | const [txs, total] = await this.network.getUTxsByAddress(addr, {offset: uOffset, limit: 100}) 67 | for (const tx of txs) { 68 | const op = this.parseOp(tx, false, addresses) 69 | if (op) ops.push(op) 70 | } 71 | if (uOffset < total) uOffset += limit 72 | else break 73 | } 74 | let offset = 0 75 | while (ops.length < displayLatest) { 76 | const [txs, total] = await this.network.getTxsByAddress(addr, {offset, limit: 100}) 77 | for (const tx of txs) { 78 | const op = this.parseOp(tx, true, addresses) 79 | if (op) ops.push(op) 80 | } 81 | if (offset < total) offset += limit 82 | else break 83 | } 84 | } 85 | return ops 86 | } 87 | 88 | private parseOp(tx: AugErgoTx, confirmed: boolean, addresses: Address[]): AmmDexOperation | undefined { 89 | const outputOrder = tx.outputs 90 | .map(o => { 91 | const op = this.ordersParser.parse(o) 92 | return op ? ([op, o] as [AmmOrderInfo, AugErgoBox]) : undefined 93 | }) 94 | .find(x => !!x) 95 | 96 | if (outputOrder) { 97 | const [summary, output] = outputOrder 98 | if (!output.spentTransactionId) 99 | return { 100 | type: "order", 101 | timestamp: tx.timestamp, 102 | txId: tx.id, 103 | boxId: output.boxId, 104 | status: confirmed ? "submitted" : "pending", 105 | order: summary 106 | } 107 | } 108 | 109 | const inputOrder = tx.inputs 110 | .map(o => { 111 | const op = this.ordersParser.parse(o) 112 | return op ? ([op, o] as [AmmOrderInfo, AugErgoBox]) : undefined 113 | }) 114 | .find(x => !!x) 115 | 116 | const poolOutput = () => tx.outputs.map(o => this.poolsParser.parse(o)).find(x => !!x) 117 | 118 | if (inputOrder && !confirmed) { 119 | const [summary, input] = inputOrder 120 | return { 121 | type: "order", 122 | timestamp: (tx as any).creationTimestamp, 123 | txId: tx.id, 124 | boxId: input.boxId, 125 | orderInput: input, 126 | status: "inProgress", 127 | order: summary 128 | } 129 | } 130 | if (inputOrder && confirmed) { 131 | const [summary, input] = inputOrder 132 | const pool = poolOutput() 133 | if (pool) { 134 | return { 135 | type: "order", 136 | timestamp: tx.timestamp, 137 | txId: tx.id, 138 | boxId: input.boxId, 139 | status: "executed", 140 | order: summary 141 | } 142 | } else { 143 | return { 144 | type: "refund", 145 | timestamp: tx.timestamp, 146 | txId: tx.id, 147 | status: "executed", 148 | operation: summary.type 149 | } 150 | } 151 | } 152 | 153 | const pool = poolOutput() 154 | if (pool) { 155 | const rewardLP = tx.outputs 156 | .filter(o => addresses.includes(o.address)) 157 | .flatMap(o => o.assets) 158 | .find(a => a.tokenId === pool.lp.asset.id) 159 | if (rewardLP) 160 | return { 161 | type: "setup", 162 | timestamp: tx.timestamp, 163 | txId: tx.id, 164 | status: "executed", 165 | pool: pool, 166 | reward: AssetAmount.fromToken(rewardLP) 167 | } 168 | } 169 | 170 | return undefined 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/amm/common/services/pools.ts: -------------------------------------------------------------------------------- 1 | import {ErgoNetwork} from "@ergolabs/ergo-sdk" 2 | import {makePools, Pools} from "../../../services/pools" 3 | import * as N2Dexy from "../contracts/n2dexyGOLDPoolContracts" 4 | import * as N2T from "../contracts/n2tPoolContracts" 5 | import * as T2T from "../contracts/t2tPoolContracts" 6 | import {AmmPool} from "../entities/ammPool" 7 | import {N2DexyAmmPoolsParser, N2TAmmPoolsParser, T2TAmmPoolsParser} from "../parsers/ammPoolsParser" 8 | 9 | export function makeNativePools(network: ErgoNetwork): Pools { 10 | return makePools(network, new N2TAmmPoolsParser(), N2T.poolBundle()) 11 | } 12 | 13 | export function makeTokenPools(network: ErgoNetwork): Pools { 14 | return makePools(network, new T2TAmmPoolsParser(), T2T.poolBundle()) 15 | } 16 | 17 | export function makeDexyPools(network: ErgoNetwork): Pools { 18 | return makePools(network, new N2DexyAmmPoolsParser(), N2Dexy.poolBundle()) 19 | } 20 | -------------------------------------------------------------------------------- /src/amm/common/types.ts: -------------------------------------------------------------------------------- 1 | import {TokenId} from "@ergolabs/ergo-sdk" 2 | 3 | export type PoolId = TokenId 4 | -------------------------------------------------------------------------------- /src/amm/common/validation/ammPoolValidation.ts: -------------------------------------------------------------------------------- 1 | import {ErgoNetwork, isNative} from "@ergolabs/ergo-sdk" 2 | import {sqrt} from "../../../utils/sqrt" 3 | import {OK, PoolValidation, ValidationResult} from "../../../validation/poolValidation" 4 | import {BurnLP, EmissionLP} from "../constants" 5 | import {AmmPool} from "../entities/ammPool" 6 | 7 | class T2TAmmPoolValidation implements PoolValidation { 8 | constructor(public readonly network: ErgoNetwork) {} 9 | 10 | async validate(pool: AmmPool): Promise { 11 | const nft = await this.network.getFullTokenInfo(pool.id) 12 | const lp = await this.network.getFullTokenInfo(pool.lp.asset.id) 13 | const poolBoxes = await this.network.getByTokenId(pool.id, {offset: 0, limit: 1}) 14 | const genesisBox = poolBoxes[0] 15 | const errorsAcc = [] 16 | if (nft && lp && genesisBox) { 17 | if (nft.emissionAmount != 1n) 18 | errorsAcc.push(`Wrong pool NFT emission amount. Required: 1, actual: ${nft.emissionAmount}`) 19 | 20 | const requiredEmission = EmissionLP - BurnLP 21 | if (lp.emissionAmount != requiredEmission) 22 | errorsAcc.push( 23 | `Wrong pool LP emission amount. Required: ${requiredEmission}, actual: ${lp.emissionAmount}` 24 | ) 25 | 26 | const poolTokensNum = 4 27 | if (genesisBox.assets.length === poolTokensNum) { 28 | const [nft0, lp0, x0, y0] = genesisBox.assets 29 | if (nft0.amount !== 1n || nft0.tokenId !== pool.id) errorsAcc.push(`Wrong genesis NFT.`) 30 | const allowedLP = sqrt(x0.amount * y0.amount) 31 | const takenLP = requiredEmission - lp0.amount 32 | if (allowedLP < takenLP) 33 | errorsAcc.push(`Illegal pool initialization. Allowed LP: ${allowedLP}, taken: ${takenLP}`) 34 | } else { 35 | errorsAcc.push( 36 | `Wrong number of pool tokens. Required: ${poolTokensNum}, actual: ${genesisBox.assets.length}` 37 | ) 38 | } 39 | } 40 | return errorsAcc.length > 0 ? errorsAcc : OK 41 | } 42 | } 43 | 44 | class N2TAmmPoolValidation implements PoolValidation { 45 | constructor(public readonly network: ErgoNetwork) {} 46 | 47 | async validate(pool: AmmPool): Promise { 48 | const nft = await this.network.getFullTokenInfo(pool.id) 49 | const lp = await this.network.getFullTokenInfo(pool.lp.asset.id) 50 | const poolBoxes = await this.network.getByTokenId(pool.id, {offset: 0, limit: 1}) 51 | const genesisBox = poolBoxes[0] 52 | const errorsAcc = [] 53 | if (nft && lp && genesisBox) { 54 | if (nft.emissionAmount != 1n) 55 | errorsAcc.push(`Wrong pool NFT emission amount. Required: 1, actual: ${nft.emissionAmount}`) 56 | 57 | const requiredEmission = EmissionLP - BurnLP 58 | if (lp.emissionAmount != requiredEmission) 59 | errorsAcc.push( 60 | `Wrong pool LP emission amount. Required: ${requiredEmission}, actual: ${lp.emissionAmount}` 61 | ) 62 | 63 | const poolTokensNum = 3 64 | if (genesisBox.assets.length === poolTokensNum) { 65 | const [nft0, lp0, y0] = genesisBox.assets 66 | if (nft0.amount !== 1n || nft0.tokenId !== pool.id) errorsAcc.push(`Wrong genesis NFT.`) 67 | const allowedLP = sqrt(genesisBox.value * y0.amount) 68 | const takenLP = requiredEmission - lp0.amount 69 | if (allowedLP < takenLP) 70 | errorsAcc.push(`Illegal pool initialization. Allowed LP: ${allowedLP}, taken: ${takenLP}`) 71 | } else { 72 | errorsAcc.push( 73 | `Wrong number of pool tokens. Required: ${poolTokensNum}, actual: ${genesisBox.assets.length}` 74 | ) 75 | } 76 | } 77 | return errorsAcc.length > 0 ? errorsAcc : OK 78 | } 79 | } 80 | 81 | export class DefaultAmmPoolValidation implements PoolValidation { 82 | private n2tValidation: N2TAmmPoolValidation 83 | private t2tValidation: T2TAmmPoolValidation 84 | 85 | constructor(public readonly network: ErgoNetwork) { 86 | this.n2tValidation = new N2TAmmPoolValidation(network) 87 | this.t2tValidation = new T2TAmmPoolValidation(network) 88 | } 89 | 90 | validate(pool: AmmPool): Promise { 91 | return isNative(pool.x.asset) ? this.n2tValidation.validate(pool) : this.t2tValidation.validate(pool) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/amm/nativeFee/contracts/n2dexyPoolContracts.ts: -------------------------------------------------------------------------------- 1 | import {AssetInfo, ErgoTree, PublicKey, RustModule, TokenId} from "@ergolabs/ergo-sdk" 2 | import {NativeExFee, NativeExFeePerToken} from "../../../types" 3 | import {fromHex} from "../../../utils/hex" 4 | import {decimalToFractional} from "../../../utils/math" 5 | import {DexyGOLDAssetId, SigmaPropConstPrefixHex} from "../../common/constants" 6 | import {PoolId} from "../../common/types" 7 | import * as N2Dexy from "./n2dexyTemplates" 8 | 9 | export function deposit( 10 | poolId: PoolId, 11 | pk: PublicKey, 12 | selfX: bigint, 13 | exFee: NativeExFee, 14 | maxMinerFee: bigint 15 | ): ErgoTree { 16 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2Dexy.DepositSample) 17 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 18 | .with_constant(12, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 19 | .with_constant( 20 | 2, 21 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(selfX.toString())) 22 | ) 23 | .with_constant( 24 | 16, 25 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(selfX.toString())) 26 | ) 27 | .with_constant( 28 | 15, 29 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(exFee.toString())) 30 | ) 31 | .with_constant( 32 | 17, 33 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(exFee.toString())) 34 | ) 35 | .with_constant( 36 | 22, 37 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 38 | ) 39 | .to_base16_bytes() 40 | } 41 | 42 | // TODO: complete dex actions for dexy 43 | export function redeem(poolId: PoolId, pk: PublicKey, exFee: NativeExFee, maxMinerFee: bigint): ErgoTree { 44 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2Dexy.RedeemSample) 45 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 46 | .with_constant(11, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 47 | .with_constant( 48 | 12, 49 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(exFee.toString())) 50 | ) 51 | .with_constant( 52 | 16, 53 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 54 | ) 55 | .to_base16_bytes() 56 | } 57 | 58 | export function swapSell( 59 | poolId: PoolId, 60 | baseAmount: bigint, 61 | poolFeeNum: number, 62 | quoteId: TokenId, 63 | minQuoteAmount: bigint, 64 | exFeePerToken: NativeExFeePerToken, 65 | maxMinerFee: bigint, 66 | pk: PublicKey 67 | ): ErgoTree { 68 | const [dexFeePerTokenNum, dexFeePerTokenDenom] = decimalToFractional(exFeePerToken) 69 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2Dexy.SwapSellSample) 70 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 71 | .with_constant(9, RustModule.SigmaRust.Constant.from_byte_array(fromHex(quoteId))) 72 | .with_constant(14, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 73 | .with_constant(18, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 74 | .with_constant(8, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 75 | .with_constant( 76 | 10, 77 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(minQuoteAmount.toString())) 78 | ) 79 | .with_constant( 80 | 11, 81 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(dexFeePerTokenNum.toString())) 82 | ) 83 | .with_constant( 84 | 12, 85 | RustModule.SigmaRust.Constant.from_i64( 86 | RustModule.SigmaRust.I64.from_str(dexFeePerTokenDenom.toString()) 87 | ) 88 | ) 89 | .with_constant( 90 | 2, 91 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(baseAmount.toString())) 92 | ) 93 | .with_constant( 94 | 17, 95 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(baseAmount.toString())) 96 | ) 97 | .with_constant( 98 | 22, 99 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 100 | ) 101 | .to_base16_bytes() 102 | } 103 | 104 | export function swapBuy( 105 | poolId: PoolId, 106 | poolFeeNum: number, 107 | minQuoteAmount: bigint, 108 | exFeePerToken: NativeExFeePerToken, 109 | maxMinerFee: bigint, 110 | pk: PublicKey 111 | ): ErgoTree { 112 | const [dexFeePerTokenNum, dexFeePerTokenDenom] = decimalToFractional(exFeePerToken) 113 | const dexFeePerTokenNumDiff = dexFeePerTokenDenom - dexFeePerTokenNum 114 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2Dexy.SwapBuySample) 115 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 116 | .with_constant(11, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 117 | .with_constant(15, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 118 | .with_constant(9, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 119 | .with_constant( 120 | 10, 121 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(minQuoteAmount.toString())) 122 | ) 123 | .with_constant( 124 | 5, 125 | RustModule.SigmaRust.Constant.from_i64( 126 | RustModule.SigmaRust.I64.from_str(dexFeePerTokenDenom.toString()) 127 | ) 128 | ) 129 | .with_constant( 130 | 6, 131 | RustModule.SigmaRust.Constant.from_i64( 132 | RustModule.SigmaRust.I64.from_str(dexFeePerTokenNumDiff.toString()) 133 | ) 134 | ) 135 | .with_constant( 136 | 19, 137 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 138 | ) 139 | .to_base16_bytes() 140 | } 141 | 142 | export function isDexy(a: AssetInfo): boolean { 143 | return a.id === DexyGOLDAssetId 144 | } 145 | -------------------------------------------------------------------------------- /src/amm/nativeFee/contracts/n2dexyTemplates.ts: -------------------------------------------------------------------------------- 1 | // TODO: should update contracts for mainnet 2 | export const SwapSellSample = 3 | "101808cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d040005e012040604060404040004000e2002020202020202020202020202020202020202020202020202020202020202020e20040404040404040404040404040404040404040404040404040404040404040405c00c051405c09a0c040404ca0f06010104d00f05e01204ca0f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d804d604db63087202d605b2a5730500d606b2db63087205730600d6077e8c72060206edededededed938cb2720473070001730893c27205d07201938c72060173099272077e730a06927ec172050699997ec1a7069d9c72077e730b067e730c067e720306909c9c7e8cb27204730d0002067e7203067e730e069c9a7207730f9a9c7ec17202067e7310067e9c73117e7312050690b0ada5d90108639593c272087313c1720873147315d90108599a8c7208018c72080273167317" 4 | 5 | export const SwapSellTemplate = 6 | "d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d804d604db63087202d605b2a5730500d606b2db63087205730600d6077e8c72060206edededededed938cb2720473070001730893c27205d07201938c72060173099272077e730a06927ec172050699997ec1a7069d9c72077e730b067e730c067e720306909c9c7e8cb27204730d0002067e7203067e730e069c9a7207730f9a9c7ec17202067e7310067e9c73117e7312050690b0ada5d90108639593c272087313c1720873147315d90108599a8c7208018c72080273167317" 7 | 8 | export const SwapBuySample = 9 | "101508cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d040004060406040405140512040004000e20020202020202020202020202020202020202020202020202020202020202020205c00c04ca0f060101040404d00f04ca0f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d804d603db63087202d604b2a5730400d6059d9c7e99c17204c1a7067e7305067e730606d6068cb2db6308a773070002edededed938cb2720373080001730993c27204d072019272057e730a06909c9c7ec17202067e7206067e730b069c9a7205730c9a9c7e8cb27203730d0002067e730e067e9c72067e730f050690b0ada5d90107639593c272077310c1720773117312d90107599a8c7207018c72070273137314" 10 | 11 | export const SwapBuyTemplate = 12 | "d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d804d603db63087202d604b2a5730400d6059d9c7e99c17204c1a7067e7305067e730606d6068cb2db6308a773070002edededed938cb2720373080001730993c27204d072019272057e730a06909c9c7ec17202067e7206067e730b069c9a7205730c9a9c7e8cb27203730d0002067e730e067e9c72067e730f050690b0ada5d90107639593c272077310c1720773117312d90107599a8c7207018c72070273137314" 13 | 14 | export const DepositSample = 15 | "101808cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d040005e0d40304060406040404020580a0b787e90504040400040004000e200202020202020202020202020202020202020202020202020202020202020202040404020580f10405e0d4030580f10401000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d80bd604db63087202d605b2a5730500d606b27204730600d6077e9973078c72060206d6087ec1720206d6099d9c7e72030672077208d60ab27204730800d60b7e8c720a0206d60c9d9c7e8cb2db6308a773090002067207720bd60ddb63087205d60eb2720d730a00ededededed938cb27204730b0001730c93c27205d0720195ed8f7209720c93b1720d730dd801d60fb2720d730e00eded92c172059999c1a7730f7310938c720f018c720a01927e8c720f02069d9c99720c7209720b720795927209720c927ec1720506997e99c1a7731106997e7203069d9c997209720c720872077312938c720e018c720601927e8c720e0206a17209720c90b0ada5d9010f639593c2720f7313c1720f73147315d9010f599a8c720f018c720f0273167317" 16 | 17 | export const DepositTemplate = 18 | "d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d80bd604db63087202d605b2a5730500d606b27204730600d6077e9973078c72060206d6087ec1720206d6099d9c7e72030672077208d60ab27204730800d60b7e8c720a0206d60c9d9c7e8cb2db6308a773090002067207720bd60ddb63087205d60eb2720d730a00ededededed938cb27204730b0001730c93c27205d0720195ed8f7209720c93b1720d730dd801d60fb2720d730e00eded92c172059999c1a7730f7310938c720f018c720a01927e8c720f02069d9c99720c7209720b720795927209720c927ec1720506997e99c1a7731106997e7203069d9c997209720c720872077312938c720e018c720601927e8c720e0206a17209720c90b0ada5d9010f639593c2720f7313c1720f73147315d9010f599a8c720f018c720f0273167317" 19 | 20 | // TODO: should update according to dexy contracts 21 | export const RedeemSample = 22 | "198d031208d2040004040406040204000404040005feffffffffffffffff01040204000e20020202020202020202020202020202020202020202020202020202020202020205ca0f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206ededededed938cb27203730a0001730b93c27204d07201938c7205018c720601927e9a99c17204c1a7730c069d9c72077ec17202067208927e8c720502069d9c72077e8c72060206720890b0ada5d90109639593c27209730dc17209730e730fd90109599a8c7209018c72090273107311" 23 | 24 | export const RedeemTemplate = 25 | "d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206ededededed938cb27203730a0001730b93c27204d07201938c7205018c720601927e9a99c17204c1a7730c069d9c72077ec17202067208927e8c720502069d9c72077e8c72060206720890b0ada5d90109639593c27209730dc17209730e730fd90109599a8c7209018c72090273107311" 26 | -------------------------------------------------------------------------------- /src/amm/nativeFee/contracts/n2tPoolContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, PublicKey, RustModule, TokenId} from "@ergolabs/ergo-sdk" 2 | import {NativeExFee, NativeExFeePerToken} from "../../../types" 3 | import {fromHex} from "../../../utils/hex" 4 | import {decimalToFractional} from "../../../utils/math" 5 | import {SigmaPropConstPrefixHex} from "../../common/constants" 6 | import {PoolId} from "../../common/types" 7 | import * as N2T from "./n2tTemplates" 8 | 9 | export function deposit( 10 | poolId: PoolId, 11 | pk: PublicKey, 12 | selfX: bigint, 13 | exFee: NativeExFee, 14 | maxMinerFee: bigint 15 | ): ErgoTree { 16 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2T.DepositSample) 17 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 18 | .with_constant(12, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 19 | .with_constant( 20 | 2, 21 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(selfX.toString())) 22 | ) 23 | .with_constant( 24 | 16, 25 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(selfX.toString())) 26 | ) 27 | .with_constant( 28 | 15, 29 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(exFee.toString())) 30 | ) 31 | .with_constant( 32 | 17, 33 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(exFee.toString())) 34 | ) 35 | .with_constant( 36 | 22, 37 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 38 | ) 39 | .to_base16_bytes() 40 | } 41 | 42 | export function redeem(poolId: PoolId, pk: PublicKey, exFee: NativeExFee, maxMinerFee: bigint): ErgoTree { 43 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2T.RedeemSample) 44 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 45 | .with_constant(11, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 46 | .with_constant( 47 | 12, 48 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(exFee.toString())) 49 | ) 50 | .with_constant( 51 | 16, 52 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 53 | ) 54 | .to_base16_bytes() 55 | } 56 | 57 | export function swapSell( 58 | poolId: PoolId, 59 | baseAmount: bigint, 60 | poolFeeNum: number, 61 | quoteId: TokenId, 62 | minQuoteAmount: bigint, 63 | exFeePerToken: NativeExFeePerToken, 64 | maxMinerFee: bigint, 65 | pk: PublicKey 66 | ): ErgoTree { 67 | const [dexFeePerTokenNum, dexFeePerTokenDenom] = decimalToFractional(exFeePerToken) 68 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2T.SwapSellSample) 69 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 70 | .with_constant(9, RustModule.SigmaRust.Constant.from_byte_array(fromHex(quoteId))) 71 | .with_constant(14, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 72 | .with_constant(18, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 73 | .with_constant(8, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 74 | .with_constant( 75 | 10, 76 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(minQuoteAmount.toString())) 77 | ) 78 | .with_constant( 79 | 11, 80 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(dexFeePerTokenNum.toString())) 81 | ) 82 | .with_constant( 83 | 12, 84 | RustModule.SigmaRust.Constant.from_i64( 85 | RustModule.SigmaRust.I64.from_str(dexFeePerTokenDenom.toString()) 86 | ) 87 | ) 88 | .with_constant( 89 | 2, 90 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(baseAmount.toString())) 91 | ) 92 | .with_constant( 93 | 17, 94 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(baseAmount.toString())) 95 | ) 96 | .with_constant( 97 | 22, 98 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 99 | ) 100 | .to_base16_bytes() 101 | } 102 | 103 | export function swapBuy( 104 | poolId: PoolId, 105 | poolFeeNum: number, 106 | minQuoteAmount: bigint, 107 | exFeePerToken: NativeExFeePerToken, 108 | maxMinerFee: bigint, 109 | pk: PublicKey 110 | ): ErgoTree { 111 | const [dexFeePerTokenNum, dexFeePerTokenDenom] = decimalToFractional(exFeePerToken) 112 | const dexFeePerTokenNumDiff = dexFeePerTokenDenom - dexFeePerTokenNum 113 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(N2T.SwapBuySample) 114 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 115 | .with_constant(11, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 116 | .with_constant(15, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 117 | .with_constant(9, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 118 | .with_constant( 119 | 10, 120 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(minQuoteAmount.toString())) 121 | ) 122 | .with_constant( 123 | 5, 124 | RustModule.SigmaRust.Constant.from_i64( 125 | RustModule.SigmaRust.I64.from_str(dexFeePerTokenDenom.toString()) 126 | ) 127 | ) 128 | .with_constant( 129 | 6, 130 | RustModule.SigmaRust.Constant.from_i64( 131 | RustModule.SigmaRust.I64.from_str(dexFeePerTokenNumDiff.toString()) 132 | ) 133 | ) 134 | .with_constant( 135 | 19, 136 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 137 | ) 138 | .to_base16_bytes() 139 | } 140 | -------------------------------------------------------------------------------- /src/amm/nativeFee/contracts/n2tTemplates.ts: -------------------------------------------------------------------------------- 1 | export const SwapSellSample = 2 | "19c4031808d2040005e012040404060402040004000e2002020202020202020202020202020202020202020202020202020202020202020e20010101010101010101010101010101010101010101010101010101010101010105c00c05040514040404c80f06010104d00f05e01204c80f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d804d604db63087202d605b2a5730500d606b2db63087205730600d6077e8c72060206edededededed938cb2720473070001730893c27205d07201938c72060173099272077e730a06927ec172050699997ec1a7069d9c72077e730b067e730c067e720306909c9c7e8cb27204730d0002067e7203067e730e069c9a7207730f9a9c7ec17202067e7310067e9c73117e7312050690b0ada5d90108639593c272087313c1720873147315d90108599a8c7208018c72080273167317" 3 | 4 | export const SwapBuySample = 5 | "1980031508d2040004040406040205140512040004000e20020202020202020202020202020202020202020202020202020202020202020205c00c04c80f060101040404d00f04c80f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d804d603db63087202d604b2a5730400d6059d9c7e99c17204c1a7067e7305067e730606d6068cb2db6308a773070002edededed938cb2720373080001730993c27204d072019272057e730a06909c9c7ec17202067e7206067e730b069c9a7205730c9a9c7e8cb27203730d0002067e730e067e9c72067e730f050690b0ada5d90107639593c272077310c1720773117312d90107599a8c7207018c72070273137314" 6 | 7 | export const DepositSample = 8 | "1993041808d2040005be9a0c040404060402040205feffffffffffffffff0104040400040004000e2002020202020202020202020202020202020202020202020202020202020202020404040205ca0f05be9a0c05ca0f01000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d80bd604db63087202d605b2a5730500d606b27204730600d6077e9973078c72060206d6087ec1720206d6099d9c7e72030672077208d60ab27204730800d60b7e8c720a0206d60c9d9c7e8cb2db6308a773090002067207720bd60ddb63087205d60eb2720d730a00ededededed938cb27204730b0001730c93c27205d0720195ed8f7209720c93b1720d730dd801d60fb2720d730e00eded92c172059999c1a7730f7310938c720f018c720a01927e8c720f02069d9c99720c7209720b720795927209720c927ec1720506997e99c1a7731106997e7203069d9c997209720c720872077312938c720e018c720601927e8c720e0206a17209720c90b0ada5d9010f639593c2720f7313c1720f73147315d9010f599a8c720f018c720f0273167317" 9 | 10 | export const RedeemSample = 11 | "198d031208d2040004040406040204000404040005feffffffffffffffff01040204000e20020202020202020202020202020202020202020202020202020202020202020205ca0f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206ededededed938cb27203730a0001730b93c27204d07201938c7205018c720601927e9a99c17204c1a7730c069d9c72077ec17202067208927e8c720502069d9c72077e8c72060206720890b0ada5d90109639593c27209730dc17209730e730fd90109599a8c7209018c72090273107311" 12 | 13 | export const SwapSellTemplate = 14 | "d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d804d604db63087202d605b2a5730500d606b2db63087205730600d6077e8c72060206edededededed938cb2720473070001730893c27205d07201938c72060173099272077e730a06927ec172050699997ec1a7069d9c72077e730b067e730c067e720306909c9c7e8cb27204730d0002067e7203067e730e069c9a7207730f9a9c7ec17202067e7310067e9c73117e7312050690b0ada5d90108639593c272087313c1720873147315d90108599a8c7208018c72080273167317" 15 | 16 | export const SwapBuyTemplate = 17 | "d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d804d603db63087202d604b2a5730400d6059d9c7e99c17204c1a7067e7305067e730606d6068cb2db6308a773070002edededed938cb2720373080001730993c27204d072019272057e730a06909c9c7ec17202067e7206067e730b069c9a7205730c9a9c7e8cb27203730d0002067e730e067e9c72067e730f050690b0ada5d90107639593c272077310c1720773117312d90107599a8c7207018c72070273137314" 18 | 19 | export const DepositTemplate = 20 | "d803d6017300d602b2a4730100d6037302eb027201d195ed92b1a4730393b1db630872027304d80bd604db63087202d605b2a5730500d606b27204730600d6077e9973078c72060206d6087ec1720206d6099d9c7e72030672077208d60ab27204730800d60b7e8c720a0206d60c9d9c7e8cb2db6308a773090002067207720bd60ddb63087205d60eb2720d730a00ededededed938cb27204730b0001730c93c27205d0720195ed8f7209720c93b1720d730dd801d60fb2720d730e00eded92c172059999c1a7730f7310938c720f018c720a01927e8c720f02069d9c99720c7209720b720795927209720c927ec1720506997e99c1a7731106997e7203069d9c997209720c720872077312938c720e018c720601927e8c720e0206a17209720c90b0ada5d9010f639593c2720f7313c1720f73147315d9010f599a8c720f018c720f0273167317" 21 | 22 | export const RedeemTemplate = 23 | "d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206ededededed938cb27203730a0001730b93c27204d07201938c7205018c720601927e9a99c17204c1a7730c069d9c72077ec17202067208927e8c720502069d9c72077e8c72060206720890b0ada5d90109639593c27209730dc17209730e730fd90109599a8c7209018c72090273107311" 24 | 25 | // OLD_TEMPLATES 26 | export const OldSwapSellTemplate = 27 | "d803d6017300d602b2a4730100d6037302eb027201d195ed93b1a4730393b1db630872027304d804d604db63087202d605b2a5730500d606b2db63087205730600d6077e8c72060206edededededed938cb2720473070001730893c27205d07201938c72060173099272077e730a06927ec172050699997ec1a7069d9c72077e730b067e730c067e720306909c9c7e8cb27204730d0002067e7203067e730e069c9a7207730f9a9c7ec17202067e7310067e9c73117e7312050690b0ada5d90108639593c272087313c1720873147315d90108599a8c7208018c72080273167317" 28 | 29 | export const OldSwapBuyTemplate = 30 | "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d804d603db63087202d604b2a5730400d6059d9c7e99c17204c1a7067e7305067e730606d6068cb2db6308a773070002edededed938cb2720373080001730993c27204d072019272057e730a06909c9c7ec17202067e7206067e730b069c9a7205730c9a9c7e8cb27203730d0002067e730e067e9c72067e730f050690b0ada5d90107639593c272077310c1720773117312d90107599a8c7207018c72070273137314" 31 | 32 | export const OldDepositTemplate = 33 | "d803d6017300d602b2a4730100d6037302eb027201d195ed93b1a4730393b1db630872027304d80bd604db63087202d605b2a5730500d606b27204730600d6077e9973078c72060206d6087ec1720206d6099d9c7e72030672077208d60ab27204730800d60b7e8c720a0206d60c9d9c7e8cb2db6308a773090002067207720bd60ddb63087205d60eb2720d730a00ededededed938cb27204730b0001730c93c27205d0720195ed8f7209720c93b1720d730dd801d60fb2720d730e00eded92c172059999c1a7730f7310938c720f018c720a01927e8c720f02069d9c99720c7209720b720795927209720c927ec1720506997e99c1a7731106997e7203069d9c997209720c720872077312938c720e018c720601927e8c720e0206a17209720c90b0ada5d9010f639593c2720f7313c1720f73147315d9010f599a8c720f018c720f0273167317" 34 | 35 | export const OldRedeemTemplate = 36 | "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d806d603db63087202d604b2a5730400d605b2db63087204730500d606b27203730600d6077e8cb2db6308a77307000206d6087e9973088cb272037309000206ededededed938cb27203730a0001730b93c27204d07201938c7205018c720601927e9a99c17204c1a7730c069d9c72077ec17202067208927e8c720502069d9c72077e8c72060206720890b0ada5d90109639593c27209730dc17209730e730fd90109599a8c7209018c72090273107311" 37 | -------------------------------------------------------------------------------- /src/amm/nativeFee/contracts/t2tPoolContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, PublicKey, RustModule, TokenId} from "@ergolabs/ergo-sdk" 2 | import {fromHex} from "../../../utils/hex" 3 | import {decimalToFractional} from "../../../utils/math" 4 | import {SigmaPropConstPrefixHex} from "../../common/constants" 5 | import {PoolId} from "../../common/types" 6 | import * as T2T from "./t2tTemplates" 7 | 8 | export function deposit(poolId: PoolId, pk: PublicKey, dexFee: bigint, maxMinerFee: bigint): ErgoTree { 9 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(T2T.DepositSample) 10 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 11 | .with_constant(13, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 12 | .with_constant( 13 | 15, 14 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(dexFee.toString())) 15 | ) 16 | .with_constant( 17 | 25, 18 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 19 | ) 20 | .to_base16_bytes() 21 | } 22 | 23 | export function redeem(poolId: PoolId, pk: PublicKey, dexFee: bigint, maxMinerFee: bigint): ErgoTree { 24 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(T2T.RedeemSample) 25 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 26 | .with_constant(13, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 27 | .with_constant( 28 | 15, 29 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(dexFee.toString())) 30 | ) 31 | .with_constant( 32 | 19, 33 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 34 | ) 35 | .to_base16_bytes() 36 | } 37 | 38 | export function swap( 39 | poolId: PoolId, 40 | poolFeeNum: number, 41 | quoteId: TokenId, 42 | minQuoteAmount: bigint, 43 | dexFeePerToken: number, 44 | maxMinerFee: bigint, 45 | pk: PublicKey 46 | ): ErgoTree { 47 | const [dexFeePerTokenNum, dexFeePerTokenDenom] = decimalToFractional(dexFeePerToken) 48 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(T2T.SwapSample) 49 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + pk)) 50 | .with_constant(2, RustModule.SigmaRust.Constant.from_byte_array(fromHex(quoteId))) 51 | .with_constant(3, RustModule.SigmaRust.Constant.from_i32(poolFeeNum)) 52 | .with_constant(14, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 53 | .with_constant( 54 | 15, 55 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(minQuoteAmount.toString())) 56 | ) 57 | .with_constant( 58 | 16, 59 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(dexFeePerTokenNum.toString())) 60 | ) 61 | .with_constant( 62 | 17, 63 | RustModule.SigmaRust.Constant.from_i64( 64 | RustModule.SigmaRust.I64.from_str(dexFeePerTokenDenom.toString()) 65 | ) 66 | ) 67 | .with_constant( 68 | 21, 69 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(maxMinerFee.toString())) 70 | ) 71 | .to_base16_bytes() 72 | } 73 | -------------------------------------------------------------------------------- /src/amm/nativeFee/contracts/t2tTemplates.ts: -------------------------------------------------------------------------------- 1 | export const SwapSample = 2 | "1988041708d204000e20010101010101010101010101010101010101010101010101010101010101010104c80f04d00f040404080402040004040400040606010104000e20020202020202020202020202020202020202020202020202020202020202020205c00c050205140e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed92b1a4730593b1db630872027306d80ad606db63087202d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7730a000206d60e7e8cb27206730b000206d60f9a720a730cedededededed938cb27206730d0001730e93c27207d07201938c7208017203927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e72040690b0ada5d90110639593c272107312c1721073137314d90110599a8c7210018c72100273157316" 3 | 4 | export const DepositSample = 5 | "19b9041b08d20400040404080402040205feffffffffffffffff010404040004060402040004000e200202020202020202020202020202020202020202020202020202020202020202050205ca0f0404040204040402010101000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d803d6017300d602b2a4730100d603db6308a7eb027201d195ed92b1a4730293b1db630872027303d80cd604db63087202d605b2a5730400d606b27204730500d6077e9973068c72060206d608b27204730700d6097e8c72080206d60a9d9c7e8cb27203730800020672077209d60bb27204730900d60c7e8c720b0206d60d9d9c7e8cb27203730a0002067207720cd60edb63087205d60fb2720e730b00edededededed93b27204730c008602730d730e93c27205d0720192c1720599c1a7730f95ed8f720a720d93b1720e7310d801d610b2720e731100ed938c7210018c720b01927e8c721002069d9c99720d720a720c720795ed91720a720d93b1720e7312d801d610b2720e731300ed938c7210018c720801927e8c721002069d9c99720a720d720972079593720a720d73147315938c720f018c720601927e8c720f0206a1720a720d90b0ada5d90110639593c272107316c1721073177318d90110599a8c7210018c7210027319731a" 6 | 7 | export const RedeemSample = 8 | "19b9031508d204000404040804020400040404020406040005feffffffffffffffff01040204000e200202020202020202020202020202020202020202020202020202020202020202050205ca0f0e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d809d603db63087202d604b2a5730400d605db63087204d606b27205730500d607b27203730600d608b27205730700d609b27203730800d60a7e8cb2db6308a77309000206d60b7e99730a8cb27203730b000206ededededededed93b27203730c008602730d730e93c27204d0720192c1720499c1a7730f938c7206018c720701938c7208018c720901927e8c720602069d9c720a7e8c72070206720b927e8c720802069d9c720a7e8c72090206720b90b0ada5d9010c639593c2720c7310c1720c73117312d9010c599a8c720c018c720c0273137314" 9 | 10 | export const SwapTemplate = 11 | "d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed92b1a4730593b1db630872027306d80ad606db63087202d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7730a000206d60e7e8cb27206730b000206d60f9a720a730cedededededed938cb27206730d0001730e93c27207d07201938c7208017203927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e72040690b0ada5d90110639593c272107312c1721073137314d90110599a8c7210018c72100273157316" 12 | 13 | export const DepositTemplate = 14 | "d803d6017300d602b2a4730100d603db6308a7eb027201d195ed92b1a4730293b1db630872027303d80cd604db63087202d605b2a5730400d606b27204730500d6077e9973068c72060206d608b27204730700d6097e8c72080206d60a9d9c7e8cb27203730800020672077209d60bb27204730900d60c7e8c720b0206d60d9d9c7e8cb27203730a0002067207720cd60edb63087205d60fb2720e730b00edededededed93b27204730c008602730d730e93c27205d0720192c1720599c1a7730f95ed8f720a720d93b1720e7310d801d610b2720e731100ed938c7210018c720b01927e8c721002069d9c99720d720a720c720795ed91720a720d93b1720e7312d801d610b2720e731300ed938c7210018c720801927e8c721002069d9c99720a720d720972079593720a720d73147315938c720f018c720601927e8c720f0206a1720a720d90b0ada5d90110639593c272107316c1721073177318d90110599a8c7210018c7210027319731a" 15 | 16 | export const RedeemTemplate = 17 | "d802d6017300d602b2a4730100eb027201d195ed92b1a4730293b1db630872027303d809d603db63087202d604b2a5730400d605db63087204d606b27205730500d607b27203730600d608b27205730700d609b27203730800d60a7e8cb2db6308a77309000206d60b7e99730a8cb27203730b000206ededededededed93b27203730c008602730d730e93c27204d0720192c1720499c1a7730f938c7206018c720701938c7208018c720901927e8c720602069d9c720a7e8c72070206720b927e8c720802069d9c720a7e8c72090206720b90b0ada5d9010c639593c2720c7310c1720c73117312d9010c599a8c720c018c720c0273137314" 18 | 19 | // OLD_TEMPLATES 20 | 21 | export const OldSwapTemplate = 22 | "d805d6017300d602b2a4730100d6037302d6047303d6057304eb027201d195ed93b1a4730593b1db630872027306d80ad606db63087202d607b2a5730700d608b2db63087207730800d6098c720802d60a7e720906d60bb27206730900d60c7e8c720b0206d60d7e8cb2db6308a7730a000206d60e7e8cb27206730b000206d60f9a720a730cedededededed938cb27206730d0001730e93c27207d07201938c7208017203927209730f927ec1720706997ec1a7069d9c720a7e7310067e73110695938c720b017203909c9c720c720d7e7204069c720f9a9c720e7e7205069c720d7e720406909c9c720e720d7e7204069c720f9a9c720c7e7205069c720d7e72040690b0ada5d90110639593c272107312c1721073137314d90110599a8c7210018c72100273157316" 23 | 24 | export const OldDepositTemplate = 25 | "d803d6017300d602b2a4730100d603db6308a7eb027201d195ed93b1a4730293b1db630872027303d80cd604db63087202d605b2a5730400d606b27204730500d6077e9973068c72060206d608b27204730700d6097e8c72080206d60a9d9c7e8cb27203730800020672077209d60bb27204730900d60c7e8c720b0206d60d9d9c7e8cb27203730a0002067207720cd60edb63087205d60fb2720e730b00edededededed93b27204730c008602730d730e93c27205d0720192c1720599c1a7730f95ed8f720a720d93b1720e7310d801d610b2720e731100ed938c7210018c720b01927e8c721002069d9c99720d720a720c720795ed91720a720d93b1720e7312d801d610b2720e731300ed938c7210018c720801927e8c721002069d9c99720a720d720972079593720a720d73147315938c720f018c720601927e8c720f0206a1720a720d90b0ada5d90110639593c272107316c1721073177318d90110599a8c7210018c7210027319731a" 26 | 27 | export const OldRedeemTemplate = 28 | "d802d6017300d602b2a4730100eb027201d195ed93b1a4730293b1db630872027303d809d603db63087202d604b2a5730400d605db63087204d606b27205730500d607b27203730600d608b27205730700d609b27203730800d60a7e8cb2db6308a77309000206d60b7e99730a8cb27203730b000206ededededededed93b27203730c008602730d730e93c27204d0720192c1720499c1a7730f938c7206018c720701938c7208018c720901927e8c720602069d9c720a7e8c72070206720b927e8c720802069d9c720a7e8c72090206720b90b0ada5d9010c639593c2720c7310c1720c73117312d9010c599a8c720c018c720c0273137314" 29 | -------------------------------------------------------------------------------- /src/amm/nativeFee/interpreters/n2tPoolActions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | EmptyRegisters, 4 | ErgoBoxCandidate, 5 | ergoTreeFromAddress, 6 | InsufficientInputs, 7 | isNative, 8 | TransactionContext, 9 | TxRequest 10 | } from "@ergolabs/ergo-sdk" 11 | import {prepend} from "ramda" 12 | import {NativeExFeeType} from "../../../types" 13 | import {PoolSetupAction} from "../../common/interpreters/poolSetupAction" 14 | import {DepositParams} from "../../common/models/depositParams" 15 | import {PoolSetupParams} from "../../common/models/poolSetupParams" 16 | import {RedeemParams} from "../../common/models/redeemParams" 17 | import {SwapParams} from "../../common/models/swapParams" 18 | import {minValueForOrder} from "../../common/interpreters/mins" 19 | import {PoolActions} from "../../common/interpreters/poolActions" 20 | import * as N2T from "../contracts/n2tPoolContracts" 21 | 22 | export class N2tPoolActions implements PoolActions { 23 | constructor(public readonly uiRewardAddress: Address, private readonly setupImpl: PoolSetupAction) {} 24 | 25 | async setup(params: PoolSetupParams, ctx: TransactionContext): Promise { 26 | return this.setupImpl.setup(params, ctx, this.mkUiReward(ctx.network.height, params.uiFee)) 27 | } 28 | 29 | deposit(params: DepositParams, ctx: TransactionContext): Promise { 30 | const [x, y] = [params.x, params.y] 31 | const proxyScript = N2T.deposit(params.poolId, params.pk, x.amount, params.exFee, ctx.feeNErgs) 32 | const outputGranted = ctx.inputs.totalOutputWithoutChange 33 | const inY = outputGranted.assets.filter(t => t.tokenId === y.asset.id)[0] 34 | 35 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, params.exFee) 36 | if (outputGranted.nErgs < minNErgs) 37 | return Promise.reject( 38 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 39 | ) 40 | if (!inY) return Promise.reject(new InsufficientInputs(`Token ${y.asset.name} not provided`)) 41 | 42 | const height = ctx.network.height 43 | const orderOut: ErgoBoxCandidate = { 44 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 45 | ergoTree: proxyScript, 46 | creationHeight: height, 47 | assets: [inY], 48 | additionalRegisters: EmptyRegisters 49 | } 50 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 51 | return Promise.resolve({ 52 | inputs: ctx.inputs, 53 | dataInputs: [], 54 | outputs: prepend(orderOut, uiRewardOut), 55 | changeAddress: ctx.changeAddress, 56 | feeNErgs: ctx.feeNErgs 57 | }) 58 | } 59 | 60 | redeem(params: RedeemParams, ctx: TransactionContext): Promise { 61 | const proxyScript = N2T.redeem(params.poolId, params.pk, params.exFee, ctx.feeNErgs) 62 | const outputGranted = ctx.inputs.totalOutputWithoutChange 63 | const tokensIn = outputGranted.assets.filter(t => t.tokenId === params.lp.asset.id) 64 | 65 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, params.exFee) 66 | if (outputGranted.nErgs < minNErgs) 67 | return Promise.reject( 68 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 69 | ) 70 | if (tokensIn.length != 1) 71 | return Promise.reject(new InsufficientInputs(`Token ${params.lp.asset.name ?? "LP"} not provided`)) 72 | 73 | const height = ctx.network.height 74 | const orderOut = { 75 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 76 | ergoTree: proxyScript, 77 | creationHeight: height, 78 | assets: tokensIn, 79 | additionalRegisters: EmptyRegisters 80 | } 81 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 82 | return Promise.resolve({ 83 | inputs: ctx.inputs, 84 | dataInputs: [], 85 | outputs: prepend(orderOut, uiRewardOut), 86 | changeAddress: ctx.changeAddress, 87 | feeNErgs: ctx.feeNErgs 88 | }) 89 | } 90 | 91 | async swap(params: SwapParams, ctx: TransactionContext): Promise { 92 | const out = await (isNative(params.baseInput.asset) 93 | ? N2tPoolActions.mkSwapSell(params, ctx) 94 | : N2tPoolActions.mkSwapBuy(params, ctx)) 95 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 96 | return { 97 | inputs: ctx.inputs, 98 | dataInputs: [], 99 | outputs: prepend(out, uiRewardOut), 100 | changeAddress: ctx.changeAddress, 101 | feeNErgs: ctx.feeNErgs 102 | } 103 | } 104 | 105 | private static async mkSwapSell( 106 | params: SwapParams, 107 | ctx: TransactionContext 108 | ): Promise { 109 | const proxyScript = N2T.swapSell( 110 | params.poolId, 111 | params.baseInput.amount, 112 | params.poolFeeNum, 113 | params.quoteAsset, 114 | params.minQuoteOutput, 115 | params.exFeePerToken, 116 | ctx.feeNErgs, 117 | params.pk 118 | ) 119 | const outputGranted = ctx.inputs.totalOutputWithoutChange 120 | 121 | const minExFee = BigInt((Number(params.minQuoteOutput) * params.exFeePerToken).toFixed(0)) 122 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, minExFee) 123 | if (outputGranted.nErgs < minNErgs) 124 | return Promise.reject( 125 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 126 | ) 127 | 128 | return { 129 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 130 | ergoTree: proxyScript, 131 | creationHeight: ctx.network.height, 132 | assets: [], 133 | additionalRegisters: EmptyRegisters 134 | } 135 | } 136 | 137 | private static async mkSwapBuy( 138 | params: SwapParams, 139 | ctx: TransactionContext 140 | ): Promise { 141 | const proxyScript = N2T.swapBuy( 142 | params.poolId, 143 | params.poolFeeNum, 144 | params.minQuoteOutput, 145 | params.exFeePerToken, 146 | ctx.feeNErgs, 147 | params.pk 148 | ) 149 | const outputGranted = ctx.inputs.totalOutputWithoutChange 150 | const baseAssetId = params.baseInput.asset.id 151 | const baseIn = outputGranted.assets.filter(t => t.tokenId === baseAssetId)[0] 152 | 153 | const minExFee = BigInt((Number(params.minQuoteOutput) * params.exFeePerToken).toFixed(0)) 154 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, minExFee) 155 | if (outputGranted.nErgs < minNErgs) 156 | return Promise.reject( 157 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 158 | ) 159 | if (!baseIn) 160 | return Promise.reject(new InsufficientInputs(`Base asset ${params.baseInput.asset.name} not provided`)) 161 | 162 | return { 163 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 164 | ergoTree: proxyScript, 165 | creationHeight: ctx.network.height, 166 | assets: [baseIn], 167 | additionalRegisters: EmptyRegisters 168 | } 169 | } 170 | 171 | private mkUiReward(height: number, uiFee: bigint): ErgoBoxCandidate[] { 172 | return uiFee > 0 173 | ? [ 174 | { 175 | value: uiFee, 176 | ergoTree: ergoTreeFromAddress(this.uiRewardAddress), 177 | creationHeight: height, 178 | assets: [], 179 | additionalRegisters: EmptyRegisters 180 | } 181 | ] 182 | : [] 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/amm/nativeFee/interpreters/poolActions.ts: -------------------------------------------------------------------------------- 1 | import {Address, ErgoTx, isNative, Prover, TxAssembler, TxRequest} from "@ergolabs/ergo-sdk" 2 | import {NativeExFeeType} from "../../../types" 3 | import {N2tPoolSetupAction} from "../../common/interpreters/n2tPoolSetupAction" 4 | import {N2dexyPoolSetupAction} from "../../common/interpreters/n2dexyPoolSetupAction" 5 | import {PoolActionsSelector, wrapPoolActions} from "../../common/interpreters/poolActions" 6 | import {T2tPoolSetupAction} from "../../common/interpreters/t2tPoolSetupAction" 7 | import {isDexy} from "../contracts/n2dexyPoolContracts" 8 | import {N2DexyPoolActions} from "./n2DexyPoolActions" 9 | import {N2tPoolActions} from "./n2tPoolActions" 10 | import {T2tPoolActions} from "./t2tPoolActions" 11 | 12 | export function makeNativePoolActionsSelector( 13 | uiRewardAddress: Address 14 | ): PoolActionsSelector { 15 | return pool => 16 | isDexy(pool.y.asset) 17 | ? new N2DexyPoolActions(uiRewardAddress, new N2dexyPoolSetupAction()) 18 | : isNative(pool.x.asset) || isNative(pool.y.asset) 19 | ? new N2tPoolActions(uiRewardAddress, new N2tPoolSetupAction()) 20 | : new T2tPoolActions(uiRewardAddress, new T2tPoolSetupAction()); 21 | } 22 | 23 | export function makeWrappedNativePoolActionsSelector( 24 | uiRewardAddress: Address, 25 | prover: Prover, 26 | txAsm: TxAssembler 27 | ): PoolActionsSelector { 28 | return pool => { 29 | const poolAction = isDexy(pool.y.asset) 30 | ? new N2DexyPoolActions(uiRewardAddress, new N2dexyPoolSetupAction()) 31 | : isNative(pool.x.asset) || isNative(pool.y.asset) 32 | ? new N2tPoolActions(uiRewardAddress, new N2tPoolSetupAction()) 33 | : new T2tPoolActions(uiRewardAddress, new T2tPoolSetupAction()); 34 | return wrapPoolActions( 35 | poolAction, 36 | prover, 37 | txAsm 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/amm/nativeFee/interpreters/t2tPoolActions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | EmptyRegisters, 4 | ErgoBoxCandidate, 5 | ergoTreeFromAddress, 6 | InsufficientInputs, 7 | TransactionContext, 8 | TxRequest 9 | } from "@ergolabs/ergo-sdk" 10 | import {prepend} from "ramda" 11 | import {NativeExFeeType} from "../../../types" 12 | import {PoolSetupAction} from "../../common/interpreters/poolSetupAction" 13 | import {DepositParams} from "../../common/models/depositParams" 14 | import {PoolSetupParams} from "../../common/models/poolSetupParams" 15 | import {RedeemParams} from "../../common/models/redeemParams" 16 | import {SwapParams} from "../../common/models/swapParams" 17 | import {minValueForOrder} from "../../common/interpreters/mins" 18 | import {PoolActions} from "../../common/interpreters/poolActions" 19 | import * as T2T from "../contracts/t2tPoolContracts" 20 | 21 | export class T2tPoolActions implements PoolActions { 22 | constructor(public readonly uiRewardAddress: Address, private readonly setupImpl: PoolSetupAction) {} 23 | 24 | async setup(params: PoolSetupParams, ctx: TransactionContext): Promise { 25 | return this.setupImpl.setup(params, ctx, this.mkUiReward(ctx.network.height, params.uiFee)) 26 | } 27 | 28 | deposit(params: DepositParams, ctx: TransactionContext): Promise { 29 | const [x, y] = [params.x, params.y] 30 | const proxyScript = T2T.deposit(params.poolId, params.pk, params.exFee, ctx.feeNErgs) 31 | const outputGranted = ctx.inputs.totalOutputWithoutChange 32 | const pairIn = [ 33 | outputGranted.assets.filter(t => t.tokenId === x.asset.id), 34 | outputGranted.assets.filter(t => t.tokenId === y.asset.id) 35 | ].flat() 36 | 37 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, params.exFee) 38 | if (outputGranted.nErgs < minNErgs) 39 | return Promise.reject( 40 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 41 | ) 42 | if (pairIn.length != 2) 43 | return Promise.reject( 44 | new InsufficientInputs(`Wrong number of input tokens provided ${pairIn.length}, required 2`) 45 | ) 46 | 47 | const orderOut: ErgoBoxCandidate = { 48 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 49 | ergoTree: proxyScript, 50 | creationHeight: ctx.network.height, 51 | assets: pairIn, 52 | additionalRegisters: EmptyRegisters 53 | } 54 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 55 | return Promise.resolve({ 56 | inputs: ctx.inputs, 57 | dataInputs: [], 58 | outputs: prepend(orderOut, uiRewardOut), 59 | changeAddress: ctx.changeAddress, 60 | feeNErgs: ctx.feeNErgs 61 | }) 62 | } 63 | 64 | redeem(params: RedeemParams, ctx: TransactionContext): Promise { 65 | const proxyScript = T2T.redeem(params.poolId, params.pk, params.exFee, ctx.feeNErgs) 66 | const outputGranted = ctx.inputs.totalOutputWithoutChange 67 | const tokensIn = outputGranted.assets.filter(t => t.tokenId === params.lp.asset.id) 68 | 69 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, params.exFee) 70 | if (outputGranted.nErgs < minNErgs) 71 | return Promise.reject( 72 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 73 | ) 74 | if (tokensIn.length != 1) 75 | return Promise.reject( 76 | new InsufficientInputs(`Wrong number of input tokens provided ${tokensIn.length}, required 1`) 77 | ) 78 | 79 | const orderOut = { 80 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 81 | ergoTree: proxyScript, 82 | creationHeight: ctx.network.height, 83 | assets: tokensIn, 84 | additionalRegisters: EmptyRegisters 85 | } 86 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 87 | return Promise.resolve({ 88 | inputs: ctx.inputs, 89 | dataInputs: [], 90 | outputs: prepend(orderOut, uiRewardOut), 91 | changeAddress: ctx.changeAddress, 92 | feeNErgs: ctx.feeNErgs 93 | }) 94 | } 95 | 96 | swap(params: SwapParams, ctx: TransactionContext): Promise { 97 | const proxyScript = T2T.swap( 98 | params.poolId, 99 | params.poolFeeNum, 100 | params.quoteAsset, 101 | params.minQuoteOutput, 102 | params.exFeePerToken, 103 | ctx.feeNErgs, 104 | params.pk 105 | ) 106 | const outputGranted = ctx.inputs.totalOutputWithoutChange 107 | const baseAssetId = params.baseInput.asset.id 108 | const baseIn = outputGranted.assets.filter(t => t.tokenId === baseAssetId)[0] 109 | 110 | const minExFee = BigInt((Number(params.minQuoteOutput) * params.exFeePerToken).toFixed(0)) 111 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, minExFee) 112 | if (outputGranted.nErgs < minNErgs) 113 | return Promise.reject( 114 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 115 | ) 116 | if (!baseIn) 117 | return Promise.reject(new InsufficientInputs(`Base asset ${params.baseInput.asset.name} not provided`)) 118 | 119 | const orderOut: ErgoBoxCandidate = { 120 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 121 | ergoTree: proxyScript, 122 | creationHeight: ctx.network.height, 123 | assets: [baseIn], 124 | additionalRegisters: EmptyRegisters 125 | } 126 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 127 | return Promise.resolve({ 128 | inputs: ctx.inputs, 129 | dataInputs: [], 130 | outputs: prepend(orderOut, uiRewardOut), 131 | changeAddress: ctx.changeAddress, 132 | feeNErgs: ctx.feeNErgs 133 | }) 134 | } 135 | 136 | private mkUiReward(height: number, uiFee: bigint): ErgoBoxCandidate[] { 137 | return uiFee > 0 138 | ? [ 139 | { 140 | value: uiFee, 141 | ergoTree: ergoTreeFromAddress(this.uiRewardAddress), 142 | creationHeight: height, 143 | assets: [], 144 | additionalRegisters: EmptyRegisters 145 | } 146 | ] 147 | : [] 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/amm/spfFee/contracts/n2tPoolContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, PublicKey, TokenId} from "@ergolabs/ergo-sdk" 2 | import {SpecExFeePerToken} from "../../../types" 3 | import {Bool, Bytes, Int, Long, ProveDlog, RedeemerBytes} from "../../../utils/contract" 4 | import {decimalToFractional} from "../../../utils/math" 5 | import {PoolId} from "../../common/types" 6 | import {DepositContract, RedeemContract, SwapBuyContract, SwapSellContract} from "./n2tTemplates" 7 | 8 | export function deposit( 9 | poolId: PoolId, 10 | pk: PublicKey, 11 | selfX: bigint, 12 | selfY: bigint, 13 | maxMinerFee: bigint 14 | ): ErgoTree { 15 | return DepositContract.build({ 16 | refundProp: ProveDlog(pk), 17 | selfXAmount: Long(selfX), 18 | selfYAmount: Long(selfY), 19 | poolNFT: Bytes(poolId), 20 | redeemerPropBytes: RedeemerBytes(pk), 21 | maxMinerFee: Long(maxMinerFee) 22 | }) 23 | } 24 | 25 | export function redeem(poolId: PoolId, pk: PublicKey, maxMinerFee: bigint): ErgoTree { 26 | return RedeemContract.build({ 27 | poolNFT: Bytes(poolId), 28 | maxMinerFee: Long(maxMinerFee), 29 | redeemerPropBytes: RedeemerBytes(pk), 30 | refundProp: ProveDlog(pk) 31 | }) 32 | } 33 | 34 | export function swapSell( 35 | poolId: PoolId, 36 | baseAmount: bigint, 37 | poolFeeNum: number, 38 | quoteId: TokenId, 39 | minQuoteAmount: bigint, 40 | exFeePerToken: SpecExFeePerToken, 41 | maxMinerFee: bigint, 42 | maxExFee: bigint, 43 | specIsQuote: boolean, 44 | pk: PublicKey 45 | ): ErgoTree { 46 | const [dexFeePerTokenNum, dexFeePerTokenDenom] = decimalToFractional(exFeePerToken.amount) 47 | 48 | return SwapSellContract.build({ 49 | exFeePerTokenDenom: Long(dexFeePerTokenDenom), 50 | delta: Long(dexFeePerTokenDenom - dexFeePerTokenNum), 51 | baseAmount: Long(baseAmount), 52 | feeNum: Int(poolFeeNum), 53 | refundProp: ProveDlog(pk), 54 | spectrumIsQuote: Bool(specIsQuote), 55 | maxExFee: Long(maxExFee), 56 | poolNFT: Bytes(poolId), 57 | redeemerPropBytes: RedeemerBytes(pk), 58 | quoteId: Bytes(quoteId), 59 | minQuoteAmount: Long(minQuoteAmount), 60 | spectrumId: Bytes(exFeePerToken.tokenId), 61 | feeDenom: Int(1000), 62 | maxMinerFee: Long(maxMinerFee) 63 | }) 64 | } 65 | 66 | export function swapBuy( 67 | poolId: PoolId, 68 | baseAmount: bigint, 69 | poolFeeNum: number, 70 | minQuoteAmount: bigint, 71 | exFeePerToken: SpecExFeePerToken, 72 | maxMinerFee: bigint, 73 | maxExFee: bigint, 74 | pk: PublicKey 75 | ): ErgoTree { 76 | const [dexFeePerTokenNum, dexFeePerTokenDenom] = decimalToFractional(exFeePerToken.amount) 77 | 78 | return SwapBuyContract.build({ 79 | baseAmount: Long(baseAmount), 80 | feeNum: Int(poolFeeNum), 81 | refundProp: ProveDlog(pk), 82 | maxExFee: Long(maxExFee), 83 | exFeePerTokenDenom: Long(dexFeePerTokenDenom), 84 | exFeePerTokenNum: Long(dexFeePerTokenNum), 85 | poolNFT: Bytes(poolId), 86 | redeemerPropBytes: RedeemerBytes(pk), 87 | minQuoteAmount: Long(minQuoteAmount), 88 | spectrumId: Bytes(exFeePerToken.tokenId), 89 | feeDenom: Int(1000), 90 | maxMinerFee: Long(maxMinerFee) 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /src/amm/spfFee/contracts/t2tPoolContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, PublicKey, TokenId} from "@ergolabs/ergo-sdk" 2 | import {SpecExFeePerToken} from "../../../types" 3 | import {Bool, Bytes, Int, Long, ProveDlog, RedeemerBytes} from "../../../utils/contract" 4 | import {decimalToFractional} from "../../../utils/math" 5 | import {PoolId} from "../../common/types" 6 | import {DepositContract, RedeemContract, SwapContract} from "./t2tTemplates" 7 | 8 | export function deposit( 9 | poolId: PoolId, 10 | pk: PublicKey, 11 | maxMinerFee: bigint, 12 | selfXAmount: bigint, 13 | selfYAmount: bigint 14 | ): ErgoTree { 15 | return DepositContract.build({ 16 | selfXAmount: Long(selfXAmount), 17 | refundProp: ProveDlog(pk), 18 | selfYAmount: Long(selfYAmount), 19 | poolNFT: Bytes(poolId), 20 | redeemerPropBytes: RedeemerBytes(pk), 21 | maxMinerFee: Long(maxMinerFee) 22 | }) 23 | } 24 | 25 | export function redeem(poolId: PoolId, pk: PublicKey, maxMinerFee: bigint): ErgoTree { 26 | return RedeemContract.build({ 27 | refundProp: ProveDlog(pk), 28 | poolNFT: Bytes(poolId), 29 | redeemerPropBytes: RedeemerBytes(pk), 30 | maxMinerFee: Long(maxMinerFee) 31 | }) 32 | } 33 | 34 | export function swap( 35 | poolId: PoolId, 36 | poolFeeNum: number, 37 | quoteId: TokenId, 38 | minQuoteAmount: bigint, 39 | exFeePerToken: SpecExFeePerToken, 40 | maxMinerFee: bigint, 41 | maxExFee: bigint, 42 | baseAmount: bigint, 43 | specIsQuote: boolean, 44 | pk: PublicKey 45 | ): ErgoTree { 46 | const [exFeePerTokenNum, exFeePerTokenDenom] = decimalToFractional(exFeePerToken.amount) 47 | 48 | return SwapContract.build({ 49 | quoteId: Bytes(quoteId), 50 | maxExFee: Long(maxExFee), 51 | exFeePerTokenDenom: Long(exFeePerTokenDenom), 52 | baseAmount: Long(baseAmount), 53 | feeNum: Int(poolFeeNum), 54 | feeDenom: Int(1000), 55 | refundProp: ProveDlog(pk), 56 | spectrumIsQuote: Bool(specIsQuote), 57 | poolNFT: Bytes(poolId), 58 | redeemerPropBytes: RedeemerBytes(pk), 59 | minQuoteAmount: Long(minQuoteAmount), 60 | delta: Long(exFeePerTokenDenom - exFeePerTokenNum), 61 | spectrumId: Bytes(exFeePerToken.tokenId), 62 | maxMinerFee: Long(maxMinerFee) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /src/amm/spfFee/contracts/t2tTemplates.ts: -------------------------------------------------------------------------------- 1 | // SWAP 2 | import {Bool, Bytes, Contract, Int, Long, ProveDlog, RedeemerBytes} from "../../../utils/contract" 3 | 4 | export const SwapSample = 5 | "19bd052304000e20040404040404040404040404040404040404040404040404040404040404040405c80105e01204c80f04d00f08cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d0404040804020400010005f015052c0404040606010104000e2002020202020202020202020202020202020202020202020202020202020202020e20010101010101010101010101010101010101010101010101010101010101010105c00c0100010105f015059c01060100040404020e20030303030303030303030303030303030303030303030303030303030303030301010e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d805d601b2a4730000d6027301d6037302d6049c73037e730405d6057305eb027306d195ed92b1a4730793b1db630872017308d80ad606db63087201d607b2a5730900d608db63087207d609b27208730a00d60a8c720902d60b95730b9d9c7e99720a730c067e7203067e730d067e720a06d60cb27206730e00d60d7e8c720c0206d60e7e8cb27206730f000206d60f9a720b7310ededededededed938cb2720673110001731293c272077313938c720901720292720a731492c17207c1a79573157316d801d610997e7317069d9c720b7e7318067e72030695ed917210731992b17208731ad801d611b27208731b00ed938c721101731c927e8c721102067210731d95938c720c017202909c720d7e7204069c720f9a9c720e7e7205067e720406909c720e7e7204069c720f9a9c720d7e7205067e72040690b0ada5d90110639593c27210731ec17210731f7320d90110599a8c7210018c72100273217322" 6 | 7 | export const SwapTemplate = 8 | "d805d601b2a4730000d6027301d6037302d6049c73037e730405d6057305eb027306d195ed92b1a4730793b1db630872017308d80ad606db63087201d607b2a5730900d608db63087207d609b27208730a00d60a8c720902d60b95730b9d9c7e99720a730c067e7203067e730d067e720a06d60cb27206730e00d60d7e8c720c0206d60e7e8cb27206730f000206d60f9a720b7310ededededededed938cb2720673110001731293c272077313938c720901720292720a731492c17207c1a79573157316d801d610997e7317069d9c720b7e7318067e72030695ed917210731992b17208731ad801d611b27208731b00ed938c721101731c927e8c721102067210731d95938c720c017202909c720d7e7204069c720f9a9c720e7e7205067e720406909c720e7e7204069c720f9a9c720d7e7205067e72040690b0ada5d90110639593c27210731ec17210731f7320d90110599a8c7210018c72100273217322" 9 | 10 | export const SwapContract = Contract(SwapSample, { 11 | quoteId: [1, Bytes], 12 | exFeePerTokenDenom: [2, Long], 13 | baseAmount: [3, Long], 14 | feeNum: [4, Int], 15 | feeDenom: [5, Int], 16 | refundProp: [6, ProveDlog], 17 | spectrumIsQuote: [11, Bool], 18 | maxExFee: [12, Long], 19 | delta: [13, Long], 20 | poolNFT: [18, Bytes], 21 | redeemerPropBytes: [19, RedeemerBytes], 22 | minQuoteAmount: [20, Long], 23 | spectrumId: [28, Bytes], 24 | maxMinerFee: [33, Long] 25 | }) 26 | 27 | // DEPOSIT 28 | 29 | export const DepositSample = 30 | "19dd041a040008cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d040404080402040205feffffffffffffffff01040405a01f040605f02e040004000e2002020202020202020202020202020202020202020202020202020202020202020e2001010101010101010101010101010101010101010101010101010101010101010404040204040402010101000e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d80cd602db63087201d603b2a5730400d604b27202730500d6057e9973068c72040206d606b27202730700d6077e8c72060206d6089d9c7e73080672057207d609b27202730900d60a7e8c72090206d60b9d9c7e730a067205720ad60cdb63087203d60db2720c730b00edededededed938cb27202730c0001730d93c27203730e92c17203c1a795ed8f7208720b93b1720c730fd801d60eb2720c731000ed938c720e018c720901927e8c720e02069d9c99720b7208720a720595ed917208720b93b1720c7311d801d60eb2720c731200ed938c720e018c720601927e8c720e02069d9c997208720b7207720595937208720b73137314938c720d018c720401927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7315c1720e73167317d9010e599a8c720e018c720e0273187319" 31 | 32 | export const DepositTemplate = 33 | "d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d80cd602db63087201d603b2a5730400d604b27202730500d6057e9973068c72040206d606b27202730700d6077e8c72060206d6089d9c7e73080672057207d609b27202730900d60a7e8c72090206d60b9d9c7e730a067205720ad60cdb63087203d60db2720c730b00edededededed938cb27202730c0001730d93c27203730e92c17203c1a795ed8f7208720b93b1720c730fd801d60eb2720c731000ed938c720e018c720901927e8c720e02069d9c99720b7208720a720595ed917208720b93b1720c7311d801d60eb2720c731200ed938c720e018c720601927e8c720e02069d9c997208720b7207720595937208720b73137314938c720d018c720401927e8c720d0206a17208720b90b0ada5d9010e639593c2720e7315c1720e73167317d9010e599a8c720e018c720e0273187319" 34 | 35 | export const DepositContract = Contract(DepositSample, { 36 | refundProp: [1, ProveDlog], 37 | selfXAmount: [8, Long], 38 | selfYAmount: [10, Long], 39 | poolNFT: [13, Bytes], 40 | redeemerPropBytes: [14, RedeemerBytes], 41 | maxMinerFee: [24, Long] 42 | }) 43 | 44 | // REDEEM 45 | 46 | export const RedeemSample = 47 | "19e60314040008cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d0404040804020400040404020406040005feffffffffffffffff01040204000e2002020202020202020202020202020202020202020202020202020202020202020e2001010101010101010101010101010101010101010101010101010101010101010e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d809d602db63087201d603b2a5730400d604db63087203d605b27204730500d606b27202730600d607b27204730700d608b27202730800d6097e8cb2db6308a77309000206d60a7e99730a8cb27202730b000206edededededed938cb27202730c0001730d93c27203730e938c7205018c720601938c7207018c720801927e8c720502069d9c72097e8c72060206720a927e8c720702069d9c72097e8c72080206720a90b0ada5d9010b639593c2720b730fc1720b73107311d9010b599a8c720b018c720b0273127313" 48 | 49 | export const RedeemTemplate = 50 | "d801d601b2a4730000eb027301d195ed92b1a4730293b1db630872017303d809d602db63087201d603b2a5730400d604db63087203d605b27204730500d606b27202730600d607b27204730700d608b27202730800d6097e8cb2db6308a77309000206d60a7e99730a8cb27202730b000206edededededed938cb27202730c0001730d93c27203730e938c7205018c720601938c7207018c720801927e8c720502069d9c72097e8c72060206720a927e8c720702069d9c72097e8c72080206720a90b0ada5d9010b639593c2720b730fc1720b73107311d9010b599a8c720b018c720b0273127313" 51 | 52 | export const RedeemContract = Contract(RedeemSample, { 53 | refundProp: [1, ProveDlog], 54 | poolNFT: [13, Bytes], 55 | redeemerPropBytes: [14, RedeemerBytes], 56 | maxMinerFee: [18, Long] 57 | }) 58 | -------------------------------------------------------------------------------- /src/amm/spfFee/interpreters/poolActions.ts: -------------------------------------------------------------------------------- 1 | import {Address, ErgoTx, isNative, Prover, TxAssembler, TxRequest} from "@ergolabs/ergo-sdk" 2 | import {SpecExFeeType} from "../../../types" 3 | import {N2tPoolSetupAction} from "../../common/interpreters/n2tPoolSetupAction" 4 | import {PoolActionsSelector, wrapPoolActions} from "../../common/interpreters/poolActions" 5 | import {T2tPoolSetupAction} from "../../common/interpreters/t2tPoolSetupAction" 6 | import {N2tPoolActions} from "./n2tPoolActions" 7 | import {T2tPoolActions} from "./t2tPoolActions" 8 | 9 | export function makeSpfPoolActionsSelector( 10 | uiRewardAddress: Address 11 | ): PoolActionsSelector { 12 | return pool => 13 | isNative(pool.x.asset) || isNative(pool.y.asset) 14 | ? new N2tPoolActions(uiRewardAddress, new N2tPoolSetupAction()) 15 | : new T2tPoolActions(uiRewardAddress, new T2tPoolSetupAction()) 16 | } 17 | 18 | export function makeWrappedSpfPoolActionsSelector( 19 | uiRewardAddress: Address, 20 | prover: Prover, 21 | txAsm: TxAssembler 22 | ): PoolActionsSelector { 23 | return pool => 24 | wrapPoolActions( 25 | isNative(pool.x.asset) || isNative(pool.y.asset) 26 | ? new N2tPoolActions(uiRewardAddress, new N2tPoolSetupAction()) 27 | : new T2tPoolActions(uiRewardAddress, new T2tPoolSetupAction()), 28 | prover, 29 | txAsm 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/amm/spfFee/interpreters/t2tPoolActions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | EmptyRegisters, 4 | ErgoBoxCandidate, 5 | ergoTreeFromAddress, 6 | InsufficientInputs, 7 | TransactionContext, 8 | TxRequest 9 | } from "@ergolabs/ergo-sdk" 10 | import {prepend} from "ramda" 11 | import {SpecAssetId} from "../../../constants" 12 | import {SpecExFeeType} from "../../../types" 13 | import {minValueForOrder} from "../../common/interpreters/mins" 14 | import {PoolActions} from "../../common/interpreters/poolActions" 15 | import {PoolSetupAction} from "../../common/interpreters/poolSetupAction" 16 | import {DepositParams} from "../../common/models/depositParams" 17 | import {PoolSetupParams} from "../../common/models/poolSetupParams" 18 | import {RedeemParams} from "../../common/models/redeemParams" 19 | import {SwapParams} from "../../common/models/swapParams" 20 | import * as T2T from "../contracts/t2tPoolContracts" 21 | 22 | export class T2tPoolActions implements PoolActions { 23 | constructor(public readonly uiRewardAddress: Address, private readonly setupImpl: PoolSetupAction) {} 24 | 25 | setup(params: PoolSetupParams, ctx: TransactionContext): Promise { 26 | return this.setupImpl.setup(params, ctx, this.mkUiReward(ctx.network.height, params.uiFee)) 27 | } 28 | 29 | deposit(params: DepositParams, ctx: TransactionContext): Promise { 30 | const [x, y] = [params.x, params.y] 31 | const [specIsX, specIsY] = [params.x.asset.id === SpecAssetId, params.y.asset.id === SpecAssetId] 32 | const specIsDeposited = specIsX || specIsY 33 | 34 | // TODO: ASK about fee 35 | const outputGranted = ctx.inputs.totalOutputWithoutChange 36 | const pairIn = [ 37 | outputGranted.assets.filter(t => t.tokenId === x.asset.id), 38 | outputGranted.assets.filter(t => t.tokenId === y.asset.id), 39 | specIsDeposited ? [] : outputGranted.assets.filter(t => t.tokenId === SpecAssetId) 40 | ].flat() 41 | 42 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, 0n) 43 | if (outputGranted.nErgs < minNErgs) 44 | return Promise.reject( 45 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 46 | ) 47 | if (!((pairIn.length == 2 && specIsDeposited) || pairIn.length === 3)) 48 | return Promise.reject( 49 | new InsufficientInputs( 50 | `Wrong number of input tokens provided ${pairIn.length}, required ${specIsDeposited ? 2 : 3}` 51 | ) 52 | ) 53 | 54 | const proxyScript = T2T.deposit(params.poolId, params.pk, ctx.feeNErgs, x.amount, y.amount) 55 | 56 | const orderOut: ErgoBoxCandidate = { 57 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 58 | ergoTree: proxyScript, 59 | creationHeight: ctx.network.height, 60 | assets: pairIn, 61 | additionalRegisters: EmptyRegisters 62 | } 63 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 64 | return Promise.resolve({ 65 | inputs: ctx.inputs, 66 | dataInputs: [], 67 | outputs: prepend(orderOut, uiRewardOut), 68 | changeAddress: ctx.changeAddress, 69 | feeNErgs: ctx.feeNErgs 70 | }) 71 | } 72 | 73 | redeem(params: RedeemParams, ctx: TransactionContext): Promise { 74 | const proxyScript = T2T.redeem(params.poolId, params.pk, ctx.feeNErgs) 75 | const outputGranted = ctx.inputs.totalOutputWithoutChange 76 | const tokensIn = outputGranted.assets 77 | .filter(t => t.tokenId === params.lp.asset.id) 78 | .concat(outputGranted.assets.filter(t => t.tokenId === SpecAssetId)) 79 | 80 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, 0n) 81 | if (outputGranted.nErgs < minNErgs) 82 | return Promise.reject( 83 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 84 | ) 85 | if (tokensIn.length != 2) 86 | return Promise.reject( 87 | new InsufficientInputs(`Wrong number of input tokens provided ${tokensIn.length}, required 2`) 88 | ) 89 | 90 | const orderOut = { 91 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 92 | ergoTree: proxyScript, 93 | creationHeight: ctx.network.height, 94 | assets: tokensIn, 95 | additionalRegisters: EmptyRegisters 96 | } 97 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 98 | return Promise.resolve({ 99 | inputs: ctx.inputs, 100 | dataInputs: [], 101 | outputs: prepend(orderOut, uiRewardOut), 102 | changeAddress: ctx.changeAddress, 103 | feeNErgs: ctx.feeNErgs 104 | }) 105 | } 106 | 107 | swap(params: SwapParams, ctx: TransactionContext): Promise { 108 | const specIsBase = params.baseInput.asset.id === SpecAssetId 109 | const outputGranted = ctx.inputs.totalOutputWithoutChange 110 | const baseAssetId = params.baseInput.asset.id 111 | 112 | const tokensIn = outputGranted.assets 113 | .filter(t => t.tokenId === baseAssetId) 114 | .concat(specIsBase ? [] : outputGranted.assets.filter(t => t.tokenId === SpecAssetId)) 115 | 116 | const minNErgs = minValueForOrder(ctx.feeNErgs, params.uiFee, 0n) 117 | if (outputGranted.nErgs < minNErgs) 118 | return Promise.reject( 119 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 120 | ) 121 | if (!((tokensIn.length === 1 && specIsBase) || tokensIn.length === 2)) 122 | return Promise.reject( 123 | new InsufficientInputs( 124 | `Base asset ${params.baseInput.asset.name} not provided or SPF fee not provided` 125 | ) 126 | ) 127 | 128 | const maxExFee = specIsBase ? tokensIn[0].amount - params.baseInput.amount : tokensIn[1].amount 129 | const proxyScript = T2T.swap( 130 | params.poolId, 131 | params.poolFeeNum, 132 | params.quoteAsset, 133 | params.minQuoteOutput, 134 | params.exFeePerToken, 135 | ctx.feeNErgs, 136 | maxExFee, 137 | params.baseInput.amount, 138 | params.quoteAsset === SpecAssetId, 139 | params.pk 140 | ) 141 | 142 | const orderOut: ErgoBoxCandidate = { 143 | value: outputGranted.nErgs - ctx.feeNErgs - params.uiFee, 144 | ergoTree: proxyScript, 145 | creationHeight: ctx.network.height, 146 | assets: tokensIn, 147 | additionalRegisters: EmptyRegisters 148 | } 149 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, params.uiFee) 150 | return Promise.resolve({ 151 | inputs: ctx.inputs, 152 | dataInputs: [], 153 | outputs: prepend(orderOut, uiRewardOut), 154 | changeAddress: ctx.changeAddress, 155 | feeNErgs: ctx.feeNErgs 156 | }) 157 | } 158 | 159 | private mkUiReward(height: number, uiFee: bigint): ErgoBoxCandidate[] { 160 | return uiFee > 0 161 | ? [ 162 | { 163 | value: uiFee, 164 | ergoTree: ergoTreeFromAddress(this.uiRewardAddress), 165 | creationHeight: height, 166 | assets: [], 167 | additionalRegisters: EmptyRegisters 168 | } 169 | ] 170 | : [] 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const NativeAssetId = "0000000000000000000000000000000000000000000000000000000000000000" 2 | export const NativeAssetTicker = "ERG" 3 | export const NativeAssetDecimals = 9 4 | export const BlocktimeMillis = BigInt(2 * 60 * 1000) 5 | 6 | export const SpecAssetId = "9a06d9e545a41fd51eeffc5e20d818073bf820c635e2a9d922269913e0de369d" 7 | -------------------------------------------------------------------------------- /src/contracts/poolContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, HexString} from "@ergolabs/ergo-sdk" 2 | 3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 4 | // @ts-ignore 5 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 6 | export type PoolContracts = { 7 | poolTree: ErgoTree 8 | poolTemplateHash: HexString 9 | } 10 | -------------------------------------------------------------------------------- /src/entities/price.ts: -------------------------------------------------------------------------------- 1 | export class Price { 2 | readonly numerator: bigint 3 | readonly denominator: bigint 4 | 5 | constructor(numerator: bigint, denominator: bigint) { 6 | this.numerator = numerator 7 | this.denominator = denominator 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/fromBox.ts: -------------------------------------------------------------------------------- 1 | import {ErgoBox} from "@ergolabs/ergo-sdk" 2 | 3 | /** Used to convert `ErgoBox` to domain entity `TEntity`. 4 | */ 5 | export interface FromBox { 6 | /** Convert `ErgoBox` to domain entity `TEntity`. 7 | */ 8 | from(box: ErgoBox): TEntity | undefined 9 | 10 | /** Convert an array of `ErgoBox` to an array of entities `TEntity`. 11 | */ 12 | fromMany(boxes: ErgoBox[]): TEntity[] 13 | } 14 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {PoolActions, PoolActionsSelector} from "./amm/common/interpreters/poolActions" 2 | export { 3 | makeSpfPoolActionsSelector, 4 | makeWrappedSpfPoolActionsSelector 5 | } from "./amm/spfFee/interpreters/poolActions" 6 | export { 7 | makeNativePoolActionsSelector, 8 | makeWrappedNativePoolActionsSelector 9 | } from "./amm/nativeFee/interpreters/poolActions" 10 | export {Refunds, AmmOrderRefundsWrapper, AmmOrderRefunds} from "./amm/common/interpreters/refunds" 11 | export {makeTokenPools, makeNativePools, makeDexyPools} from "./amm/common/services/pools" 12 | export {History, NetworkHistory, makeHistory} from "./amm/common/services/history" 13 | export {AmmOrderInfo} from "./amm/common/models/ammOrderInfo" 14 | export {AmmPoolInfo} from "./amm/common/models/ammPoolInfo" 15 | export { 16 | AmmDexOperation, 17 | AmmOrderType, 18 | AmmDexOperationType, 19 | AmmOrder, 20 | AmmOrderStatus, 21 | RefundOperation, 22 | TxStatus 23 | } from "./amm/common/models/operations" 24 | export {AmmOrdersParser, DefaultAmmOrdersParser} from "./amm/common/parsers/ammOrdersParser" 25 | export {T2TAmmPoolsParser, N2TAmmPoolsParser} from "./amm/common/parsers/ammPoolsParser" 26 | export {AmmPoolsInfoParser, DefaultAmmPoolsInfoParser} from "./amm/common/parsers/ammPoolsInfoParser" 27 | export {DefaultAmmPoolValidation} from "./amm/common/validation/ammPoolValidation" 28 | export {PoolValidation, ValidationResult, ValidationErrors, OK} from "./validation/poolValidation" 29 | export {AmmPool} from "./amm/common/entities/ammPool" 30 | export {Swap} from "./amm/common/entities/swap" 31 | export {PoolSetupParams, makePoolSetupParams} from "./amm/common/models/poolSetupParams" 32 | export {DepositParams} from "./amm/common/models/depositParams" 33 | export {RedeemParams} from "./amm/common/models/redeemParams" 34 | export {SwapParams} from "./amm/common/models/swapParams" 35 | export {PoolId} from "./amm/common/types" 36 | export {SwapExtremums, swapVars} from "./amm/common/math/swap" 37 | export {Price} from "./entities/price" 38 | export {RefundParams} from "./models/refundParams" 39 | export {decimalToFractional, evaluate} from "./utils/math" 40 | export { 41 | blocksToMillis, 42 | millisToBlocks, 43 | daysCountToBlocks, 44 | hoursCountToBlocks, 45 | monthCountToBlocks, 46 | weeksCountToBlocks, 47 | blocksToTimestamp, 48 | timestampToBlocks, 49 | blocksToDaysCount 50 | } from "./utils/blocks" 51 | export {minValueForOrder, minValueForSetup} from "./amm/common/interpreters/mins" 52 | 53 | export {mkLockActions, LockActions} from "./security/interpreters/lockActions" 54 | export {LockParams, WithdrawalParams, RelockParams} from "./security/models" 55 | export {LockParser, mkLockParser} from "./security/parsers/lockParser" 56 | export {LocksHistory, mkLocksHistory} from "./security/services/locksHistory" 57 | 58 | export {PoolContracts} from "./contracts/poolContracts" 59 | export {Pools, makePools} from "./services/pools" 60 | 61 | export { 62 | mkPoolActions, 63 | mkWrappedPoolActions, 64 | wrapPoolActions, 65 | PoolActions as LmPoolActions 66 | } from "./lqmining/interpreters/poolActions" 67 | export {ActionContext} from "./lqmining/models/actionContext" 68 | export {makeLmPools} from "./lqmining/services/pools" 69 | export {makeStakes, Stakes} from "./lqmining/services/stakes" 70 | export {PoolSetupConf, LqDepositConf, LqRedeemConf} from "./lqmining/models/poolOpParams" 71 | export {LmPool, LmPoolConfig} from "./lqmining/entities/lmPool" 72 | 73 | export * from "./constants" 74 | -------------------------------------------------------------------------------- /src/lqmining/contracts/poolValidator.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, HexString} from "@ergolabs/ergo-sdk" 2 | import {PoolContracts} from "../../contracts/poolContracts" 3 | import {LmPool} from "../entities/lmPool" 4 | 5 | export function pool(): ErgoTree { 6 | return ( 7 | "19ec05240400040004020402040404040406040604080408040404020400040004020402" + 8 | "0400040a050005000404040204020e2074aeba0675c10c7fff46d3aa5e5a8efc55f0b0d8" + 9 | "7393dcb2f4b0a04be213cecb040004020500040204020406050005000402050205000500" + 10 | "d81ed601b2a5730000d602db63087201d603db6308a7d604b27203730100d605e4c6a704" + 11 | "10d606e4c6a70505d607e4c6a70605d608b27202730200d609b27203730300d60ab27202" + 12 | "730400d60bb27203730500d60cb27202730600d60db27203730700d60e8c720d01d60fb2" + 13 | "7202730800d610b27203730900d6118c721001d6128c720b02d613998c720a027212d614" + 14 | "8c720902d615b27205730a00d6169a99a37215730bd617b27205730c00d6189d72167217" + 15 | "d61995919e72167217730d9a7218730e7218d61ab27205730f00d61b7e721a05d61c9d72" + 16 | "06721bd61d998c720c028c720d02d61e998c720f028c721002d1ededededed93b2720273" + 17 | "10007204ededed93e4c672010410720593e4c672010505720693e4c6720106057207928c" + 18 | "c77201018cc7a70193c27201c2a7ededed938c7208018c720901938c720a018c720b0193" + 19 | "8c720c01720e938c720f01721193b172027311959172137312d802d61f9c721399721ba2" + 20 | "73137e721905d620b2a5731400ededed929a997206721472079c7e9995907219721a7219" + 21 | "9a721a7315731605721c937213f0721d93721ff0721eedededed93cbc272207317938602" + 22 | "720e7213b2db630872207318009386027211721fb2db63087220731900e6c67220040893" + 23 | "e4c67220050e8c720401958f7213731aededec929a997206721472079c7e999590721972" + 24 | "1a72199a721a731b731c05721c92a39a9a72159c721a7217b27205731d0093721df07213" + 25 | "92721e95917219721a731e9c721d99721ba2731f7e721905d801d61fe4c672010704eded" + 26 | "eded90721f9972197320909972149c7e99721a721f05721c9a721c7207907ef0998c7208" + 27 | "027214069d9c7e721c067e721e067e997212732106937213732293721d7323" 28 | ) 29 | } 30 | 31 | export function managedPool(): ErgoTree { 32 | return '19c0062804000400040204020404040404060406040804080404040204000400040204020400040a050005000404040204020e200508f3623d4b2be3bdb9737b3e65644f011167eefb830d9965205f022ceda40d0400040205000402040204060500050005feffffffffffffffff01050005000402060101050005000100d81fd601b2a5730000d602db63087201d603db6308a7d604b27203730100d605e4c6a70410d606e4c6a70505d607e4c6a70605d608b27202730200d609b27203730300d60ab27202730400d60bb27203730500d60cb27202730600d60db27203730700d60e8c720d01d60fb27202730800d610b27203730900d6118c721001d6128c720b02d613998c720a027212d6148c720902d615b27205730a00d6169a99a37215730bd617b27205730c00d6189d72167217d61995919e72167217730d9a7218730e7218d61ab27205730f00d61b7e721a05d61c9d7206721bd61d998c720c028c720d02d61e8c721002d61f998c720f02721ed1ededededed93b272027310007204ededed93e4c672010410720593e4c672010505720693e4c6720106057207928cc77201018cc7a70193c27201c2a7ededed938c7208018c720901938c720a018c720b01938c720c01720e938c720f01721193b172027311959172137312d802d6209c721399721ba273137e721905d621b2a5731400ededed929a7e9972067214067e7207067e9c7e9995907219721a72199a721a7315731605721c06937213f0721d937220f0721fedededed93cbc272217317938602720e7213b2db6308722173180093860272117220b2db63087221731900e6c67221060893e4c67221070e8c720401958f7213731aededec929a7e9972067214067e7207067e9c7e9995907219721a72199a721a731b731c05721c0692a39a9a72159c721a7217b27205731d0093721df0721392721f95917219721a731e9c721d99721ba2731f7e721905d804d620e4c672010704d62199721a7220d6227e722105d62399997320721e9c7212722295ed917223732191721f7322edededed9072209972197323909972149c7222721c9a721c7207907ef0998c7208027214069d9c99997e7214069d9c7e7206067e7221067e721a0673247e721f067e722306937213732593721d73267327'; 33 | 34 | } 35 | 36 | export function managedPoolTemplateHash(): HexString { 37 | return "728bc5b8f6244f191bd5c6b783b7895981dc37f1504458d0fc8e02754ecb3eff" 38 | } 39 | 40 | export function managedPoolBundle(): PoolContracts { 41 | return { 42 | poolTree: managedPool(), 43 | poolTemplateHash: managedPoolTemplateHash() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/lqmining/contracts/proxyValidators.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree, PublicKey, RustModule, TokenId} from "@ergolabs/ergo-sdk" 2 | import {ErgoTreePrefixHex, SigmaPropConstPrefixHex} from "../../amm/common/constants" 3 | import {fromHex} from "../../utils/hex" 4 | import {PoolId} from "../types" 5 | import {StakingBundleTreeBlake2b256} from "./templates" 6 | 7 | export const depositSample = '19a2041904000e2002020202020202020202020202020202020202020202020202020202020202020e20000000000000000000000000000000000000000000000000000000000000000008cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d0404040a040204040400040005fcffffffffffffffff0104000e200508f3623d4b2be3bdb9737b3e65644f011167eefb830d9965205f022ceda40d04060400040804140402050204040e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010100d803d601b2a4730000d6027301d6037302eb027303d195ed92b1a4730493b1db630872017305d805d604db63087201d605b2a5730600d606c57201d607b2a5730700d6088cb2db6308a773080002ededed938cb27204730900017202ed93c2720572039386027206730ab2db63087205730b00ededededed93cbc27207730c93d0e4c672070608720393e4c67207070e72029386028cb27204730d00017208b2db63087207730e009386028cb27204730f00019c72087e731005b2db6308720773110093860272067312b2db6308720773130090b0ada5d90109639593c272097314c1720973157316d90109599a8c7209018c72090273177318'; 8 | 9 | export const depositTemplate = 'd803d601b2a4730000d6027301d6037302eb027303d195ed92b1a4730493b1db630872017305d805d604db63087201d605b2a5730600d606c57201d607b2a5730700d6088cb2db6308a773080002ededed938cb27204730900017202ed93c2720572039386027206730ab2db63087205730b00ededededed93cbc27207730c93d0e4c672070608720393e4c67207070e72029386028cb27204730d00017208b2db63087207730e009386028cb27204730f00019c72087e731005b2db6308720773110093860272067312b2db6308720773130090b0ada5d90109639593c272097314c1720973157316d90109599a8c7209018c72090273177318'; 10 | 11 | export const redeemSample = '19d1020e08cd02217daf90deb73bdf8b6709bb42093fdfaff6573fd47b630e2d3fdd4a8193a74d04040400040a04020e691005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a573040500050005a09c010e2001010101010101010101010101010101010101010101010101010101010101010e20000000000000000000000000000000000000000000000000000000000000000005d00f04000100eb027300d195ed92b1a4730193b1db6308b2a47302007303d802d601b2a5730400d60290b0ada5d90102639593c272027305c1720273067307d90102599a8c7202018c7202027308ededed93c272017309938602730a730bb2db63087201730c0072027202730d'; 12 | 13 | export const redeemTemplate = 'eb027300d195ed92b1a4730193b1db6308b2a47302007303d802d601b2a5730400d60290b0ada5d90102639593c272027305c1720273067307d90102599a8c7202018c7202027308ededed93c272017309938602730a730bb2db63087201730c0072027202730d'; 14 | 15 | export function deposit( 16 | poolId: PoolId, 17 | redeemerPk: PublicKey, 18 | expectedNumEpochs: number, 19 | minerFee: bigint 20 | ): ErgoTree { 21 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(depositSample) 22 | .with_constant(1, RustModule.SigmaRust.Constant.from_byte_array(fromHex(poolId))) 23 | .with_constant( 24 | 2, 25 | RustModule.SigmaRust.Constant.from_byte_array( 26 | fromHex(ErgoTreePrefixHex + SigmaPropConstPrefixHex + redeemerPk) 27 | ) 28 | ) 29 | .with_constant(3, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + redeemerPk)) 30 | .with_constant(12, RustModule.SigmaRust.Constant.from_byte_array(fromHex(StakingBundleTreeBlake2b256))) 31 | .with_constant(16, RustModule.SigmaRust.Constant.from_i32(expectedNumEpochs)) 32 | .with_constant( 33 | 23, 34 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(minerFee.toString())) 35 | ) 36 | .to_base16_bytes() 37 | } 38 | 39 | export function redeem( 40 | redeemerPk: PublicKey, 41 | lqId: TokenId, 42 | expectedLqAmount: bigint, 43 | minerFee: bigint 44 | ): ErgoTree { 45 | return RustModule.SigmaRust.ErgoTree.from_base16_bytes(redeemSample) 46 | .with_constant(0, RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + redeemerPk)) 47 | .with_constant( 48 | 9, 49 | RustModule.SigmaRust.Constant.from_byte_array( 50 | fromHex(ErgoTreePrefixHex + SigmaPropConstPrefixHex + redeemerPk) 51 | ) 52 | ) 53 | .with_constant(10, RustModule.SigmaRust.Constant.from_byte_array(fromHex(lqId))) 54 | .with_constant( 55 | 11, 56 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(expectedLqAmount.toString())) 57 | ) 58 | .with_constant( 59 | 8, 60 | RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(minerFee.toString())) 61 | ) 62 | .to_base16_bytes() 63 | } 64 | -------------------------------------------------------------------------------- /src/lqmining/contracts/templates.ts: -------------------------------------------------------------------------------- 1 | import {HexString} from "@ergolabs/ergo-sdk" 2 | 3 | export const StakingBundleTreeBlake2b256: HexString = 4 | "0508f3623d4b2be3bdb9737b3e65644f011167eefb830d9965205f022ceda40d" 5 | 6 | export const StakingBundleTemplateHash: HexString = 7 | "88c122183349cde8c85f70287a098db39f1b3138e5650659f3c1746d02e435cb" 8 | -------------------------------------------------------------------------------- /src/lqmining/entities/lmPool.spec.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount} from "@ergolabs/ergo-sdk" 2 | import test from "ava" 3 | import {LmPool} from "./lmPool" 4 | 5 | test("Epochs left (unit epoch)", t => { 6 | const startedAt = 1000 7 | const pool = initPool(1, 10, startedAt) 8 | const diff = 5 9 | t.deepEqual(pool.numEpochsRemain(startedAt + diff), diff - 1) 10 | }) 11 | 12 | test("Epochs left", t => { 13 | const startedAt = 1000 14 | const pool = initPool(4, 10, startedAt) 15 | const diff = 5 16 | t.deepEqual(pool.numEpochsRemain(startedAt + diff), 8) 17 | }) 18 | 19 | function initPool(epochLen: number, epochNum: number, programStart: number): LmPool { 20 | const reward = new AssetAmount({id: "rew"}, 1000000000n) 21 | const lq = new AssetAmount({id: "lq"}, 1000000000n) 22 | const vlq = new AssetAmount({id: "vlq"}, 1000000000n) 23 | const tt = new AssetAmount({id: "tt"}, 1000000000n) 24 | const conf = { 25 | epochLen, 26 | epochNum, 27 | programStart, 28 | programBudget: reward.amount, 29 | execBudget: 100000000n 30 | } 31 | return new LmPool("0x", conf, reward, lq, vlq, tt) 32 | } 33 | -------------------------------------------------------------------------------- /src/lqmining/entities/lmPool.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount} from "@ergolabs/ergo-sdk" 2 | import {max} from "mathjs" 3 | import {PoolId} from "../types" 4 | 5 | export type LmPoolConfig = { 6 | epochLen: number 7 | epochNum: number 8 | programStart: number 9 | programBudget: bigint 10 | } 11 | 12 | export class LmPool { 13 | constructor( 14 | public readonly id: PoolId, 15 | public readonly conf: LmPoolConfig, 16 | public readonly budget: AssetAmount, 17 | public readonly lq: AssetAmount, 18 | public readonly vlq: AssetAmount, 19 | public readonly tt: AssetAmount 20 | ) {} 21 | 22 | get epochAlloc(): bigint { 23 | return this.conf.programBudget / BigInt(this.conf.epochNum) 24 | } 25 | 26 | numEpochsRemain(height: number): number { 27 | return this.conf.epochNum - max(this.currentEpoch(height), 0) 28 | } 29 | 30 | currentEpoch(height: number): number { 31 | const curBlockIx = height - this.conf.programStart + 1 32 | const curEpochIxRem = curBlockIx % this.conf.epochLen 33 | const curEpochIxR = Math.floor(curBlockIx / this.conf.epochLen) 34 | if (curEpochIxRem == 0 && curEpochIxR == 0) { 35 | return 0 36 | } else { 37 | if (curEpochIxRem > 0) { 38 | return curEpochIxR + 1 39 | } else { 40 | return curEpochIxR 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lqmining/interpreters/poolActions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | BoxSelection, 4 | EmptyRegisters, 5 | ErgoBoxCandidate, 6 | ergoTreeFromAddress, 7 | ErgoTx, 8 | InputSelector, 9 | Prover, 10 | TxAssembler, 11 | TxRequest 12 | } from "@ergolabs/ergo-sdk" 13 | import {prepend} from "ramda" 14 | import {notImplemented} from "../../utils/notImplemented" 15 | import * as validators from "../contracts/proxyValidators" 16 | import {ActionContext} from "../models/actionContext" 17 | import {LqDepositConf, LqRedeemConf, PoolSetupConf} from "../models/poolOpParams" 18 | 19 | /** LM Pool actions 20 | */ 21 | export interface PoolActions { 22 | /** Setup new LM program (LM pool). 23 | */ 24 | setup(conf: PoolSetupConf, ctx: ActionContext): Promise 25 | 26 | /** Deposit liquidity (LP tokens) to LM pool. 27 | */ 28 | deposit(conf: LqDepositConf, ctx: ActionContext): Promise 29 | 30 | /** Redeem liquidity (LP tokens) from LM pool. 31 | */ 32 | redeem(conf: LqRedeemConf, ctx: ActionContext): Promise 33 | } 34 | 35 | export function mkPoolActions(selector: InputSelector, uiRewardAddress: Address): PoolActions { 36 | return new LmPoolActions(selector, uiRewardAddress) 37 | } 38 | 39 | export function mkWrappedPoolActions( 40 | selector: InputSelector, 41 | prover: Prover, 42 | txAsm: TxAssembler, 43 | uiRewardAddress: Address 44 | ): PoolActions { 45 | return wrapPoolActions(mkPoolActions(selector, uiRewardAddress), prover, txAsm) 46 | } 47 | 48 | export function wrapPoolActions( 49 | actions: PoolActions, 50 | prover: Prover, 51 | txAsm: TxAssembler 52 | ): PoolActions { 53 | return new PoolActionsWrapper(actions, prover, txAsm) 54 | } 55 | 56 | class LmPoolActions implements PoolActions { 57 | constructor(public readonly selector: InputSelector, public readonly uiRewardAddress: Address) {} 58 | 59 | setup(conf: PoolSetupConf, ctx: ActionContext): Promise { 60 | notImplemented([conf, ctx]) 61 | } 62 | 63 | async deposit(conf: LqDepositConf, ctx: ActionContext): Promise { 64 | const orderValidator = validators.deposit( 65 | conf.poolId, 66 | conf.redeemerPk, 67 | conf.fullEpochsRemain, 68 | ctx.minerFee 69 | ) 70 | const depositInput = conf.depositAmount.toToken() 71 | const orderOut: ErgoBoxCandidate = { 72 | value: ctx.minBoxValue + ctx.minBoxValue + conf.executionFee, 73 | ergoTree: orderValidator, 74 | creationHeight: ctx.network.height, 75 | assets: [depositInput], 76 | additionalRegisters: EmptyRegisters 77 | } 78 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, ctx.uiFee) 79 | const inputs = await this.selector.select({ 80 | nErgs: orderOut.value + ctx.uiFee + ctx.minerFee, 81 | assets: [depositInput] 82 | }) 83 | if (inputs instanceof BoxSelection) { 84 | return { 85 | inputs: inputs, 86 | dataInputs: [], 87 | outputs: prepend(orderOut, uiRewardOut), 88 | changeAddress: ctx.changeAddress, 89 | feeNErgs: ctx.minerFee 90 | } 91 | } else { 92 | return Promise.reject(inputs) 93 | } 94 | } 95 | 96 | async redeem(conf: LqRedeemConf, ctx: ActionContext): Promise { 97 | const orderValidator = validators.redeem( 98 | conf.redeemerPk, 99 | conf.expectedLqAmount.asset.id, 100 | conf.expectedLqAmount.amount, 101 | ctx.minerFee 102 | ) 103 | const redeemerKey = conf.redeemerKey.toToken() 104 | const orderOut: ErgoBoxCandidate = { 105 | value: ctx.minBoxValue + conf.executionFee, 106 | ergoTree: orderValidator, 107 | creationHeight: ctx.network.height, 108 | assets: [redeemerKey], 109 | additionalRegisters: EmptyRegisters 110 | } 111 | const uiRewardOut: ErgoBoxCandidate[] = this.mkUiReward(ctx.network.height, ctx.uiFee) 112 | const inputs = await this.selector.select({ 113 | nErgs: orderOut.value + ctx.uiFee + ctx.minerFee, 114 | assets: [redeemerKey] 115 | }) 116 | if (inputs instanceof BoxSelection) { 117 | return { 118 | inputs: inputs, 119 | dataInputs: [], 120 | outputs: prepend(orderOut, uiRewardOut), 121 | changeAddress: ctx.changeAddress, 122 | feeNErgs: ctx.minerFee 123 | } 124 | } else { 125 | return Promise.reject(inputs) 126 | } 127 | } 128 | 129 | private mkUiReward(height: number, uiFee: bigint): ErgoBoxCandidate[] { 130 | return uiFee > 0 131 | ? [ 132 | { 133 | value: uiFee, 134 | ergoTree: ergoTreeFromAddress(this.uiRewardAddress), 135 | creationHeight: height, 136 | assets: [], 137 | additionalRegisters: EmptyRegisters 138 | } 139 | ] 140 | : [] 141 | } 142 | } 143 | 144 | class PoolActionsWrapper implements PoolActions { 145 | constructor( 146 | public readonly impl: PoolActions, 147 | public readonly prover: Prover, 148 | public readonly txAsm: TxAssembler 149 | ) {} 150 | 151 | async setup(conf: PoolSetupConf, ctx: ActionContext): Promise { 152 | const [txr0, txr1] = await this.impl.setup(conf, ctx) 153 | const tx0 = await this.prover.sign(this.txAsm.assemble(txr0, ctx.network)) 154 | const tx1 = await this.prover.sign(this.txAsm.assemble(txr1, ctx.network)) 155 | return [tx0, tx1] 156 | } 157 | 158 | async deposit(conf: LqDepositConf, ctx: ActionContext): Promise { 159 | return this.prover.sign(this.txAsm.assemble(await this.impl.deposit(conf, ctx), ctx.network)) 160 | } 161 | 162 | async redeem(conf: LqRedeemConf, ctx: ActionContext): Promise { 163 | return this.prover.sign(this.txAsm.assemble(await this.impl.redeem(conf, ctx), ctx.network)) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/lqmining/models/actionContext.ts: -------------------------------------------------------------------------------- 1 | import {Address} from "@ergolabs/ergo-sdk" 2 | import {NetworkContext} from "@ergolabs/ergo-sdk/build/main/entities/networkContext" 3 | 4 | export type ActionContext = { 5 | readonly changeAddress: Address 6 | readonly minBoxValue: bigint 7 | readonly minerFee: bigint 8 | readonly uiFee: bigint 9 | readonly network: NetworkContext 10 | } 11 | -------------------------------------------------------------------------------- /src/lqmining/models/poolOpParams.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, PublicKey} from "@ergolabs/ergo-sdk" 2 | import {PoolId} from "../types" 3 | 4 | export type PoolSetupConf = { 5 | readonly budget: AssetAmount 6 | } 7 | 8 | export type LqDepositConf = { 9 | readonly poolId: PoolId 10 | readonly fullEpochsRemain: number 11 | readonly depositAmount: AssetAmount 12 | readonly redeemerPk: PublicKey 13 | readonly executionFee: bigint 14 | } 15 | 16 | export type LqRedeemConf = { 17 | readonly expectedLqAmount: AssetAmount 18 | readonly redeemerKey: AssetAmount 19 | readonly redeemerPk: PublicKey 20 | readonly executionFee: bigint 21 | } 22 | -------------------------------------------------------------------------------- /src/lqmining/models/stake.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount} from "@ergolabs/ergo-sdk" 2 | import {PoolId} from "../types" 3 | 4 | export type Stake = { 5 | readonly poolId: PoolId 6 | readonly lockedLq: AssetAmount 7 | readonly bundleKeyAsset: AssetAmount 8 | } 9 | -------------------------------------------------------------------------------- /src/lqmining/parsers/lmPoolFromBox.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, deserializeConstant, ErgoBox, Int64Constant, RegisterId} from "@ergolabs/ergo-sdk" 2 | import {Int32ArrayConstant} from "@ergolabs/ergo-sdk/build/main/entities/constant" 3 | import {FromBox} from "../../fromBox" 4 | import {LmPool} from "../entities/lmPool" 5 | 6 | export class LmPoolFromBox implements FromBox { 7 | from(box: ErgoBox): LmPool | undefined { 8 | const r4 = box.additionalRegisters[RegisterId.R4] 9 | const r5 = box.additionalRegisters[RegisterId.R5] 10 | if (box.assets.length === 5 && r4 && r5) { 11 | const nft = box.assets[0].tokenId 12 | const rew = AssetAmount.fromToken(box.assets[1]) 13 | const lq = AssetAmount.fromToken(box.assets[2]) 14 | const vlq = AssetAmount.fromToken(box.assets[3]) 15 | const tmp = AssetAmount.fromToken(box.assets[4]) 16 | const settings = deserializeConstant(r4) 17 | const budget = deserializeConstant(r5) 18 | if (settings instanceof Int32ArrayConstant && budget instanceof Int64Constant) { 19 | const conf = { 20 | epochLen: settings.value[0], 21 | epochNum: settings.value[1], 22 | programStart: settings.value[2], 23 | programBudget: budget.value 24 | } 25 | return new LmPool(nft, conf, rew, lq, vlq, tmp) 26 | } 27 | } 28 | return undefined 29 | } 30 | 31 | fromMany(boxes: ErgoBox[]): LmPool[] { 32 | const pools = [] 33 | for (const box of boxes) { 34 | const pool = this.from(box) 35 | if (pool) pools.push(pool) 36 | } 37 | return pools 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/lqmining/parsers/stakeFromBox.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, deserializeConstant, ErgoBox, RegisterId, toHex} from "@ergolabs/ergo-sdk" 2 | import {ByteaConstant} from "@ergolabs/ergo-sdk/build/main/entities/constant" 3 | import {FromBox} from "../../fromBox" 4 | import {Stake} from "../models/stake" 5 | 6 | export class StakeFromBox implements FromBox { 7 | from(box: ErgoBox): Stake | undefined { 8 | const r7 = box.additionalRegisters[RegisterId.R7] 9 | 10 | if (!r7) { 11 | return undefined 12 | } 13 | 14 | const poolId = deserializeConstant(r7) 15 | if (!(poolId instanceof ByteaConstant)) { 16 | return undefined 17 | } 18 | 19 | if (box.assets.length === 3) { 20 | return { 21 | poolId: toHex(poolId.value), 22 | lockedLq: AssetAmount.fromToken(box.assets[0]), 23 | bundleKeyAsset: AssetAmount.fromToken(box.assets[2]) 24 | } 25 | } 26 | if (box.assets.length === 2) { 27 | return { 28 | poolId: toHex(poolId.value), 29 | lockedLq: AssetAmount.fromToken(box.assets[0]), 30 | bundleKeyAsset: AssetAmount.fromToken(box.assets[1]) 31 | } 32 | } 33 | return undefined 34 | } 35 | 36 | fromMany(boxes: ErgoBox[]): Stake[] { 37 | const stakes = [] 38 | for (const box of boxes) { 39 | const stake = this.from(box) 40 | if (stake) stakes.push(stake) 41 | } 42 | return stakes 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lqmining/services/pools.ts: -------------------------------------------------------------------------------- 1 | import {ErgoNetwork} from "@ergolabs/ergo-sdk" 2 | import {makePools, Pools} from "../../services/pools" 3 | import * as PV from "../contracts/poolValidator" 4 | import {LmPool} from "../entities/lmPool" 5 | import {LmPoolFromBox} from "../parsers/lmPoolFromBox" 6 | 7 | export function makeLmPools(network: ErgoNetwork): Pools { 8 | return makePools(network, new LmPoolFromBox(), PV.managedPoolBundle()) 9 | } 10 | -------------------------------------------------------------------------------- /src/lqmining/services/stakes.ts: -------------------------------------------------------------------------------- 1 | import {ErgoNetwork, Paging, TokenId} from "@ergolabs/ergo-sdk" 2 | import {BoxAssetsSearch} from "@ergolabs/ergo-sdk/build/main/network/models" 3 | import {FromBox} from "../../fromBox" 4 | import {StakingBundleTemplateHash} from "../contracts/templates" 5 | import {Stake} from "../models/stake" 6 | 7 | export interface Stakes { 8 | /** Search stakes by staking keys. 9 | */ 10 | searchByKeys(stakingKeys: TokenId[], paging: Paging): Promise<[Stake[], number]> 11 | } 12 | 13 | export function makeStakes(network: ErgoNetwork, parser: FromBox): Stakes { 14 | return new NetworkStakes(network, parser) 15 | } 16 | 17 | class NetworkStakes implements Stakes { 18 | constructor(public readonly network: ErgoNetwork, public readonly parser: FromBox) {} 19 | 20 | async searchByKeys(stakingKeys: TokenId[], paging: Paging): Promise<[Stake[], number]> { 21 | const req: BoxAssetsSearch = {ergoTreeTemplateHash: StakingBundleTemplateHash, assets: stakingKeys} 22 | const [boxes, totalBoxes] = await this.network.searchUnspentBoxesByTokensUnion(req, paging) 23 | const stakes = this.parser.fromMany(boxes) 24 | const invalid = boxes.length - stakes.length 25 | const total = totalBoxes - invalid 26 | return [stakes, total] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/lqmining/types.ts: -------------------------------------------------------------------------------- 1 | import {TokenId} from "@ergolabs/ergo-sdk" 2 | 3 | export type PoolId = TokenId 4 | -------------------------------------------------------------------------------- /src/lqmining/validation/lmPoolValidation.ts: -------------------------------------------------------------------------------- 1 | import {notImplemented} from "../../utils/notImplemented" 2 | import {PoolValidation, ValidationResult} from "../../validation/poolValidation" 3 | import {LmPool} from "../entities/lmPool" 4 | 5 | export class LmPoolValidation implements PoolValidation { 6 | validate(pool: LmPool): Promise { 7 | return notImplemented([pool]) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/models/refundParams.ts: -------------------------------------------------------------------------------- 1 | import {Address, ErgoBox, TxId} from "@ergolabs/ergo-sdk" 2 | 3 | export type RefundParams = { 4 | txId: TxId // txId the operation request to refund was submitted in 5 | recipientAddress: Address 6 | utxos?: ErgoBox[] 7 | } 8 | -------------------------------------------------------------------------------- /src/security/contracts/lockingContracts.ts: -------------------------------------------------------------------------------- 1 | import {ErgoTree} from "@ergolabs/ergo-sdk" 2 | import {SigmaRust} from "@ergolabs/ergo-sdk/build/main/utils/rustLoader" 3 | import {TokenLockSample} from "./lockingTemplates" 4 | 5 | export function tokenLock(R: SigmaRust): ErgoTree { 6 | return R.ErgoTree.from_base16_bytes(TokenLockSample).to_base16_bytes() 7 | } 8 | -------------------------------------------------------------------------------- /src/security/contracts/lockingTemplates.ts: -------------------------------------------------------------------------------- 1 | export const TokenLockSample = 2 | "195e03040004000400d802d601b2a5730000d602e4c6a70404ea02e4c6a70508d19593c27201c2a7d802d603b2db63087201730100d604b2db6308a7730200eded92e4c6720104047202938c7203018c720401928c7203028c7204028f7202a3" 3 | 4 | export const TokenLockTemplate = 5 | "d802d601b2a5730000d602e4c6a70404ea02e4c6a70508d19593c27201c2a7d802d603b2db63087201730100d604b2db6308a7730200eded92e4c6720104047202938c7203018c720401928c7203028c7204028f7202a3" 6 | 7 | export const TokenLockTemplateHash = "84d846dfc6f5ab119a457fc027656a1f6ecea6710a5c03dad38340a1b580c3a9" 8 | -------------------------------------------------------------------------------- /src/security/entities.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, BoxId, PublicKey} from "@ergolabs/ergo-sdk" 2 | 3 | export type TokenLock = { 4 | readonly boxId: BoxId 5 | readonly lockedAsset: AssetAmount 6 | readonly deadline: number 7 | readonly redeemer: PublicKey 8 | readonly active: boolean 9 | } 10 | -------------------------------------------------------------------------------- /src/security/interpreters/lockActions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoxSelection, 3 | EmptyRegisters, 4 | ErgoNetwork, 5 | ergoTreeFromAddress, 6 | ErgoTx, 7 | InsufficientInputs, 8 | Int32Constant, 9 | MinBoxValue, 10 | MinTransactionContext, 11 | Prover, 12 | RegisterId, 13 | registers, 14 | SigmaPropConstant, 15 | TransactionContext, 16 | TxAssembler 17 | } from "@ergolabs/ergo-sdk" 18 | import {SigmaRust} from "@ergolabs/ergo-sdk/build/main/utils/rustLoader" 19 | import {tokenLock} from "../contracts/lockingContracts" 20 | import {LockParams, RelockParams, WithdrawalParams} from "../models" 21 | import {LockParser} from "../parsers/lockParser" 22 | 23 | export interface LockActions { 24 | lockTokens(params: LockParams, ctx: TransactionContext): Promise 25 | 26 | withdrawTokens(params: WithdrawalParams, ctx: MinTransactionContext): Promise 27 | 28 | relockTokens(params: RelockParams, ctx: TransactionContext): Promise 29 | } 30 | 31 | export function mkLockActions( 32 | network: ErgoNetwork, 33 | parser: LockParser, 34 | prover: Prover, 35 | txAsm: TxAssembler, 36 | R: SigmaRust 37 | ): LockActions { 38 | return new ErgoTokensLockActions(network, parser, prover, txAsm, R) 39 | } 40 | 41 | class ErgoTokensLockActions implements LockActions { 42 | constructor( 43 | public readonly network: ErgoNetwork, 44 | public readonly parser: LockParser, 45 | public readonly prover: Prover, 46 | public readonly txAsm: TxAssembler, 47 | public readonly R: SigmaRust 48 | ) {} 49 | 50 | lockTokens(params: LockParams, ctx: TransactionContext): Promise { 51 | const outputGranted = ctx.inputs.totalOutputWithoutChange 52 | const lockScript = tokenLock(this.R) 53 | const lockOutput = { 54 | value: outputGranted.nErgs - ctx.feeNErgs, 55 | ergoTree: lockScript, 56 | creationHeight: ctx.network.height, 57 | assets: outputGranted.assets, 58 | additionalRegisters: registers([ 59 | [RegisterId.R4, new Int32Constant(params.deadline)], 60 | [RegisterId.R5, new SigmaPropConstant(params.redeemer)] 61 | ]) 62 | } 63 | const txr = { 64 | inputs: ctx.inputs, 65 | dataInputs: [], 66 | outputs: [lockOutput], 67 | changeAddress: ctx.changeAddress, 68 | feeNErgs: ctx.feeNErgs 69 | } 70 | 71 | const minNErgs = ctx.feeNErgs * 2n + MinBoxValue 72 | if (outputGranted.nErgs < minNErgs) 73 | return Promise.reject( 74 | new InsufficientInputs(`Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}`) 75 | ) 76 | else if (outputGranted.assets.length < 1) 77 | return Promise.reject(new InsufficientInputs(`No assets supplied for the lock`)) 78 | else return this.prover.sign(this.txAsm.assemble(txr, ctx.network)) 79 | } 80 | 81 | async withdrawTokens(params: WithdrawalParams, ctx: MinTransactionContext): Promise { 82 | const lockBox = await this.network.getOutput(params.boxId) 83 | if (lockBox) { 84 | const redeemerOutput = { 85 | value: lockBox.value - ctx.feeNErgs, 86 | ergoTree: ergoTreeFromAddress(params.address), 87 | creationHeight: ctx.network.height, 88 | assets: lockBox.assets, 89 | additionalRegisters: EmptyRegisters 90 | } 91 | const txr = { 92 | inputs: BoxSelection.safe(lockBox), 93 | dataInputs: [], 94 | outputs: [redeemerOutput], 95 | changeAddress: params.address, 96 | feeNErgs: ctx.feeNErgs 97 | } 98 | return this.prover.sign(this.txAsm.assemble(txr, ctx.network)) 99 | } else { 100 | return Promise.reject(new InsufficientInputs(`Output{id='${params.boxId}'} not found`)) 101 | } 102 | } 103 | 104 | async relockTokens(params: RelockParams, ctx: TransactionContext): Promise { 105 | const lockBox = await this.network.getOutput(params.boxId) 106 | if (lockBox) { 107 | const lock = this.parser.parseTokenLock(lockBox) 108 | if (lock) { 109 | const updatedInputs = ctx.inputs.addInput(lockBox) 110 | const outputGranted = updatedInputs.totalOutputWithoutChange 111 | const relockOutput = { 112 | value: outputGranted.nErgs - ctx.feeNErgs, 113 | ergoTree: lockBox.ergoTree, 114 | creationHeight: ctx.network.height, 115 | assets: lockBox.assets, 116 | additionalRegisters: registers([ 117 | [RegisterId.R4, new Int32Constant(params.updateDeadline ? params.updateDeadline : lock.deadline)], 118 | [ 119 | RegisterId.R5, 120 | new SigmaPropConstant(params.updateRedeemer ? params.updateRedeemer : lock.redeemer) 121 | ] 122 | ]) 123 | } 124 | const txr = { 125 | inputs: updatedInputs, 126 | dataInputs: [], 127 | outputs: [relockOutput], 128 | changeAddress: ctx.changeAddress, 129 | feeNErgs: ctx.feeNErgs 130 | } 131 | 132 | const minNErgs = ctx.feeNErgs * 2n + MinBoxValue 133 | if (outputGranted.nErgs < minNErgs) 134 | return Promise.reject( 135 | new InsufficientInputs( 136 | `Minimal amount of nERG required ${minNErgs}, given ${outputGranted.nErgs}` 137 | ) 138 | ) 139 | else return this.prover.sign(this.txAsm.assemble(txr, ctx.network)) 140 | } else { 141 | return Promise.reject(new InsufficientInputs(`Output{id='${params.boxId}'} is not a valid lock`)) 142 | } 143 | } else { 144 | return Promise.reject(new InsufficientInputs(`Output{id='${params.boxId}'} not found`)) 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/security/models.ts: -------------------------------------------------------------------------------- 1 | import {Address, BoxId, PublicKey} from "@ergolabs/ergo-sdk" 2 | 3 | export type LockParams = { 4 | readonly deadline: number 5 | readonly redeemer: PublicKey 6 | } 7 | 8 | export type WithdrawalParams = { 9 | readonly boxId: BoxId // locked box 10 | readonly address: Address 11 | } 12 | 13 | export type RelockParams = { 14 | readonly boxId: BoxId // locked box 15 | readonly updateRedeemer?: PublicKey 16 | readonly updateDeadline?: number 17 | } 18 | -------------------------------------------------------------------------------- /src/security/parsers/lockParser.spec.ts: -------------------------------------------------------------------------------- 1 | import {AugErgoBox, RustModule} from "@ergolabs/ergo-sdk" 2 | import test from "ava" 3 | import {mkLockParser} from "./lockParser" 4 | 5 | test.before(async () => { 6 | await RustModule.load(true) 7 | }) 8 | 9 | const parser = mkLockParser() 10 | 11 | test("parse valid lock", t => { 12 | const box = SampleOutputs[0] 13 | const r = parser.parseTokenLock(box) 14 | t.log(r) 15 | }) 16 | 17 | const SampleOutputsJson = `[ 18 | { 19 | "boxId": "06b5b7763855b03d8cef6972353fd2edac9c223f5cdae632201131f15b0d2a67", 20 | "transactionId": "8907b1b48abdbd42212d234fb96462c6786d5b38fb2b08bffcb623c940d0aaa7", 21 | "blockId": "31ccc813216205c1edda191fd53bbb507a8b608c33e7874f9c362501170e029c", 22 | "value": 2060000, 23 | "index": 0, 24 | "globalIndex": 12255797, 25 | "creationHeight": 669397, 26 | "settlementHeight": 669399, 27 | "ergoTree": "195e03040004000400d802d601b2a5730000d602e4c6a70404ea02e4c6a70508d19593c27201c2a7d802d603b2db63087201730100d604b2db6308a7730200eded92e4c6720104047202938c7203018c720401928c7203028c7204028f7202a3", 28 | "address": "XqM6yyAmxNgCcRzvutWwtdSvKqqaEtd4cZRsVvJu1xeu4y5T9tZexJpPf1XMMWCZdv8zVK1XUbmM5gjB9KzWXQCMEWJcdNas6HYJFYf47m63kU3xZMHjUA3vNKRZWEj8AvQ75YBUx", 29 | "assets": [ 30 | { 31 | "tokenId": "303f39026572bcb4060b51fafc93787a236bb243744babaa99fceb833d61e198", 32 | "index": 0, 33 | "amount": 5460202, 34 | "name": null, 35 | "decimals": null, 36 | "type": null 37 | } 38 | ], 39 | "additionalRegisters": { 40 | "R4": { 41 | "serializedValue": "04cae651", 42 | "sigmaType": "SInt", 43 | "renderedValue": "670117" 44 | }, 45 | "R5": { 46 | "serializedValue": "08cd03c6ad2949fb9f1a52c77abca7afa4d65fc46021b1f97c391c224f072e402b0a47", 47 | "sigmaType": "SSigmaProp", 48 | "renderedValue": "03c6ad2949fb9f1a52c77abca7afa4d65fc46021b1f97c391c224f072e402b0a47" 49 | } 50 | }, 51 | "spentTransactionId": null, 52 | "mainChain": true 53 | }, 54 | { 55 | "boxId": "33af2dde94337f4642b495ec59f0e6b16adf0d73b63e356010dc34eb9576399e", 56 | "transactionId": "cf0f776a93e0ddfa6c9e0bf80e5aff271092729ea5c61289578ecf6aafedf0a3", 57 | "blockId": "72114b8879c3e2e0fa7c581c5b8761b92094cd5a734c5422c7b56c88f8befac6", 58 | "value": 4237389984, 59 | "index": 1, 60 | "globalIndex": 12245721, 61 | "creationHeight": 506880, 62 | "settlementHeight": 669182, 63 | "ergoTree": "0008cd03c6ad2949fb9f1a52c77abca7afa4d65fc46021b1f97c391c224f072e402b0a47", 64 | "address": "9hyJcYmz9sQRqf5oTxHSkYG8KMPpEjJbXNfeLjqKfK2GeNgJSQD", 65 | "assets": [ 66 | { 67 | "tokenId": "03faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf04", 68 | "index": 0, 69 | "amount": 26706, 70 | "name": "SigUSD", 71 | "decimals": 2, 72 | "type": "EIP-004" 73 | }, 74 | { 75 | "tokenId": "e0588d273c8183865cff31b3bfa766bc7b178e2362b45497b67e79662e3615b7", 76 | "index": 1, 77 | "amount": 3326, 78 | "name": null, 79 | "decimals": null, 80 | "type": null 81 | }, 82 | { 83 | "tokenId": "f45c4f0d95ce1c64defa607d94717a9a30c00fdd44827504be20db19f4dce36f", 84 | "index": 2, 85 | "amount": 999999900, 86 | "name": "TERG", 87 | "decimals": 0, 88 | "type": "EIP-004" 89 | }, 90 | { 91 | "tokenId": "ef802b475c06189fdbf844153cdc1d449a5ba87cce13d11bb47b5a539f27f12b", 92 | "index": 3, 93 | "amount": 2001856802852070, 94 | "name": "WT_ERG", 95 | "decimals": 9, 96 | "type": "EIP-004" 97 | }, 98 | { 99 | "tokenId": "003bd19d0187117f130b62e1bcab0939929ff5c7709f843c5c4dd158949285d0", 100 | "index": 4, 101 | "amount": 7550, 102 | "name": "SigRSV", 103 | "decimals": 0, 104 | "type": "EIP-004" 105 | }, 106 | { 107 | "tokenId": "f302c6c042ada3566df8e67069f8ac76d10ce15889535141593c168f3c59e776", 108 | "index": 5, 109 | "amount": 1000000000, 110 | "name": "TUSD", 111 | "decimals": 0, 112 | "type": "EIP-004" 113 | }, 114 | { 115 | "tokenId": "fbbaac7337d051c10fc3da0ccb864f4d32d40027551e1c3ea3ce361f39b91e40", 116 | "index": 6, 117 | "amount": 1406432, 118 | "name": "kushti", 119 | "decimals": 0, 120 | "type": "EIP-004" 121 | }, 122 | { 123 | "tokenId": "36aba4b4a97b65be491cf9f5ca57b5408b0da8d0194f30ec8330d1e8946161c1", 124 | "index": 7, 125 | "amount": 4, 126 | "name": "Erdoge", 127 | "decimals": 0, 128 | "type": "EIP-004" 129 | }, 130 | { 131 | "tokenId": "303f39026572bcb4060b51fafc93787a236bb243744babaa99fceb833d61e198", 132 | "index": 8, 133 | "amount": 21000778, 134 | "name": null, 135 | "decimals": null, 136 | "type": null 137 | }, 138 | { 139 | "tokenId": "1c51c3a53abfe87e6db9a03c649e8360f255ffc4bd34303d30fc7db23ae551db", 140 | "index": 9, 141 | "amount": 484547687518, 142 | "name": null, 143 | "decimals": null, 144 | "type": null 145 | }, 146 | { 147 | "tokenId": "fa6326a26334f5e933b96470b53b45083374f71912b0d7597f00c2c7ebeb5da6", 148 | "index": 10, 149 | "amount": 43884, 150 | "name": null, 151 | "decimals": null, 152 | "type": null 153 | }, 154 | { 155 | "tokenId": "30974274078845f263b4f21787e33cc99e9ec19a17ad85a5bc6da2cca91c5a2e", 156 | "index": 11, 157 | "amount": 2860289360791101, 158 | "name": "WT_ADA", 159 | "decimals": 8, 160 | "type": "EIP-004" 161 | } 162 | ], 163 | "additionalRegisters": {}, 164 | "spentTransactionId": null, 165 | "mainChain": true 166 | }, 167 | { 168 | "boxId": "2ddb7fb4274d01dc3a2df06929318d31e42faadb7bb3d9d43fbcb072ad6c7bf4", 169 | "transactionId": "cf0f776a93e0ddfa6c9e0bf80e5aff271092729ea5c61289578ecf6aafedf0a3", 170 | "blockId": "72114b8879c3e2e0fa7c581c5b8761b92094cd5a734c5422c7b56c88f8befac6", 171 | "value": 2000000, 172 | "index": 2, 173 | "globalIndex": 12245722, 174 | "creationHeight": 506880, 175 | "settlementHeight": 669182, 176 | "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304", 177 | "address": "2iHkR7CWvD1R4j1yZg5bkeDRQavjAaVPeTDFGGLZduHyfWMuYpmhHocX8GJoaieTx78FntzJbCBVL6rf96ocJoZdmWBL2fci7NqWgAirppPQmZ7fN9V6z13Ay6brPriBKYqLp1bT2Fk4FkFLCfdPpe", 178 | "assets": [], 179 | "additionalRegisters": {}, 180 | "spentTransactionId": "c8722188488de4aa23ec646f919b0a55fd8c7a44fce75875d38eff7554708f01", 181 | "mainChain": true 182 | } 183 | ]` 184 | 185 | const SampleOutputs: AugErgoBox[] = JSON.parse(SampleOutputsJson) 186 | -------------------------------------------------------------------------------- /src/security/parsers/lockParser.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AssetAmount, 3 | AugErgoBox, 4 | deserializeConstant, 5 | Int32Constant, 6 | RegisterId, 7 | SigmaPropConstant 8 | } from "@ergolabs/ergo-sdk" 9 | import {TokenLock} from "../entities" 10 | 11 | export interface LockParser { 12 | parseTokenLock(box: AugErgoBox): TokenLock | undefined 13 | } 14 | 15 | export function mkLockParser(): LockParser { 16 | return new DefaultLockParser() 17 | } 18 | 19 | class DefaultLockParser implements LockParser { 20 | parseTokenLock(box: AugErgoBox): TokenLock | undefined { 21 | const r4 = box.additionalRegisters[RegisterId.R4] 22 | const r5 = box.additionalRegisters[RegisterId.R5] 23 | if (r4 && r5) { 24 | const lockedAsset = box.assets[0] 25 | const r4c = deserializeConstant(r4) 26 | const deadline = r4c instanceof Int32Constant ? r4c.value : undefined 27 | const r5c = deserializeConstant(r5) 28 | const redeemer = r5c instanceof SigmaPropConstant ? r5c.value : undefined 29 | return lockedAsset && deadline && redeemer 30 | ? { 31 | boxId: box.boxId, 32 | lockedAsset: AssetAmount.fromToken(lockedAsset), 33 | deadline, 34 | redeemer, 35 | active: !box.spentTransactionId 36 | } 37 | : undefined 38 | } else return undefined 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/security/services/locksHistory.ts: -------------------------------------------------------------------------------- 1 | import {Address, ErgoNetwork, publicKeyFromAddress} from "@ergolabs/ergo-sdk" 2 | import {TokenLockTemplateHash} from "../contracts/lockingTemplates" 3 | import {TokenLock} from "../entities" 4 | import {LockParser} from "../parsers/lockParser" 5 | 6 | export interface LocksHistory { 7 | /** Get locks by a given list of addresses. 8 | * @address - address to fetch operations by 9 | */ 10 | getAllByAddresses(addresses: Address[]): Promise 11 | } 12 | 13 | export function mkLocksHistory(network: ErgoNetwork, parser: LockParser): LocksHistory { 14 | return new DefaultLocksHistory(network, parser) 15 | } 16 | 17 | class DefaultLocksHistory implements LocksHistory { 18 | constructor(public readonly network: ErgoNetwork, public readonly parser: LockParser) {} 19 | async getAllByAddresses(addresses: Address[]): Promise { 20 | const ops: TokenLock[] = [] 21 | for (const pk of addresses.map(publicKeyFromAddress)) { 22 | if (pk) { 23 | const query = { 24 | ergoTreeTemplateHash: TokenLockTemplateHash, 25 | registers: { 26 | R5: pk 27 | } 28 | } 29 | const limit = 100 30 | let offset = 0 31 | // eslint-disable-next-line no-constant-condition 32 | while (true) { 33 | const [utxos, total] = await this.network.searchUnspentBoxes(query, {offset, limit}) 34 | for (const out of utxos) { 35 | const op = this.parser.parseTokenLock(out) 36 | if (op) ops.push(op) 37 | } 38 | if (offset < total) offset += limit 39 | else break 40 | } 41 | } 42 | } 43 | return ops 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/pools.ts: -------------------------------------------------------------------------------- 1 | import {ErgoNetwork, Paging, TokenId} from "@ergolabs/ergo-sdk" 2 | import {PoolContracts} from "../contracts/poolContracts" 3 | import {FromBox} from "../fromBox" 4 | import {PoolId} from "../lqmining/types" 5 | 6 | export interface Pools { 7 | /** Get a pool by the given pool `id`. 8 | */ 9 | get(poolId: PoolId): Promise 10 | 11 | /** Get all pools from the network. 12 | */ 13 | getAll(paging: Paging): Promise<[TPool[], number]> 14 | 15 | /** Get pools containing all of given tokens from the network. 16 | */ 17 | getByTokens(tokens: TokenId[], paging: Paging): Promise<[TPool[], number]> 18 | 19 | /** Get pools containing any of the given tokens from the network. 20 | */ 21 | getByTokensUnion(tokens: TokenId[], paging: Paging): Promise<[TPool[], number]> 22 | } 23 | 24 | export function makePools( 25 | network: ErgoNetwork, 26 | parser: FromBox, 27 | contracts: PoolContracts 28 | ): Pools { 29 | return new NetworkPools(network, parser, contracts) 30 | } 31 | 32 | class NetworkPools implements Pools { 33 | constructor( 34 | readonly network: ErgoNetwork, 35 | readonly parser: FromBox, 36 | readonly contracts: PoolContracts 37 | ) {} 38 | 39 | async get(poolId: PoolId): Promise { 40 | const boxes = await this.network.getUnspentByTokenId(poolId, {offset: 0, limit: 1}) 41 | if (boxes.length > 0) { 42 | const poolBox = boxes[0] 43 | return this.parser.from(poolBox) 44 | } 45 | return undefined 46 | } 47 | 48 | async getAll(paging: Paging): Promise<[TPool[], number]> { 49 | const [boxes, totalBoxes] = await this.network.getUnspentByErgoTree(this.contracts.poolTree, paging) 50 | const pools = this.parser.fromMany(boxes) 51 | const invalid = boxes.length - pools.length 52 | const total = totalBoxes - invalid 53 | return [pools, total] 54 | } 55 | 56 | async getByTokens(tokens: TokenId[], paging: Paging): Promise<[TPool[], number]> { 57 | const req = {ergoTreeTemplateHash: this.contracts.poolTemplateHash, assets: tokens} 58 | const [boxes, totalBoxes] = await this.network.searchUnspentBoxes(req, paging) 59 | const pools = this.parser.fromMany(boxes) 60 | const invalid = boxes.length - pools.length 61 | const total = totalBoxes - invalid 62 | return [pools, total] 63 | } 64 | 65 | async getByTokensUnion(tokens: TokenId[], paging: Paging): Promise<[TPool[], number]> { 66 | const req = {ergoTreeTemplateHash: this.contracts.poolTemplateHash, assets: tokens} 67 | const [boxes, totalBoxes] = await this.network.searchUnspentBoxesByTokensUnion(req, paging) 68 | const pools = this.parser.fromMany(boxes) 69 | const invalid = boxes.length - pools.length 70 | const total = totalBoxes - invalid 71 | return [pools, total] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import {TokenAmount, TokenId} from "@ergolabs/ergo-sdk" 2 | 3 | export type NativeExFee = bigint 4 | export type NativeExFeePerToken = number 5 | 6 | export type SpecExFee = TokenAmount 7 | export type SpecExFeePerToken = {tokenId: TokenId; amount: number} 8 | 9 | export type ExFee = T extends NativeExFeeType 10 | ? NativeExFee 11 | : SpecExFee 12 | export type ExFeePerToken = T extends NativeExFeeType 13 | ? NativeExFeePerToken 14 | : SpecExFeePerToken 15 | 16 | export interface NativeExFeeType { 17 | _N: unknown 18 | } 19 | 20 | export interface SpecExFeeType { 21 | _T: unknown 22 | } 23 | 24 | export type ExFeeType = NativeExFeeType | SpecExFeeType 25 | -------------------------------------------------------------------------------- /src/utils/arrays.ts: -------------------------------------------------------------------------------- 1 | export function evalFirst(arr: T[], fn: (t: T) => R | undefined): R | undefined { 2 | let maybeR: R | undefined = undefined 3 | for (const e of arr) { 4 | const res = fn(e) 5 | if (res) { 6 | maybeR = res 7 | break 8 | } 9 | } 10 | return maybeR 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/blake2b256.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import blake from "blakejs" 3 | import {HexString} from "@ergolabs/ergo-sdk" 4 | 5 | export class Blake2b256 { 6 | static hash(input: Uint8Array | HexString): Uint8Array { 7 | return blake.blake2b(input, undefined, 32) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/blocks.ts: -------------------------------------------------------------------------------- 1 | import {BlocktimeMillis} from "../constants" 2 | 3 | const HOUR_MILLIS: number = 60 * 60_000 4 | const DAY_MILLIS: number = 24 * HOUR_MILLIS 5 | const WEEK_MILLIS: number = 7 * DAY_MILLIS 6 | const MONTH_MILLIS: number = 4 * WEEK_MILLIS 7 | 8 | export function blocksToMillis(blocks: number): bigint { 9 | return BigInt(blocks) * BlocktimeMillis 10 | } 11 | 12 | export function millisToBlocks(millis: bigint): number { 13 | return Number(millis / BlocktimeMillis) 14 | } 15 | 16 | export function blocksToTimestamp(currentHeight: number, block: number) { 17 | return Date.now() + Number(blocksToMillis(block - currentHeight - 1)) 18 | } 19 | 20 | export function timestampToBlocks(currentHeight: number, timestamp: number) { 21 | return currentHeight + millisToBlocks(BigInt(timestamp - Date.now())) + 1 22 | } 23 | 24 | export function hoursCountToBlocks(hoursCount: number): number { 25 | return millisToBlocks(BigInt(hoursCount * HOUR_MILLIS)) 26 | } 27 | 28 | export function daysCountToBlocks(daysCount: number): number { 29 | return millisToBlocks(BigInt(daysCount * DAY_MILLIS)) 30 | } 31 | 32 | export function blocksToDaysCount(block: number): number { 33 | return Math.ceil(Number(blocksToMillis(block)) / DAY_MILLIS) 34 | } 35 | 36 | export function weeksCountToBlocks(weeksCount: number): number { 37 | return millisToBlocks(BigInt(weeksCount * WEEK_MILLIS)) 38 | } 39 | 40 | export function monthCountToBlocks(monthCount: number): number { 41 | return millisToBlocks(BigInt(monthCount * MONTH_MILLIS)) 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/contract.ts: -------------------------------------------------------------------------------- 1 | import {RustModule} from "@ergolabs/ergo-sdk" 2 | import { 3 | ErgoTreePrefixHex, 4 | SigmaFalseHex, 5 | SigmaPropConstPrefixHex, 6 | SigmaTrueHex 7 | } from "../amm/common/constants" 8 | import {fromHex} from "./hex" 9 | 10 | interface ConstantContract { 11 | serialize: () => any 12 | } 13 | 14 | export class _Int implements ConstantContract { 15 | readonly _INT: unknown 16 | 17 | constructor(public readonly value: number) {} 18 | 19 | serialize() { 20 | return RustModule.SigmaRust.Constant.from_i32(this.value) 21 | } 22 | } 23 | 24 | export class _Long { 25 | readonly _LONG: unknown 26 | 27 | constructor(public readonly value: bigint) {} 28 | 29 | serialize() { 30 | return RustModule.SigmaRust.Constant.from_i64(RustModule.SigmaRust.I64.from_str(this.value.toString())) 31 | } 32 | } 33 | 34 | export class _Bool { 35 | readonly _BOOL: unknown 36 | 37 | constructor(public readonly value: boolean) {} 38 | 39 | serialize() { 40 | return RustModule.SigmaRust.Constant.decode_from_base16(this.value ? SigmaTrueHex : SigmaFalseHex) 41 | } 42 | } 43 | 44 | export class _ProveDlog { 45 | readonly _PROVE_DLOG: unknown 46 | 47 | constructor(public readonly value: string) {} 48 | 49 | serialize() { 50 | return RustModule.SigmaRust.Constant.decode_from_base16(SigmaPropConstPrefixHex + this.value) 51 | } 52 | } 53 | 54 | export class _RedeemerBytes { 55 | readonly _REDEEMER_BYTES: unknown 56 | 57 | constructor(public readonly value: string) {} 58 | 59 | serialize() { 60 | return RustModule.SigmaRust.Constant.from_byte_array( 61 | fromHex(ErgoTreePrefixHex + SigmaPropConstPrefixHex + this.value) 62 | ) 63 | } 64 | } 65 | 66 | export class _Bytes { 67 | readonly _BYTES: unknown 68 | 69 | constructor(public readonly value: string) {} 70 | 71 | serialize() { 72 | return RustModule.SigmaRust.Constant.from_byte_array(fromHex(this.value)) 73 | } 74 | } 75 | 76 | export const Int = (value: number) => new _Int(value) 77 | 78 | export const Long = (value: bigint) => new _Long(value) 79 | 80 | export const Bool = (value: boolean) => new _Bool(value) 81 | 82 | export const ProveDlog = (value: string) => new _ProveDlog(value) 83 | 84 | export const RedeemerBytes = (value: string) => new _RedeemerBytes(value) 85 | 86 | export const Bytes = (value: string) => new _Bytes(value) 87 | 88 | export const Constants = { 89 | Int, 90 | Long, 91 | Bool, 92 | ProveDlog, 93 | RedeemerBytes, 94 | Bytes 95 | } 96 | 97 | type Constant = _Int | _Long | _Bool | _ProveDlog | _RedeemerBytes | _Bytes 98 | 99 | type ConstantDictionary = {[key: string]: Constant} 100 | 101 | type ContractMapping = { 102 | [key in keyof T]: [number, (...args: any[]) => T[key]] 103 | } 104 | 105 | export class _Contract { 106 | constructor(private ergoTreeSample: string, private mapping: ContractMapping) {} 107 | 108 | build(data: T): string { 109 | return Object.entries(data) 110 | .reduce((ergoTree, [constantName, constantValue]) => { 111 | try { 112 | return ergoTree.with_constant(this.mapping[constantName][0], constantValue.serialize()) 113 | } catch (e) { 114 | console.error(`problem with constant ${constantName}`) 115 | console.log(e) 116 | throw e 117 | } 118 | }, RustModule.SigmaRust.ErgoTree.from_base16_bytes(this.ergoTreeSample)) 119 | .to_base16_bytes() 120 | } 121 | } 122 | 123 | export const Contract = ( 124 | ergoTreeSample: string, 125 | mapping: ContractMapping 126 | ): _Contract => new _Contract(ergoTreeSample, mapping) 127 | -------------------------------------------------------------------------------- /src/utils/hex.ts: -------------------------------------------------------------------------------- 1 | import {HexString} from "@ergolabs/ergo-sdk" 2 | 3 | export function fromHex(s: HexString): Uint8Array { 4 | return Uint8Array.from(Buffer.from(s, "hex")) 5 | } 6 | 7 | export function toHex(arr: Uint8Array): HexString { 8 | return Buffer.from(arr).toString("hex") 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/json.ts: -------------------------------------------------------------------------------- 1 | import JSONBigInt from "json-bigint" 2 | 3 | export const JSONBI = JSONBigInt({useNativeBigInt: true}) 4 | -------------------------------------------------------------------------------- /src/utils/makeTarget.ts: -------------------------------------------------------------------------------- 1 | import {AssetAmount, isNative, OverallAmount} from "@ergolabs/ergo-sdk" 2 | 3 | export function makeTarget(assetsIn: AssetAmount[], minNErgForFee: bigint): OverallAmount { 4 | const nativeAsset = assetsIn.filter(i => isNative(i.asset))[0] 5 | const isNativePool = !!nativeAsset 6 | 7 | const totalNErgs = isNativePool ? nativeAsset.amount + minNErgForFee : minNErgForFee 8 | 9 | const assets = assetsIn.filter(i => !isNative(i.asset)).map(am => am.toToken()) 10 | 11 | return { 12 | nErgs: totalNErgs, 13 | assets 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/math.ts: -------------------------------------------------------------------------------- 1 | import {all, BigNumber, ConfigOptions, create, FormatOptions, MathJsStatic} from "mathjs" 2 | 3 | const mathConf: ConfigOptions = { 4 | epsilon: 1e-24, 5 | matrix: "Matrix", 6 | number: "BigNumber", 7 | precision: 64 8 | } 9 | 10 | const formatOptions: FormatOptions = { 11 | notation: "fixed" 12 | } 13 | 14 | export const math = create(all, mathConf) as Partial 15 | 16 | export function evaluate(expr: string): string { 17 | return math.format!(math.evaluate!(expr), formatOptions) 18 | } 19 | 20 | export function decimalToFractional(n: BigNumber | number): [bigint, bigint] { 21 | const fmtN = math.format!(n, formatOptions) 22 | const [whole, decimals = ""] = String(fmtN).split(".") 23 | const numDecimals = decimals.length 24 | const denominator = BigInt(evaluate(`10^${numDecimals}`)) 25 | const numerator = BigInt(whole) * denominator + BigInt(decimals) 26 | return [numerator, denominator] 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/notImplemented.ts: -------------------------------------------------------------------------------- 1 | export function notImplemented(args: any[] = []): never { 2 | args 3 | throw "An implementation is missing" 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/sqrt.ts: -------------------------------------------------------------------------------- 1 | export function sqrt(x: bigint): bigint { 2 | function go(n: bigint, x0: bigint): bigint { 3 | const x1 = (n / x0 + x0) >> 1n 4 | if (x0 === x1 || x0 === x1 - 1n) { 5 | return x0 6 | } 7 | return go(n, x1) 8 | } 9 | 10 | if (x < 0n) throw "Square root of negative number is not supported" 11 | else if (x < 2n) return x 12 | else return go(x, 1n) 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/utf8.ts: -------------------------------------------------------------------------------- 1 | export function stringToBytea(s: string): Uint8Array { 2 | return new Uint8Array(Buffer.from(s)) 3 | } 4 | -------------------------------------------------------------------------------- /src/validation/poolValidation.ts: -------------------------------------------------------------------------------- 1 | export const OK = "OK" 2 | 3 | export type OK = typeof OK 4 | export type ValidationErrors = string[] 5 | export type ValidationResult = OK | ValidationErrors 6 | 7 | export interface PoolValidation { 8 | /** Check whether the given pool is properly initialized. 9 | */ 10 | validate(pool: TPool): Promise 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2020", 5 | "outDir": "build/main", 6 | "rootDir": "src", 7 | "moduleResolution": "node", 8 | "module": "esnext", 9 | "declaration": true, 10 | "inlineSourceMap": true, 11 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 12 | "resolveJsonModule": true /* Include modules imported with .json extension. */, 13 | 14 | "strict": true /* Enable all strict type-checking options. */, 15 | 16 | /* Strict Type-Checking Options */ 17 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 18 | // "strictNullChecks": true /* Enable strict null checks. */, 19 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 20 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 21 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 22 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 23 | 24 | /* Additional Checks */ 25 | "noUnusedLocals": true /* Report errors on unused locals. */, 26 | "noUnusedParameters": true /* Report errors on unused parameters. */, 27 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 28 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 29 | 30 | /* Debugging Options */ 31 | "traceResolution": false /* Report module resolution log messages. */, 32 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 33 | "listFiles": false /* Print names of files part of the compilation. */, 34 | "pretty": true /* Stylize errors and messages using color and context. */, 35 | 36 | /* Experimental Options */ 37 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 38 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 39 | 40 | "lib": ["es2017", "es2019", "DOM.Iterable", "DOM", "esnext"], 41 | "types": ["node"], 42 | "typeRoots": ["node_modules/@types", "src/types"] 43 | }, 44 | "include": ["src/**/*.ts"], 45 | "exclude": ["node_modules/**"], 46 | "compileOnSave": false 47 | } 48 | --------------------------------------------------------------------------------