This commit is contained in:
AzizbekFayziyev 2025-07-01 21:07:46 +05:00
commit e62342e016
129 changed files with 15994 additions and 0 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
submodules/

61
.eslintrc.json Normal file
View file

@ -0,0 +1,61 @@
{
"env": {
"es2021": true,
"node": true
},
"extends": [
"airbnb-base",
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals",
"prettier"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "import"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"no-console": "off",
"no-void": "off",
"import/extensions": "off",
"no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"func-names": "off",
"consistent-return": "off",
"no-restricted-syntax": "off",
"@typescript-eslint/indent": ["error", "tab"],
"class-methods-use-this": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/no-explicit-any": "error",
"lines-between-class-members": "off",
"camelcase": "off",
"no-underscore-dangle": "off",
"no-shadow": "off",
"no-await-in-loop": "off",
"radix": "off",
"no-plusplus": "off",
"no-promise-executor-return": "off",
"import/no-duplicates": "off",
"import/prefer-default-export": "off",
"import/no-cycle": "off"
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".ts"]
}
}
}
}

33
.gitignore vendored Normal file
View file

@ -0,0 +1,33 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
.env
# vercel
.vercel

3
.husky/pre-commit Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
npx lint-staged

8
.prettierignore Normal file
View file

@ -0,0 +1,8 @@
node_modules
build
dist
coverage
.next
.env
*.lock
submodules/

5
.sequelizerc Normal file
View file

@ -0,0 +1,5 @@
const path = require('path');
module.exports = {
'config': path.resolve('config', 'config.cjs'),
};

38
README.md Normal file
View file

@ -0,0 +1,38 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

16
config/config.cjs Normal file
View file

@ -0,0 +1,16 @@
require('dotenv').config();
let config = {
username: process.env.PGUSER,
password: process.env.PGPASSWORD,
database: process.env.PGDATABASE,
host: process.env.PGHOST,
port: process.env.PGPORT,
dialect: 'postgres',
};
module.exports = {
development: config,
test: config,
production: config,
};

8
jsconfig.json Normal file
View file

@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

6
nodemon.json Normal file
View file

@ -0,0 +1,6 @@
{
"verbose": true,
"ignore": ["node_modules", ".next"],
"watch": ["src/**/*", "server.js"],
"ext": "js json ts tsx"
}

9849
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

58
package.json Normal file
View file

@ -0,0 +1,58 @@
{
"name": "zano-trade-backend",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "cross-env PORT=3000 nodemon --exec tsx ./src/server.ts",
"start": "cross-env NODE_ENV=production PORT=30289 tsx ./src/server.ts",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "husky"
},
"lint-staged": {
"*.ts": [
"prettier --write",
"eslint --fix"
]
},
"dependencies": {
"axios": "^1.4.0",
"big.js": "^6.2.1",
"crypto-js": "^4.1.1",
"decimal.js": "^10.4.3",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"jimp": "^0.22.8",
"jsonwebtoken": "^9.0.0",
"nanoid": "^4.0.1",
"node-fetch": "^3.3.1",
"nodemon": "^2.0.22",
"pg": "^8.10.0",
"sequelize": "^6.37.3",
"sha256": "^0.2.0",
"socket.io": "^4.6.1",
"sqlite3": "^5.1.7",
"ts-node": "^10.9.1",
"tsx": "^4.15.7",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@types/big.js": "^6.2.0",
"@types/crypto-js": "^4.1.1",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.2",
"@types/pg": "^8.10.2",
"@types/sha256": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"cross-env": "^7.0.3",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-import": "^2.31.0",
"husky": "^9.1.7",
"lint-staged": "^15.5.2",
"prettier": "3.5.3",
"sequelize-cli": "^6.6.2"
}
}

12
prettier.config.cjs Normal file
View file

@ -0,0 +1,12 @@
/** @type {import("prettier").Config} */
module.exports = {
semi: true,
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
tabWidth: 4,
useTabs: true,
bracketSpacing: true,
arrowParens: 'always',
endOfLine: 'lf',
};

11
public/currencies/all.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_983_1016)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M9 3L10.7634 6.57295L14.7063 7.1459L11.8532 9.92705L12.5267 13.8541L9 12L5.47329 13.8541L6.14683 9.92705L3.29366 7.1459L7.23664 6.57295L9 3Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_983_1016">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 458 B

11
public/currencies/btc.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_31_643)">
<path d="M17.7306 11.1773C16.5284 15.9987 11.6445 18.933 6.82192 17.7307C2.00135 16.5287 -0.933233 11.645 0.269445 6.8239C1.47107 2.00189 6.35499 -0.932582 11.1761 0.269433C15.9983 1.47145 18.9327 6.35566 17.7304 11.1774L17.7305 11.1773H17.7306Z" fill="#F7931A"/>
<path d="M12.9691 7.71772C13.1482 6.52002 12.2363 5.87621 10.9892 5.44672L11.3938 3.82433L10.4061 3.57824L10.0123 5.15792C9.75259 5.09316 9.48593 5.03215 9.2209 4.97166L9.61757 3.38155L8.63047 3.13547L8.22571 4.75733C8.01083 4.70842 7.79978 4.66007 7.59502 4.60913L7.59617 4.60402L6.23409 4.26393L5.97135 5.31875C5.97135 5.31875 6.70415 5.48671 6.6887 5.49705C7.08867 5.59686 7.161 5.8616 7.14899 6.07144L6.68818 7.91973C6.71572 7.92672 6.75144 7.93684 6.79086 7.95268C6.75791 7.9445 6.72285 7.93557 6.68646 7.92686L6.04055 10.516C5.99167 10.6375 5.8676 10.8199 5.58797 10.7506C5.59786 10.765 4.87008 10.5715 4.87008 10.5715L4.3797 11.702L5.66505 12.0224C5.90417 12.0823 6.13849 12.1451 6.36925 12.2041L5.96052 13.8451L6.94709 14.0912L7.35186 12.4676C7.62138 12.5407 7.88294 12.6082 8.13899 12.6718L7.73559 14.2878L8.72335 14.5338L9.13203 12.8959C10.8163 13.2146 12.0827 13.0861 12.6158 11.5629C13.0453 10.3365 12.5944 9.62914 11.7083 9.16785C12.3537 9.01902 12.8398 8.5946 12.9694 7.71785L12.9691 7.71763L12.9691 7.71772ZM10.7124 10.8818C10.4071 12.1082 8.34203 11.4453 7.6725 11.279L8.21489 9.10498C8.88438 9.27211 11.0314 9.60283 10.7124 10.8818H10.7124ZM11.0178 7.69995C10.7394 8.81548 9.02059 8.24874 8.46302 8.10977L8.95477 6.13804C9.51233 6.27701 11.3079 6.53638 11.0179 7.69995H11.0178Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_31_643">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

11
public/currencies/cad.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_356_923)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M8.68118 15V3H9.45057V15H8.68118ZM10.7219 6.96094C10.6844 6.58281 10.5233 6.28906 10.2387 6.07969C9.9541 5.87031 9.56785 5.76563 9.07995 5.76563C8.74843 5.76563 8.46851 5.8125 8.2402 5.90625C8.01188 5.99688 7.83674 6.12344 7.71476 6.28594C7.59592 6.44844 7.53649 6.63281 7.53649 6.83906C7.53024 7.01094 7.5662 7.16094 7.64439 7.28906C7.72571 7.41719 7.83674 7.52813 7.97748 7.62188C8.11822 7.7125 8.28085 7.79219 8.46538 7.86094C8.64991 7.92656 8.84694 7.98281 9.05649 8.02969L9.9197 8.23594C10.3388 8.32969 10.7235 8.45469 11.0738 8.61094C11.4241 8.76719 11.7274 8.95937 11.9839 9.1875C12.2404 9.41563 12.439 9.68438 12.5797 9.99375C12.7236 10.3031 12.7971 10.6578 12.8002 11.0578C12.7971 11.6453 12.6469 12.1547 12.3498 12.5859C12.0558 13.0141 11.6305 13.3469 11.0738 13.5844C10.5202 13.8188 9.85246 13.9359 9.07057 13.9359C8.29493 13.9359 7.61937 13.8172 7.0439 13.5797C6.47155 13.3422 6.02431 12.9906 5.70217 12.525C5.38316 12.0563 5.21583 11.4766 5.2002 10.7859H7.16587C7.18777 11.1078 7.28003 11.3766 7.44266 11.5922C7.60843 11.8047 7.82892 11.9656 8.10415 12.075C8.3825 12.1813 8.69682 12.2344 9.04711 12.2344C9.39114 12.2344 9.68983 12.1844 9.94316 12.0844C10.1996 11.9844 10.3982 11.8453 10.539 11.6672C10.6797 11.4891 10.7501 11.2844 10.7501 11.0531C10.7501 10.8375 10.686 10.6563 10.5577 10.5094C10.4326 10.3625 10.2481 10.2375 10.0041 10.1344C9.76332 10.0312 9.46777 9.9375 9.11748 9.85313L8.07131 9.59063C7.26127 9.39375 6.62168 9.08594 6.15254 8.66719C5.68341 8.24844 5.4504 7.68438 5.45353 6.975C5.4504 6.39375 5.60522 5.88594 5.91797 5.45156C6.23386 5.01719 6.66703 4.67813 7.21748 4.43438C7.76793 4.19063 8.39345 4.06875 9.09402 4.06875C9.80711 4.06875 10.4295 4.19063 10.9612 4.43438C11.496 4.67813 11.912 5.01719 12.2091 5.45156C12.5062 5.88594 12.6595 6.38906 12.6688 6.96094H10.7219Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_356_923">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

12
public/currencies/ct.svg Normal file
View file

@ -0,0 +1,12 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1048_3365)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M13.0003 5.00005C12.2092 4.20893 11.2013 3.67017 10.104 3.45191C9.00667 3.23364 7.86928 3.34566 6.83564 3.77381C5.80199 4.20196 4.91852 4.927 4.29695 5.85726C3.67537 6.78751 3.34361 7.88119 3.34361 9C3.34361 10.1188 3.67537 11.2125 4.29695 12.1427C4.91852 13.073 5.80199 13.798 6.83564 14.2262C7.86928 14.6543 9.00667 14.7664 10.104 14.5481C11.2013 14.3298 12.2092 13.7911 13.0003 13L11.2804 11.28C10.8294 11.7309 10.2549 12.038 9.62944 12.1624C9.00397 12.2868 8.35566 12.223 7.76648 11.9789C7.17731 11.7349 6.67373 11.3216 6.31943 10.7914C5.96513 10.2611 5.77603 9.63772 5.77603 9C5.77603 8.36228 5.96513 7.73888 6.31943 7.20864C6.67373 6.67839 7.17731 6.26512 7.76648 6.02107C8.35566 5.77703 9.00397 5.71317 9.62944 5.83759C10.2549 5.962 10.8294 6.26909 11.2804 6.72003L13.0003 5.00005Z" fill="white"/>
<path d="M13.2428 7.58572L14.4675 8.29282V9.70702L13.2428 10.4141L12.0181 9.70702V8.29282L13.2428 7.58572Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1048_3365">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

11
public/currencies/eur.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_356_930)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M10.7736 7.48384L10.3066 8.52471H4L4.38208 7.48384H10.7736ZM9.87264 9.47529L9.35849 10.5399H4L4.38208 9.47529H9.87264ZM12 4.88403L11.2453 6.50951C11.1195 6.42079 10.967 6.32414 10.7877 6.21958C10.6116 6.11185 10.4088 6.02155 10.1792 5.94867C9.95283 5.87262 9.70126 5.8346 9.42453 5.8346C8.98742 5.8346 8.60692 5.9455 8.28302 6.1673C7.95912 6.3891 7.70755 6.73447 7.5283 7.20342C7.3522 7.67237 7.26415 8.2744 7.26415 9.00951C7.26415 9.75095 7.3522 10.3546 7.5283 10.8203C7.70755 11.283 7.95912 11.6236 8.28302 11.8422C8.60692 12.0577 8.98742 12.1654 9.42453 12.1654C9.70126 12.1654 9.95283 12.129 10.1792 12.0561C10.4057 11.9832 10.6038 11.8961 10.7736 11.7947C10.9465 11.6933 11.0912 11.5998 11.2075 11.5143L11.9717 13.1397C11.6289 13.4249 11.2406 13.6404 10.8066 13.7861C10.3726 13.9287 9.91195 14 9.42453 14C8.60063 14 7.87107 13.8035 7.23585 13.4106C6.60377 13.0146 6.10849 12.4458 5.75 11.7044C5.39151 10.9598 5.21226 10.0615 5.21226 9.00951C5.21226 7.96071 5.39151 7.06242 5.75 6.31464C6.10849 5.56686 6.60377 4.99493 7.23585 4.59886C7.87107 4.19962 8.60063 4 9.42453 4C9.93082 4 10.3994 4.07605 10.8302 4.22814C11.261 4.38023 11.6509 4.59886 12 4.88403Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_356_930">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,12 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_35_1723)">
<path d="M8.99444 17.9827C13.9555 17.9827 17.9772 13.961 17.9772 8.99999C17.9772 4.03897 13.9555 0.0172729 8.99444 0.0172729C4.03342 0.0172729 0.0117188 4.03897 0.0117188 8.99999C0.0117188 13.961 4.03342 17.9827 8.99444 17.9827Z" fill="#FEFEFE"/>
<path d="M5.28336 12.942C5.49144 12.942 5.688 12.8261 5.78016 12.641L7.10424 10.1268H5.058C4.74624 10.1268 4.49712 9.87263 4.49712 9.56591V8.43839C4.49712 8.12591 4.75128 7.87751 5.058 7.87751H8.29512L10.8094 3.11471C10.908 2.92967 11.0988 2.81447 11.3062 2.81447H15.5434C13.9018 1.07999 11.5841 -0.000732422 9.00576 -0.000732422C4.03488 -1.24218e-05 0 4.02911 0 9.00575C0 10.4162 0.324 11.7576 0.9072 12.9477H5.28264L5.28336 12.942Z" fill="#9B1C2E"/>
<path d="M12.7165 5.05801C12.5085 5.05801 12.3119 5.17321 12.2197 5.35825L10.8964 7.87249H12.9426C13.2544 7.87249 13.5035 8.12665 13.5035 8.43337V9.56089C13.5035 9.87337 13.2493 10.1218 12.9426 10.1218H9.7055L7.19126 14.8846C7.09262 15.0696 6.90254 15.1848 6.69446 15.1848H2.45654C4.09814 16.9193 6.41582 18 8.99414 18C13.965 18 17.9999 13.9709 17.9999 8.99425C17.9999 7.58377 17.6759 6.24241 17.0927 5.05225H12.7173L12.7165 5.05801Z" fill="#9B1C2E"/>
</g>
<defs>
<clipPath id="clip0_35_1723">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

11
public/currencies/jpy.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_356_916)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M7.42505 4L9.49722 9.03906L8.06189 10.123L5.25 4H7.42505ZM8.52727 8.99023L10.575 4H12.75L9.92342 10.123L8.52727 8.99023ZM10.0214 8.73633V14H7.92472V8.73633H10.0214ZM12.0005 8.84375V10.0498H5.78886V8.84375H12.0005ZM12.0005 10.7773V11.9834H5.78886V10.7773H12.0005Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_356_916">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 578 B

View file

@ -0,0 +1,12 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="48" height="48" rx="24" fill="#1F8FEB" fill-opacity="0.15"/>
<g clip-path="url(#clip0_1914_2941)">
<path d="M33.5005 14.4999C31.6215 12.621 29.2276 11.3414 26.6214 10.823C24.0153 10.3046 21.3139 10.5707 18.859 11.5875C16.404 12.6044 14.3057 14.3264 12.8295 16.5358C11.3532 18.7452 10.5652 21.3428 10.5652 24C10.5652 26.6572 11.3532 29.2548 12.8295 31.4642C14.3057 33.6736 16.404 35.3956 18.859 36.4125C21.3139 37.4293 24.0153 37.6954 26.6214 37.177C29.2276 36.6586 31.6215 35.379 33.5005 33.5001L29.4154 29.415C28.3444 30.486 26.9799 31.2154 25.4944 31.5109C24.0089 31.8064 22.4691 31.6547 21.0698 31.0751C19.6705 30.4955 18.4744 29.5139 17.633 28.2546C16.7915 26.9952 16.3424 25.5146 16.3424 24C16.3424 22.4854 16.7915 21.0048 17.633 19.7454C18.4744 18.4861 19.6705 17.5045 21.0698 16.9249C22.4691 16.3453 24.0089 16.1936 25.4944 16.4891C26.9799 16.7846 28.3444 17.514 29.4154 18.585L33.5005 14.4999Z" fill="white"/>
<path d="M34.0764 20.6406L36.9852 22.32V25.6788L34.0764 27.3582L31.1676 25.6788V22.32L34.0764 20.6406Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1914_2941">
<rect width="28" height="28" fill="white" transform="translate(10 10)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,3 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25.0036 12.2123C25.451 9.22342 23.1735 7.61681 20.0593 6.54503L21.0695 2.49638L18.6029 1.88227L17.6194 5.82433C16.9709 5.66274 16.305 5.51047 15.6431 5.35953L16.6337 1.39145L14.1686 0.777344L13.1578 4.82468C12.6212 4.70261 12.0941 4.58196 11.5827 4.45484L11.5856 4.4421L8.18403 3.5934L7.52787 6.22569C7.52787 6.22569 9.35792 6.64482 9.31936 6.67062C10.3182 6.91971 10.4988 7.58036 10.4689 8.10401L9.31804 12.7164C9.38682 12.7338 9.47603 12.7591 9.57448 12.7986C9.49219 12.7782 9.40462 12.7559 9.31375 12.7342L7.7007 19.1954C7.57863 19.4986 7.26879 19.9537 6.57043 19.7809C6.59515 19.8167 4.77763 19.3339 4.77763 19.3339L3.55298 22.155L6.76293 22.9545C7.36009 23.1041 7.94528 23.2607 8.52157 23.4079L7.50084 27.503L9.96464 28.1171L10.9755 24.0655C11.6486 24.248 12.3018 24.4164 12.9412 24.5752L11.9338 28.6077L14.4006 29.2218L15.4212 25.1344C19.6273 25.9297 22.7901 25.6091 24.1213 21.8079C25.194 18.7474 24.0679 16.9822 21.855 15.831C23.4668 15.4597 24.6808 14.4005 25.0044 12.2126L25.0037 12.2121L25.0036 12.2123ZM19.3678 20.1082C18.6055 23.1687 13.4483 21.5143 11.7762 21.0994L13.1308 15.6742C14.8027 16.0912 20.1645 16.9165 19.3679 20.1082H19.3678ZM20.1307 12.1679C19.4353 14.9517 15.1429 13.5374 13.7504 13.1906L14.9785 8.27021C16.3709 8.617 20.8551 9.26426 20.1309 12.1679H20.1307Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,15 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1026_7129)">
<path d="M14.9971 0.777344L14.8076 1.42555V20.2335L14.9971 20.4238L23.6664 15.2633L14.9971 0.777344Z" fill="#EDEDED"/>
<path d="M14.9972 0.777344L6.32764 15.2633L14.9972 20.4238V11.295V0.777344Z" fill="white"/>
<path d="M14.9972 22.0758L14.8904 22.207V28.9066L14.9972 29.2206L23.6718 16.918L14.9972 22.0758Z" fill="#EDEDED"/>
<path d="M14.9972 29.2206V22.0758L6.32764 16.918L14.9972 29.2206Z" fill="white"/>
<path d="M14.9971 20.4238L23.6664 15.2632L14.9971 11.2949V20.4238Z" fill="#DCDCDC"/>
<path d="M6.32764 15.2632L14.9972 20.4238V11.2949L6.32764 15.2632Z" fill="#EDEDED"/>
</g>
<defs>
<clipPath id="clip0_1026_7129">
<rect width="28.4444" height="28.4444" fill="white" transform="translate(0.777588 0.777344)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 878 B

View file

@ -0,0 +1,4 @@
<svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.5196 0.851564H29.1924C30.7929 0.851527 32.1395 0.851496 33.2418 0.942042C34.3949 1.03675 35.4932 1.2426 36.5365 1.77703C38.1256 2.59109 39.4177 3.89005 40.2274 5.48773C40.759 6.5366 40.9637 7.6408 41.0579 8.80002C41.148 9.90826 41.148 11.2621 41.1479 12.8712V17.3271C41.148 18.9362 41.148 20.2901 41.0579 21.3983C40.9637 22.5575 40.759 23.6617 40.2274 24.7106C39.4177 26.3083 38.1256 27.6073 36.5365 28.4213C35.4932 28.9557 34.3949 29.1616 33.2418 29.2563C32.1395 29.3469 30.7928 29.3468 29.1923 29.3468H15.238C14.2578 29.3468 13.3733 28.7556 12.9941 27.8469C12.6148 26.9382 12.8148 25.8895 13.5015 25.1863L25.0249 13.3852L28.4981 16.8131L21.0372 24.4537H29.0948C30.8186 24.4537 31.965 24.4518 32.8455 24.3794C33.6975 24.3095 34.0841 24.1859 34.3269 24.0615C35.0002 23.7166 35.5477 23.1662 35.8908 22.4892C36.0145 22.2451 36.1374 21.8565 36.207 20.9999C36.279 20.1147 36.2809 18.9621 36.2809 17.2291V12.9692C36.2809 11.2363 36.279 10.0837 36.207 9.19848C36.1374 8.34185 36.0145 7.95327 35.8908 7.70916C35.5477 7.03218 35.0002 6.48177 34.3269 6.13683C34.0841 6.01245 33.6975 5.8889 32.8455 5.81891C31.965 5.74659 30.8186 5.74468 29.0948 5.74468H24.6178C22.8751 5.74468 21.7153 5.74661 20.8252 5.82009C19.9631 5.89125 19.5734 6.01689 19.3298 6.14283C18.6524 6.49298 18.1038 7.05131 17.7638 7.73658C17.6415 7.98304 17.5213 8.37632 17.461 9.24378C17.3988 10.1395 17.411 11.3055 17.4322 13.0573L17.4567 15.0693L12.59 15.129L12.5644 13.0183C12.5446 11.3911 12.528 10.023 12.6058 8.90286C12.6871 7.73185 12.8813 6.61501 13.4085 5.55245C14.2109 3.93521 15.5056 2.61755 17.1044 1.79118C18.1548 1.24824 19.2632 1.03944 20.4269 0.943381C21.54 0.851496 22.901 0.851527 24.5196 0.851564Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.15336 36.1801C10.0338 36.2524 11.1803 36.2543 12.904 36.2543H17.4562C19.0279 36.2543 20.0732 36.2528 20.8784 36.1923C21.6588 36.1337 22.0166 36.03 22.2407 35.9267C23.0194 35.5675 23.6441 34.9395 24.0013 34.1566C24.1042 33.9313 24.2073 33.5716 24.2656 32.787C24.3257 31.9775 24.3273 30.9266 24.3273 29.3464H29.1943L29.1943 29.4357C29.1944 30.9031 29.1944 32.1379 29.1191 33.1513C29.0405 34.2102 28.8699 35.2222 28.4251 36.1969C27.5821 38.0444 26.1078 39.5266 24.2701 40.3741C23.3006 40.8213 22.294 40.9928 21.2408 41.0719C20.2327 41.1475 19.0046 41.1475 17.545 41.1475L12.8065 41.1475C11.206 41.1475 9.85934 41.1475 8.75703 41.057C7.60398 40.9623 6.50566 40.7564 5.46238 40.222C3.87321 39.4079 2.58117 38.109 1.77145 36.5113C1.23987 35.4624 1.03511 34.3582 0.940907 33.199C0.850843 32.0908 0.850876 30.7369 0.850912 29.1278V24.6719C0.850876 23.0628 0.850843 21.7089 0.940907 20.6007C1.03511 19.4415 1.23987 18.3373 1.77145 17.2884C2.58117 15.6907 3.87321 14.3918 5.46238 13.5777C6.50566 13.0433 7.60398 12.8374 8.75703 12.7427C9.85936 12.6522 11.206 12.6522 12.8066 12.6522H26.7608C28.1048 12.6522 29.1943 13.7476 29.1943 15.0988C29.1943 16.45 28.1048 17.5454 26.7608 17.5454H12.904C11.1803 17.5454 10.0338 17.5473 9.15336 17.6196C8.3013 17.6896 7.91479 17.8131 7.67198 17.9375C6.9986 18.2824 6.45112 18.8329 6.10802 19.5098C5.9843 19.7539 5.86141 20.1425 5.7918 20.9992C5.71986 21.8843 5.71796 23.0369 5.71796 24.7699V29.0298C5.71796 30.7628 5.71986 31.9154 5.7918 32.8005C5.86141 33.6572 5.9843 34.0458 6.10802 34.2899C6.45112 34.9668 6.9986 35.5173 7.67198 35.8622C7.91479 35.9866 8.3013 36.1101 9.15336 36.1801Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,12 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1048_3365)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M13.0003 5.00005C12.2092 4.20893 11.2013 3.67017 10.104 3.45191C9.00667 3.23364 7.86928 3.34566 6.83564 3.77381C5.80199 4.20196 4.91852 4.927 4.29695 5.85726C3.67537 6.78751 3.34361 7.88119 3.34361 9C3.34361 10.1188 3.67537 11.2125 4.29695 12.1427C4.91852 13.073 5.80199 13.798 6.83564 14.2262C7.86928 14.6543 9.00667 14.7664 10.104 14.5481C11.2013 14.3298 12.2092 13.7911 13.0003 13L11.2804 11.28C10.8294 11.7309 10.2549 12.038 9.62944 12.1624C9.00397 12.2868 8.35566 12.223 7.76648 11.9789C7.17731 11.7349 6.67373 11.3216 6.31943 10.7914C5.96513 10.2611 5.77603 9.63772 5.77603 9C5.77603 8.36228 5.96513 7.73888 6.31943 7.20864C6.67373 6.67839 7.17731 6.26512 7.76648 6.02107C8.35566 5.77703 9.00397 5.71317 9.62944 5.83759C10.2549 5.962 10.8294 6.26909 11.2804 6.72003L13.0003 5.00005Z" fill="white"/>
<path d="M13.2428 7.58572L14.4675 8.29282V9.70702L13.2428 10.4141L12.0181 9.70702V8.29282L13.2428 7.58572Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1048_3365">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

11
public/currencies/usd.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_356_923)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M8.68118 15V3H9.45057V15H8.68118ZM10.7219 6.96094C10.6844 6.58281 10.5233 6.28906 10.2387 6.07969C9.9541 5.87031 9.56785 5.76563 9.07995 5.76563C8.74843 5.76563 8.46851 5.8125 8.2402 5.90625C8.01188 5.99688 7.83674 6.12344 7.71476 6.28594C7.59592 6.44844 7.53649 6.63281 7.53649 6.83906C7.53024 7.01094 7.5662 7.16094 7.64439 7.28906C7.72571 7.41719 7.83674 7.52813 7.97748 7.62188C8.11822 7.7125 8.28085 7.79219 8.46538 7.86094C8.64991 7.92656 8.84694 7.98281 9.05649 8.02969L9.9197 8.23594C10.3388 8.32969 10.7235 8.45469 11.0738 8.61094C11.4241 8.76719 11.7274 8.95937 11.9839 9.1875C12.2404 9.41563 12.439 9.68438 12.5797 9.99375C12.7236 10.3031 12.7971 10.6578 12.8002 11.0578C12.7971 11.6453 12.6469 12.1547 12.3498 12.5859C12.0558 13.0141 11.6305 13.3469 11.0738 13.5844C10.5202 13.8188 9.85246 13.9359 9.07057 13.9359C8.29493 13.9359 7.61937 13.8172 7.0439 13.5797C6.47155 13.3422 6.02431 12.9906 5.70217 12.525C5.38316 12.0563 5.21583 11.4766 5.2002 10.7859H7.16587C7.18777 11.1078 7.28003 11.3766 7.44266 11.5922C7.60843 11.8047 7.82892 11.9656 8.10415 12.075C8.3825 12.1813 8.69682 12.2344 9.04711 12.2344C9.39114 12.2344 9.68983 12.1844 9.94316 12.0844C10.1996 11.9844 10.3982 11.8453 10.539 11.6672C10.6797 11.4891 10.7501 11.2844 10.7501 11.0531C10.7501 10.8375 10.686 10.6563 10.5577 10.5094C10.4326 10.3625 10.2481 10.2375 10.0041 10.1344C9.76332 10.0312 9.46777 9.9375 9.11748 9.85313L8.07131 9.59063C7.26127 9.39375 6.62168 9.08594 6.15254 8.66719C5.68341 8.24844 5.4504 7.68438 5.45353 6.975C5.4504 6.39375 5.60522 5.88594 5.91797 5.45156C6.23386 5.01719 6.66703 4.67813 7.21748 4.43438C7.76793 4.19063 8.39345 4.06875 9.09402 4.06875C9.80711 4.06875 10.4295 4.19063 10.9612 4.43438C11.496 4.67813 11.912 5.01719 12.2091 5.45156C12.5062 5.88594 12.6595 6.38906 12.6688 6.96094H10.7219Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_356_923">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,11 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1048_3346)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M13.2204 7.82411C13.4091 6.56319 12.4483 5.8854 11.1345 5.43324L11.5607 3.72522L10.5201 3.46614L10.1052 5.1292C9.8316 5.06103 9.55066 4.99679 9.27143 4.93311L9.68934 3.25908L8.64937 3L8.22293 4.70747C7.99654 4.65597 7.77418 4.60507 7.55846 4.55144L7.55966 4.54607L6.12462 4.18802L5.84781 5.29852C5.84781 5.29852 6.61986 5.47534 6.60359 5.48623C7.02498 5.59131 7.10119 5.87002 7.08853 6.09094L6.60303 8.03678C6.63205 8.04414 6.66969 8.0548 6.71122 8.07147C6.6765 8.06285 6.63956 8.05345 6.60122 8.04428L5.92072 10.7701C5.86922 10.898 5.73851 11.09 5.44389 11.0171C5.45432 11.0322 4.68755 10.8285 4.68755 10.8285L4.1709 12.0187L5.5251 12.356C5.77702 12.4191 6.0239 12.4852 6.26702 12.5473L5.8364 14.2749L6.87582 14.534L7.30227 12.8247C7.58622 12.9017 7.86179 12.9727 8.13156 13.0397L7.70655 14.7409L8.74722 15L9.17779 13.2756C10.9523 13.6112 12.2865 13.4759 12.8482 11.8722C13.3007 10.5811 12.8256 9.83641 11.8921 9.35078C12.572 9.1941 13.0842 8.74727 13.2207 7.82425L13.2204 7.82402L13.2204 7.82411ZM10.8428 11.1552C10.5212 12.4463 8.34548 11.7484 7.64008 11.5734L8.21152 9.28459C8.91687 9.46054 11.1789 9.80872 10.8428 11.1552H10.8428ZM11.1646 7.8054C10.8712 8.97981 9.06038 8.38316 8.47295 8.23685L8.99104 6.16105C9.57847 6.30736 11.4702 6.58042 11.1647 7.8054H11.1646Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1048_3346">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,16 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1048_3354)">
<circle cx="9" cy="9" r="9" fill="#1F8FEB"/>
<path d="M8.99889 3L8.91895 3.27346V11.208L8.99889 11.2883L12.6562 9.11125L8.99889 3Z" fill="#EDEDED"/>
<path d="M8.99876 3L5.34131 9.11125L8.99876 11.2883V7.43712V3Z" fill="white"/>
<path d="M8.99867 11.9857L8.95361 12.041V14.8674L8.99867 14.9999L12.6583 9.80971L8.99867 11.9857Z" fill="#EDEDED"/>
<path d="M8.99876 14.9999V11.9857L5.34131 9.80971L8.99876 14.9999Z" fill="white"/>
<path d="M8.99902 11.2884L12.6564 9.11129L8.99902 7.43716V11.2884Z" fill="#DCDCDC"/>
<path d="M5.34131 9.11129L8.99876 11.2884V7.43716L5.34131 9.11129Z" fill="#EDEDED"/>
</g>
<defs>
<clipPath id="clip0_1048_3354">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 846 B

12
public/currencies/xmr.svg Normal file
View file

@ -0,0 +1,12 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_35_1703)">
<path d="M17.9977 8.99911C17.9977 13.969 13.9691 17.9982 8.99883 17.9982C4.02858 17.9982 0 13.969 0 8.99911C0 4.0292 4.02868 0 8.99883 0C13.969 0 17.9977 4.02882 17.9977 8.99911Z" fill="white"/>
<path d="M8.99885 0C4.03038 0 -0.00534107 4.0349 0.000983987 8.99883C0.00222983 9.99195 0.160596 10.9473 0.457825 11.8409H3.15019V4.27061L8.99885 10.1192L14.8472 4.27061V11.841H17.5402C17.8378 10.9475 17.9954 9.99215 17.9972 8.99897C18.0056 4.02987 13.9679 0.00119793 8.99885 0.00119793V0Z" fill="#F26822"/>
<path d="M7.65382 11.4638L5.10142 8.91125V13.6749H3.15L1.30859 13.6752C2.88808 16.2666 5.7426 18 8.99885 18C12.2551 18 15.1098 16.2662 16.6895 13.6748H12.8957V8.91125L10.3432 11.4638L8.99856 12.8084L7.65392 11.4638H7.65382Z" fill="#4D4D4D"/>
</g>
<defs>
<clipPath id="clip0_35_1703">
<rect width="17.9981" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 995 B

View file

@ -0,0 +1,41 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_30_630)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5936 6.55123e-07H12.6717C13.3835 -1.56574e-05 13.9824 -2.96388e-05 14.4726 0.0404162C14.9854 0.0827233 15.4738 0.174675 15.9378 0.413398C16.6445 0.777032 17.2191 1.35727 17.5792 2.07094C17.8156 2.53946 17.9067 3.0327 17.9486 3.55051C17.9886 4.04555 17.9886 4.6503 17.9886 5.36909V7.35949C17.9886 8.07827 17.9886 8.68302 17.9486 9.17806C17.9067 9.69588 17.8156 10.1891 17.5792 10.6576C17.2191 11.3713 16.6445 11.9515 15.9378 12.3152C15.4738 12.5539 14.9854 12.6458 14.4726 12.6882C13.9824 12.7286 13.3835 12.7286 12.6717 12.7286H6.46591C6.03 12.7286 5.63664 12.4645 5.46797 12.0586C5.2993 11.6527 5.38824 11.1842 5.69362 10.8701L10.8184 5.59868L12.3629 7.12989L9.04494 10.5429H12.6283C13.3949 10.5429 13.9048 10.542 14.2963 10.5097C14.6753 10.4784 14.8472 10.4232 14.9551 10.3677C15.2546 10.2136 15.4981 9.96774 15.6507 9.66534C15.7057 9.5563 15.7603 9.38272 15.7913 9.00008C15.8233 8.60467 15.8241 8.08982 15.8241 7.31572V5.41286C15.8241 4.63875 15.8233 4.1239 15.7913 3.7285C15.7603 3.34585 15.7057 3.17227 15.6507 3.06323C15.4981 2.76083 15.2546 2.51497 14.9551 2.36088C14.8472 2.30532 14.6753 2.25013 14.2963 2.21887C13.9048 2.18657 13.3949 2.18572 12.6283 2.18572H10.6373C9.86231 2.18572 9.34651 2.18657 8.95064 2.2194C8.56727 2.25118 8.39397 2.30731 8.28562 2.36356C7.98435 2.51997 7.74037 2.76938 7.58916 3.07548C7.53478 3.18557 7.48131 3.36124 7.45451 3.74873C7.42684 4.14886 7.43228 4.66969 7.44173 5.45221L7.45258 6.35096L5.28826 6.37761L5.27687 5.43476C5.26808 4.70793 5.26068 4.09679 5.29529 3.59644C5.33147 3.07337 5.41781 2.57448 5.65227 2.09985C6.00912 1.37744 6.58493 0.788853 7.29591 0.41972C7.76305 0.177191 8.25599 0.0839239 8.77351 0.0410147C9.26854 -2.97658e-05 9.87379 -1.57868e-05 10.5936 6.55123e-07Z" fill="url(#paint0_linear_30_630)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5936 6.55123e-07H12.6717C13.3835 -1.56574e-05 13.9824 -2.96388e-05 14.4726 0.0404162C14.9854 0.0827233 15.4738 0.174675 15.9378 0.413398C16.6445 0.777032 17.2191 1.35727 17.5792 2.07094C17.8156 2.53946 17.9067 3.0327 17.9486 3.55051C17.9886 4.04555 17.9886 4.6503 17.9886 5.36909V7.35949C17.9886 8.07827 17.9886 8.68302 17.9486 9.17806C17.9067 9.69588 17.8156 10.1891 17.5792 10.6576C17.2191 11.3713 16.6445 11.9515 15.9378 12.3152C15.4738 12.5539 14.9854 12.6458 14.4726 12.6882C13.9824 12.7286 13.3835 12.7286 12.6717 12.7286H6.46591C6.03 12.7286 5.63664 12.4645 5.46797 12.0586C5.2993 11.6527 5.38824 11.1842 5.69362 10.8701L10.8184 5.59868L12.3629 7.12989L9.04494 10.5429H12.6283C13.3949 10.5429 13.9048 10.542 14.2963 10.5097C14.6753 10.4784 14.8472 10.4232 14.9551 10.3677C15.2546 10.2136 15.4981 9.96774 15.6507 9.66534C15.7057 9.5563 15.7603 9.38272 15.7913 9.00008C15.8233 8.60467 15.8241 8.08982 15.8241 7.31572V5.41286C15.8241 4.63875 15.8233 4.1239 15.7913 3.7285C15.7603 3.34585 15.7057 3.17227 15.6507 3.06323C15.4981 2.76083 15.2546 2.51497 14.9551 2.36088C14.8472 2.30532 14.6753 2.25013 14.2963 2.21887C13.9048 2.18657 13.3949 2.18572 12.6283 2.18572H10.6373C9.86231 2.18572 9.34651 2.18657 8.95064 2.2194C8.56727 2.25118 8.39397 2.30731 8.28562 2.36356C7.98435 2.51997 7.74037 2.76938 7.58916 3.07548C7.53478 3.18557 7.48131 3.36124 7.45451 3.74873C7.42684 4.14886 7.43228 4.66969 7.44173 5.45221L7.45258 6.35096L5.28826 6.37761L5.27687 5.43476C5.26808 4.70793 5.26068 4.09679 5.29529 3.59644C5.33147 3.07337 5.41781 2.57448 5.65227 2.09985C6.00912 1.37744 6.58493 0.788853 7.29591 0.41972C7.76305 0.177191 8.25599 0.0839239 8.77351 0.0410147C9.26854 -2.97658e-05 9.87379 -1.57868e-05 10.5936 6.55123e-07Z" fill="url(#paint1_radial_30_630)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5936 6.55123e-07H12.6717C13.3835 -1.56574e-05 13.9824 -2.96388e-05 14.4726 0.0404162C14.9854 0.0827233 15.4738 0.174675 15.9378 0.413398C16.6445 0.777032 17.2191 1.35727 17.5792 2.07094C17.8156 2.53946 17.9067 3.0327 17.9486 3.55051C17.9886 4.04555 17.9886 4.6503 17.9886 5.36909V7.35949C17.9886 8.07827 17.9886 8.68302 17.9486 9.17806C17.9067 9.69588 17.8156 10.1891 17.5792 10.6576C17.2191 11.3713 16.6445 11.9515 15.9378 12.3152C15.4738 12.5539 14.9854 12.6458 14.4726 12.6882C13.9824 12.7286 13.3835 12.7286 12.6717 12.7286H6.46591C6.03 12.7286 5.63664 12.4645 5.46797 12.0586C5.2993 11.6527 5.38824 11.1842 5.69362 10.8701L10.8184 5.59868L12.3629 7.12989L9.04494 10.5429H12.6283C13.3949 10.5429 13.9048 10.542 14.2963 10.5097C14.6753 10.4784 14.8472 10.4232 14.9551 10.3677C15.2546 10.2136 15.4981 9.96774 15.6507 9.66534C15.7057 9.5563 15.7603 9.38272 15.7913 9.00008C15.8233 8.60467 15.8241 8.08982 15.8241 7.31572V5.41286C15.8241 4.63875 15.8233 4.1239 15.7913 3.7285C15.7603 3.34585 15.7057 3.17227 15.6507 3.06323C15.4981 2.76083 15.2546 2.51497 14.9551 2.36088C14.8472 2.30532 14.6753 2.25013 14.2963 2.21887C13.9048 2.18657 13.3949 2.18572 12.6283 2.18572H10.6373C9.86231 2.18572 9.34651 2.18657 8.95064 2.2194C8.56727 2.25118 8.39397 2.30731 8.28562 2.36356C7.98435 2.51997 7.74037 2.76938 7.58916 3.07548C7.53478 3.18557 7.48131 3.36124 7.45451 3.74873C7.42684 4.14886 7.43228 4.66969 7.44173 5.45221L7.45258 6.35096L5.28826 6.37761L5.27687 5.43476C5.26808 4.70793 5.26068 4.09679 5.29529 3.59644C5.33147 3.07337 5.41781 2.57448 5.65227 2.09985C6.00912 1.37744 6.58493 0.788853 7.29591 0.41972C7.76305 0.177191 8.25599 0.0839239 8.77351 0.0410147C9.26854 -2.97658e-05 9.87379 -1.57868e-05 10.5936 6.55123e-07Z" fill="url(#paint2_radial_30_630)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5936 6.55123e-07H12.6717C13.3835 -1.56574e-05 13.9824 -2.96388e-05 14.4726 0.0404162C14.9854 0.0827233 15.4738 0.174675 15.9378 0.413398C16.6445 0.777032 17.2191 1.35727 17.5792 2.07094C17.8156 2.53946 17.9067 3.0327 17.9486 3.55051C17.9886 4.04555 17.9886 4.6503 17.9886 5.36909V7.35949C17.9886 8.07827 17.9886 8.68302 17.9486 9.17806C17.9067 9.69588 17.8156 10.1891 17.5792 10.6576C17.2191 11.3713 16.6445 11.9515 15.9378 12.3152C15.4738 12.5539 14.9854 12.6458 14.4726 12.6882C13.9824 12.7286 13.3835 12.7286 12.6717 12.7286H6.46591C6.03 12.7286 5.63664 12.4645 5.46797 12.0586C5.2993 11.6527 5.38824 11.1842 5.69362 10.8701L10.8184 5.59868L12.3629 7.12989L9.04494 10.5429H12.6283C13.3949 10.5429 13.9048 10.542 14.2963 10.5097C14.6753 10.4784 14.8472 10.4232 14.9551 10.3677C15.2546 10.2136 15.4981 9.96774 15.6507 9.66534C15.7057 9.5563 15.7603 9.38272 15.7913 9.00008C15.8233 8.60467 15.8241 8.08982 15.8241 7.31572V5.41286C15.8241 4.63875 15.8233 4.1239 15.7913 3.7285C15.7603 3.34585 15.7057 3.17227 15.6507 3.06323C15.4981 2.76083 15.2546 2.51497 14.9551 2.36088C14.8472 2.30532 14.6753 2.25013 14.2963 2.21887C13.9048 2.18657 13.3949 2.18572 12.6283 2.18572H10.6373C9.86231 2.18572 9.34651 2.18657 8.95064 2.2194C8.56727 2.25118 8.39397 2.30731 8.28562 2.36356C7.98435 2.51997 7.74037 2.76938 7.58916 3.07548C7.53478 3.18557 7.48131 3.36124 7.45451 3.74873C7.42684 4.14886 7.43228 4.66969 7.44173 5.45221L7.45258 6.35096L5.28826 6.37761L5.27687 5.43476C5.26808 4.70793 5.26068 4.09679 5.29529 3.59644C5.33147 3.07337 5.41781 2.57448 5.65227 2.09985C6.00912 1.37744 6.58493 0.788853 7.29591 0.41972C7.76305 0.177191 8.25599 0.0839239 8.77351 0.0410147C9.26854 -2.97658e-05 9.87379 -1.57868e-05 10.5936 6.55123e-07Z" fill="url(#paint3_radial_30_630)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75997 15.7811C4.15154 15.8134 4.66139 15.8143 5.42797 15.8143H7.45241C8.15141 15.8143 8.61627 15.8136 8.97437 15.7866C9.32141 15.7604 9.48056 15.7141 9.58022 15.6679C9.92652 15.5075 10.2043 15.2269 10.3632 14.8773C10.4089 14.7766 10.4548 14.6159 10.4807 14.2655C10.5074 13.9039 10.5082 13.4344 10.5082 12.7286H12.6726L12.6726 12.7685C12.6727 13.4239 12.6727 13.9755 12.6392 14.4282C12.6042 14.9012 12.5284 15.3532 12.3306 15.7886C11.9556 16.6139 11.3 17.276 10.4827 17.6546C10.0516 17.8543 9.60392 17.9309 9.13551 17.9662C8.68721 18 8.14102 18 7.4919 18L5.3846 18C4.67281 18 4.07394 18 3.58371 17.9596C3.07093 17.9173 2.58248 17.8253 2.11851 17.5866C1.41177 17.223 0.837174 16.6427 0.477072 15.9291C0.240667 15.4605 0.14961 14.9673 0.107713 14.4495C0.0676594 13.9544 0.0676737 13.3497 0.0676899 12.6309V10.6405C0.0676737 9.92173 0.0676594 9.31697 0.107713 8.82193C0.14961 8.30412 0.240668 7.81088 0.477072 7.34236C0.837174 6.62869 1.41177 6.04846 2.11851 5.68482C2.58248 5.4461 3.07093 5.35415 3.58372 5.31184C4.07395 5.27139 4.67283 5.27141 5.38463 5.27143H11.5904C12.1881 5.27143 12.6726 5.76071 12.6726 6.36428C12.6726 6.96785 12.1881 7.45714 11.5904 7.45714H5.42798C4.66139 7.45714 4.15154 7.45799 3.75997 7.4903C3.38104 7.52156 3.20915 7.57675 3.10117 7.63231C2.8017 7.78639 2.55823 8.03225 2.40564 8.33465C2.35062 8.4437 2.29597 8.61727 2.26501 8.99992C2.23302 9.39533 2.23218 9.91018 2.23218 10.6843V12.5871C2.23218 13.3612 2.23302 13.8761 2.26501 14.2715C2.29597 14.6541 2.35062 14.8277 2.40564 14.9368C2.55823 15.2392 2.8017 15.485 3.10117 15.6391C3.20915 15.6947 3.38104 15.7499 3.75997 15.7811Z" fill="url(#paint4_linear_30_630)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75997 15.7811C4.15154 15.8134 4.66139 15.8143 5.42797 15.8143H7.45241C8.15141 15.8143 8.61627 15.8136 8.97437 15.7866C9.32141 15.7604 9.48056 15.7141 9.58022 15.6679C9.92652 15.5075 10.2043 15.2269 10.3632 14.8773C10.4089 14.7766 10.4548 14.6159 10.4807 14.2655C10.5074 13.9039 10.5082 13.4344 10.5082 12.7286H12.6726L12.6726 12.7685C12.6727 13.4239 12.6727 13.9755 12.6392 14.4282C12.6042 14.9012 12.5284 15.3532 12.3306 15.7886C11.9556 16.6139 11.3 17.276 10.4827 17.6546C10.0516 17.8543 9.60392 17.9309 9.13551 17.9662C8.68721 18 8.14102 18 7.4919 18L5.3846 18C4.67281 18 4.07394 18 3.58371 17.9596C3.07093 17.9173 2.58248 17.8253 2.11851 17.5866C1.41177 17.223 0.837174 16.6427 0.477072 15.9291C0.240667 15.4605 0.14961 14.9673 0.107713 14.4495C0.0676594 13.9544 0.0676737 13.3497 0.0676899 12.6309V10.6405C0.0676737 9.92173 0.0676594 9.31697 0.107713 8.82193C0.14961 8.30412 0.240668 7.81088 0.477072 7.34236C0.837174 6.62869 1.41177 6.04846 2.11851 5.68482C2.58248 5.4461 3.07093 5.35415 3.58372 5.31184C4.07395 5.27139 4.67283 5.27141 5.38463 5.27143H11.5904C12.1881 5.27143 12.6726 5.76071 12.6726 6.36428C12.6726 6.96785 12.1881 7.45714 11.5904 7.45714H5.42798C4.66139 7.45714 4.15154 7.45799 3.75997 7.4903C3.38104 7.52156 3.20915 7.57675 3.10117 7.63231C2.8017 7.78639 2.55823 8.03225 2.40564 8.33465C2.35062 8.4437 2.29597 8.61727 2.26501 8.99992C2.23302 9.39533 2.23218 9.91018 2.23218 10.6843V12.5871C2.23218 13.3612 2.23302 13.8761 2.26501 14.2715C2.29597 14.6541 2.35062 14.8277 2.40564 14.9368C2.55823 15.2392 2.8017 15.485 3.10117 15.6391C3.20915 15.6947 3.38104 15.7499 3.75997 15.7811Z" fill="url(#paint5_radial_30_630)"/>
</g>
<defs>
<linearGradient id="paint0_linear_30_630" x1="8.60677" y1="12.6297" x2="8.75136" y2="-0.016212" gradientUnits="userSpaceOnUse">
<stop offset="0.43118" stop-color="#498FFD"/>
<stop offset="1" stop-color="#16D1D6"/>
</linearGradient>
<radialGradient id="paint1_radial_30_630" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(5.11938 3.85385) rotate(-15.5605) scale(8.17868 8.16021)">
<stop stop-color="#18CFD7"/>
<stop offset="1" stop-color="#18CFD7" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint2_radial_30_630" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(9.8984 7.46745) rotate(152.373) scale(3.06146 3.05526)">
<stop stop-color="#4990FE"/>
<stop offset="0.353962" stop-color="#4990FE"/>
<stop offset="1" stop-color="#4990FE" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint3_radial_30_630" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(6.15268 11.5973) rotate(-45.9158) scale(5.56964 5.56109)">
<stop stop-color="#4990FE"/>
<stop offset="0.405688" stop-color="#4990FE"/>
<stop offset="1" stop-color="#4990FE" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint4_linear_30_630" x1="12.7397" y1="18.0501" x2="12.7862" y2="5.14458" gradientUnits="userSpaceOnUse">
<stop stop-color="#2950FF"/>
<stop offset="0.821717" stop-color="#498FFD"/>
</linearGradient>
<radialGradient id="paint5_radial_30_630" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(12.7397 12.6297) rotate(129.312) scale(5.50455 5.44751)">
<stop offset="0.337169" stop-color="#2950FF"/>
<stop offset="0.792226" stop-color="#2950FF" stop-opacity="0"/>
</radialGradient>
<clipPath id="clip0_30_630">
<rect width="18" height="18" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
public/social-banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

16
public/ui/featured.svg Normal file
View file

@ -0,0 +1,16 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#D9D9D9"/>
<circle cx="8" cy="8" r="8" fill="url(#paint0_radial_3027_8265)"/>
<g clip-path="url(#clip0_3027_8265)">
<path d="M8 3.25L9.46946 6.22746L12.7553 6.70492L10.3776 9.02254L10.9389 12.2951L8 10.75L5.06107 12.2951L5.62236 9.02254L3.24472 6.70492L6.53054 6.22746L8 3.25Z" fill="white"/>
</g>
<defs>
<radialGradient id="paint0_radial_3027_8265" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(45) scale(22.6274 39.2097)">
<stop stop-color="#A366FF"/>
<stop offset="1" stop-color="#601FFF"/>
</radialGradient>
<clipPath id="clip0_3027_8265">
<rect width="10" height="10" fill="white" transform="translate(3 3)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 803 B

11
public/ui/whitelisted.svg Normal file
View file

@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" fill="#1F8FEB"/>
<circle cx="8" cy="8" r="8" fill="url(#paint0_radial_2734_8770)"/>
<path d="M4.25 8L6.75 10.5L11.75 5.5" stroke="white" stroke-width="1.5"/>
<defs>
<radialGradient id="paint0_radial_2734_8770" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(45) scale(22.6274 28.7635)">
<stop stop-color="#16D1D6"/>
<stop offset="1" stop-color="#274CFF"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 542 B

91
shared/utils.ts Normal file
View file

@ -0,0 +1,91 @@
import Decimal from 'decimal.js';
export function validateTokensInput(input: string | number, decimal_point = 12) {
let inputVal = input;
if (typeof inputVal === 'number') {
inputVal = inputVal.toString();
}
if (inputVal === '') {
return {
valid: false,
error: 'Invalid input',
};
}
inputVal = inputVal.replace(/[^0-9.,]/g, '');
const MAX_NUMBER = new Decimal(2).pow(64).minus(1);
if (decimal_point < 0 || decimal_point > 18) {
return {
valid: false,
error: 'Invalid decimal point',
};
}
const dotInput = inputVal.replace(/,/g, '');
const decimalDevider = new Decimal(10).pow(decimal_point);
const maxAllowedNumber = MAX_NUMBER.div(decimalDevider);
const minAllowedNumber = new Decimal(1).div(decimalDevider);
const rounded = (() => {
if (dotInput.replace('.', '').length > 20) {
const decimalParts = dotInput.split('.');
if (decimalParts.length === 2 && decimalParts[1].length > 1) {
const beforeDotLength = decimalParts[0].length;
const roundedInput = new Decimal(dotInput).toFixed(
Math.max(20 - beforeDotLength, 0),
);
if (roundedInput.replace(/./g, '').length <= 20) {
return roundedInput;
}
}
return false;
}
return dotInput;
})();
const decimalsAmount = dotInput.split('.')[1]?.length || 0;
if (decimalsAmount > decimal_point) {
return {
valid: false,
error: 'Invalid amount - too many decimal points',
};
}
if (rounded === false) {
return {
valid: false,
error: 'Invalid amount - number is too big or has too many decimal points',
};
}
const dotInputDecimal = new Decimal(rounded);
if (dotInputDecimal.gt(maxAllowedNumber)) {
return {
valid: false,
error: 'Invalid amount - number is too big',
};
}
if (dotInputDecimal.lt(minAllowedNumber)) {
return {
valid: false,
error: 'Invalid amount - number is too small',
};
}
return {
valid: true,
};
}

View file

@ -0,0 +1,52 @@
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import { Request, Response } from 'express';
import AuthData from '@/interfaces/bodies/user/AuthData.js';
import validateWallet from '../methods/validateWallet.js';
import userModel from '../models/User.js';
dotenv.config();
class AuthController {
async auth(req: Request, res: Response) {
try {
const userData: AuthData = req.body.data;
const { neverExpires } = req.body;
const { address, alias, signature, message } = userData;
if (!address || !alias || !signature || !message) {
return res.status(400).send({ success: false, data: 'Invalid auth data' });
}
const dataValid = !!(
userData &&
userData.address &&
alias &&
(await validateWallet(userData))
);
if (!dataValid) {
return res.status(400).send({ success: false, data: 'Invalid auth data' });
}
const success = await userModel.add(userData);
if (success) {
const token = jwt.sign(
{ ...userData },
process.env.JWT_SECRET || '',
neverExpires ? undefined : { expiresIn: '24h' },
);
res.status(200).send({ success, data: token });
} else {
res.status(500).send({ success, data: 'Internal error' });
}
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
}
const authController = new AuthController();
export default authController;

View file

@ -0,0 +1,144 @@
import { Request, Response } from 'express';
import chatsModel from '../models/Chats.js';
import CreateBody from '../interfaces/bodies/chats/CreateBody.js';
import GetChatBody from '../interfaces/bodies/chats/GetChatBody.js';
import GetAllChatsBody from '../interfaces/bodies/chats/GetAllChatsBody.js';
import DeleteChatBody from '../interfaces/bodies/chats/DeleteChatBody.js';
class ChatsController {
async create(req: Request, res: Response) {
try {
const { number } = req.body as CreateBody;
const { chatData } = req.body as CreateBody;
const rangeAcceptable =
parseFloat(chatData.receive) >= 0 &&
parseFloat(chatData.receive) <= 100000000000000000;
if (!(number && chatData && chatData.pay && chatData.receive && rangeAcceptable))
return res.status(400).send({ success: false, data: 'Invalid offer data' });
const result = await chatsModel.create(req.body as CreateBody);
if (result.success) {
return res.status(200).send(result);
}
if (
result.data === 'Same user' ||
result.data === 'Invalid offer data' ||
result.data === 'User not registered'
) {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getChat(req: Request, res: Response) {
try {
if (!(req.body as GetChatBody).id)
return res.status(400).send({ success: false, data: 'Invalid chat data' });
const result = await chatsModel.getChat(req.body as GetChatBody);
if (result.success) {
return res.status(200).send(result);
}
if (result.data === 'Unauthorized') {
return res.status(401).send(result);
}
if (result.data === 'Invalid chat data' || result.data === 'User not registered') {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getChatChunk(req: Request, res: Response) {
try {
const body = req.body as GetChatBody & { chunkNumber: number };
if (!(body.id && body.chunkNumber))
return res.status(400).send({ success: false, data: 'Invalid chat data' });
const result = await chatsModel.getChatChunk(body);
if (result.success) {
return res.status(200).send(result);
}
if (result.data === 'Unauthorized') {
return res.status(401).send(result);
}
if (result.data === 'Invalid chat data' || result.data === 'User not registered') {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getAllChats(req: Request, res: Response) {
try {
const result = await chatsModel.getAllChats(req.body as GetAllChatsBody);
if (result.success) {
return res.status(200).send(result);
}
if (result.data === 'User not registered') {
return res.status(400).send(result);
}
res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async deleteChat(req: Request, res: Response) {
try {
if (!(req.body as DeleteChatBody).id)
return res.status(400).send({ success: false, data: 'Invalid chat data' });
const result = await chatsModel.deleteChat(req.body as DeleteChatBody);
if (result.success) {
return res.status(200).send(result);
}
if (result.data === 'Unauthorized') {
return res.status(401).send(result);
}
if (result.data === 'Invalid chat data' || result.data === 'User not registered') {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
}
const chatsController = new ChatsController();
export default chatsController;

View file

@ -0,0 +1,18 @@
import { Request, Response } from 'express';
import configModel from '../models/Config.js';
class ConfigController {
async get(req: Request, res: Response) {
const result = await configModel.get();
if (!result.success) {
return res.status(500).send(result);
}
res.status(200).send(result);
}
}
const configController = new ConfigController();
export default configController;

View file

@ -0,0 +1,205 @@
import { Request, Response } from 'express';
import UserData from '@/interfaces/common/UserData.js';
import Currency from '@/schemes/Currency.js';
import Pair from '@/schemes/Pair.js';
import { Op } from 'sequelize';
import User from '../schemes/User.js';
import ordersModel from '../models/Orders.js';
import dexModel from '../models/Dex.js';
class DexController {
async getPairsPage(req: Request, res: Response) {
try {
const { body } = req;
const { page, searchText, whitelistedOnly, sortOption } = body;
if (!page || typeof page !== 'number')
return res.status(400).send({ success: false, data: 'Invalid pair page data' });
const sort = sortOption;
const result = await dexModel.getPairsPage(
page,
(searchText || '').toString(),
!!whitelistedOnly,
sort,
);
if (!result.success) return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getPairsPagesAmount(req: Request, res: Response) {
try {
const { body } = req;
const { searchText, whitelistedOnly } = body;
const result = await dexModel.getPairsPagesAmount(
(searchText || '').toString(),
!!whitelistedOnly,
);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getPair(req: Request, res: Response) {
try {
if (!req.body.id)
return res.status(400).send({ success: false, data: 'Invalid pair data' });
const result = await dexModel.getPair(req.body.id);
if (result.data === 'Invalid pair data') return res.status(400).send(result);
if (result.data === 'Internal error') return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async registerBot(req: Request, res: Response) {
const userData = req.body.userData as UserData;
if (!userData) return res.status(400).send({ success: false, data: 'Invalid user data' });
const { orderId } = req.body;
const targetOrder = await ordersModel.getOrderRow(orderId).catch(() => null);
if (!targetOrder)
return res.status(400).send({ success: false, data: 'Invalid order data' });
const targetUser = await User.findOne({
where: {
address: userData.address,
},
});
if (!targetUser || targetOrder.user_id !== targetUser.id)
return res.status(400).send({ success: false, data: 'Invalid user data' });
const result = await dexModel.renewBotExpiration(orderId, targetUser.id);
return res.status(200).send(result);
}
async volumeStats(req: Request, res: Response) {
const { address, pairID, from, to } = req.body;
if (!address || !pairID)
return res.status(400).send({ success: false, data: 'Invalid data' });
const fromTimestamp = typeof from === 'number' ? from : 0;
const toTimestamp = typeof to === 'number' ? to : +Date.now();
const result = await dexModel.volumeStats(address, pairID, fromTimestamp, toTimestamp);
return res.status(200).send(result);
}
async getAssetsPriceRates(req: Request, res: Response) {
const { assetsIds } = req.body;
const currencysRows = await Currency.findAll({
where: {
asset_id: {
[Op.in]: assetsIds,
},
},
});
if (!currencysRows) {
return res.status(200).send({
success: false,
data: 'Assets with this id doesn`t exists',
});
}
const currencyIds = currencysRows.map((currency) => currency.id);
const pairsRows = (
(await Pair.findAll({
where: {
first_currency_id: {
[Op.in]: currencyIds,
},
},
include: [
{
model: Currency,
as: 'first_currency',
required: true,
attributes: ['asset_id'],
},
],
})) || []
).map((pair) => ({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
asset_id: pair?.first_currency?.asset_id,
rate: pair.rate,
}));
if (!pairsRows || pairsRows.length === 0) {
return res.status(200).send({
success: false,
data: 'Assets with this id doesn`t exists',
});
}
// const priceRates = await Promise.all(pairsRows.map(async (pair) => {
// const currency = await Currency.findOne({ where: {
// id: pair.first_currency_id
// }})
// return {
// asset_id: currency?.asset_id,
// rate: pair.rate
// }
// }))
return res.status(200).send({
success: true,
priceRates: pairsRows,
});
}
async findPairID(req: Request, res: Response) {
const { first, second } = req.body;
if (!first || !second)
return res.status(400).send({ success: false, data: 'Invalid data' });
const firstCurrency = await Currency.findOne({ where: { asset_id: first } });
const secondCurrency = await Currency.findOne({ where: { asset_id: second } });
if (!firstCurrency || !secondCurrency)
return res.status(400).send({ success: false, data: 'Invalid data' });
const pair = await Pair.findOne({
where: {
first_currency_id: firstCurrency.id,
second_currency_id: secondCurrency.id,
},
});
if (!pair) return res.status(404).send({ success: false, data: 'Pair not found' });
return res.status(200).send({
success: true,
data: pair.id,
});
}
}
const dexController = new DexController();
export default dexController;

View file

@ -0,0 +1,118 @@
import { Request, Response } from 'express';
import offersModel from '../models/Offers.js';
import UpdateBody from '../interfaces/bodies/offers/UpdateBody.js';
import DeleteBody from '../interfaces/bodies/offers/DeleteBody.js';
import GetPageBody from '../interfaces/bodies/offers/GetPageBody.js';
class OffersController {
async update(req: Request, res: Response) {
try {
const { offerData } = req.body as UpdateBody;
const isFull = !!(
offerData &&
offerData?.price &&
offerData?.min &&
offerData?.max &&
offerData?.deposit_seller &&
offerData?.deposit_buyer &&
offerData?.type &&
offerData?.input_currency_id &&
offerData?.target_currency_id &&
offerData?.deposit_currency_id
);
const rangeCorrect =
offerData?.min > 0 &&
offerData?.min < 1000000000 &&
offerData?.max > 0 &&
offerData?.max < 1000000000 &&
offerData?.deposit_buyer > 0 &&
offerData?.deposit_buyer < 1000000000 &&
offerData?.deposit_seller > 0 &&
offerData?.deposit_seller < 1000000000 &&
offerData?.price > 0 &&
offerData?.price < 10000000000 &&
offerData?.min < offerData?.max;
if (!isFull || !rangeCorrect)
return res.status(400).send({ success: false, data: 'Invalid offer data' });
const result = await offersModel.update(req.body as UpdateBody);
if (result.success) {
return res.status(200).send(result);
}
if (
result.data === 'User not registered' ||
result.data === 'Forbidden' ||
result.data === 'Offer is finished' ||
result.data === 'Invalid offer data'
) {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async delete(req: Request, res: Response) {
try {
if (!req.body.offerData?.number)
return res.status(400).send({ success: false, data: 'Invalid offer data' });
const result = await offersModel.delete(req.body as DeleteBody);
if (result.success) {
return res.status(200).send(result);
}
if (result.data !== 'Internal error') {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getPage(req: Request, res: Response) {
try {
const pageData = (req.body as GetPageBody).data;
if (!(pageData && pageData?.page && pageData?.type))
return res.status(400).send({ success: false, data: 'Invalid page data' });
const result = await offersModel.getPage((req.body as GetPageBody).data);
if (!result.success) return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getStats(_: Request, res: Response) {
try {
const result = await offersModel.getStats();
if (!result.success) return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
}
const offersController = new OffersController();
export default offersController;

View file

@ -0,0 +1,246 @@
import { Request, Response } from 'express';
import Decimal from 'decimal.js';
import candlesModel from '../models/Candles';
import ordersModel from '../models/Orders';
import CreateOrderBody from '../interfaces/bodies/orders/CreateOrderBody';
import GetUserOrdersPageBody from '../interfaces/bodies/orders/GetUserOrdersPageBody';
import GetUserOrdersBody from '../interfaces/bodies/orders/GetUserOrdersBody';
import CancelOrderBody from '../interfaces/bodies/orders/CancelOrderBody';
import GetCandlesBody from '../interfaces/bodies/orders/GetCandlesBody';
import GetChartOrdersBody from '../interfaces/bodies/orders/GetChartOrdersBody';
import ApplyOrderBody from '../interfaces/bodies/orders/ApplyOrderBody';
import userModel from '../models/User';
import UserData from '../interfaces/common/UserData';
import Pair from '../schemes/Pair';
import Currency from '../schemes/Currency';
import { validateTokensInput } from '../../shared/utils';
class OrdersController {
async createOrder(req: Request, res: Response) {
try {
const { orderData } = req.body as CreateOrderBody;
const isFull =
orderData &&
orderData?.type &&
orderData?.side &&
orderData?.price &&
orderData?.amount &&
orderData?.pairId;
const priceDecimal = new Decimal(orderData?.price || 0);
const amountDecimal = new Decimal(orderData?.amount || 0);
const pair = await Pair.findByPk(orderData?.pairId);
const firstCurrency = await Currency.findByPk(pair?.first_currency_id);
const secondCurrency = await Currency.findByPk(pair?.second_currency_id);
if (!pair || !firstCurrency || !secondCurrency) {
return res.status(400).send({ success: false, data: 'Invalid pair data' });
}
const firstCurrencyDecimalPoint = firstCurrency?.asset_info?.decimal_point || 12;
const secondCurrencyDecimalPoint = secondCurrency?.asset_info?.decimal_point || 12;
const rangeCorrect = (() => {
const priceCorrect = validateTokensInput(
orderData?.price,
secondCurrencyDecimalPoint,
).valid;
const amountCorrect = validateTokensInput(
orderData?.amount,
firstCurrencyDecimalPoint,
).valid;
return priceCorrect && amountCorrect;
})();
const priceDecimalPointCorrect = priceDecimal.toString().replace('.', '').length <= 20;
const amountDecimalPointCorrect =
amountDecimal.toString().replace('.', '').length <= 18;
if (!priceDecimalPointCorrect || !amountDecimalPointCorrect) {
return res.status(400).send({ success: false, data: 'Invalid pair data' });
}
if (!isFull || !rangeCorrect)
return res.status(400).send({ success: false, data: 'Invalid order data' });
const result = await ordersModel.createOrder(req.body);
if (result.data === 'Invalid order data') return res.status(400).send(result);
if (result.data === 'Same order') return res.status(400).send(result);
if (result.data === 'Internal error') return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getOrdersPage(req: Request, res: Response) {
try {
if (!req.body.pairId)
return res.status(400).send({ success: false, data: 'Invalid pair data' });
const result = await ordersModel.getOrdersPage(req.body.pairId);
if (result.data === 'Invalid pair data') return res.status(400).send(result);
if (result.data === 'Internal error') return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getUserOrdersPage(req: Request, res: Response) {
try {
if (!(req.body as GetUserOrdersPageBody).pairId)
return res.status(400).send({ success: false, data: 'Invalid pair data' });
const result = await ordersModel.getUserOrdersPage(req.body as GetUserOrdersPageBody);
if (result.data === 'Invalid pair data') return res.status(400).send(result);
if (result.data === 'Internal error') return res.status(500).send(result);
const userAddress = (req.body.userData as UserData)?.address;
if (userAddress) {
await userModel.resetNotificationsForPair(
(req.body.userData as UserData)?.address,
req.body.pairId,
);
}
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getUserOrders(req: Request, res: Response) {
try {
await userModel.resetExchangeNotificationsAmount(
(req.body.userData as UserData).address,
);
const result = await ordersModel.getUserOrders(req.body as GetUserOrdersBody);
if (result.data === 'Internal error') return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async cancelOrder(req: Request, res: Response) {
try {
if (!(req.body as CancelOrderBody).orderId)
return res.status(400).send({ success: false, data: 'Invalid order data' });
const result = await ordersModel.cancelOrder(req.body as CancelOrderBody);
if (result.data === 'Invalid order data') return res.status(400).send(result);
if (result.data === 'Internal error') return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getCandles(req: Request, res: Response) {
try {
const { body }: { body: GetCandlesBody } = req;
if (!body.pairId || !body.period)
return res.status(400).send({ success: false, data: 'Invalid pair data' });
const result = await candlesModel.getCandles(body.pairId, body.period);
if (result.data === 'Invalid pair data') return res.status(400).send(result);
if (result.data === 'Internal error') return res.status(500).send(result);
return res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getChartOrders(req: Request, res: Response) {
try {
const { body }: { body: GetChartOrdersBody } = req;
if (!body.pairId)
return res.status(400).send({ success: false, data: 'Invalid pair data' });
const result = await ordersModel.getChartOrders(body.pairId);
if (result.data === 'Internal error') return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getPairStats(req: Request, res: Response) {
try {
const { body }: { body: GetChartOrdersBody } = req;
if (!body.pairId)
return res.status(400).send({ success: false, data: 'Invalid pair data' });
const result = await ordersModel.getPairStats(body.pairId);
if (result.data === 'Internal error') return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async applyOrder(req: Request, res: Response) {
try {
const { orderData } = req.body as ApplyOrderBody;
const isFull = orderData && orderData?.id && orderData?.connected_order_id;
if (!isFull)
return res.status(400).send({ success: false, data: 'Invalid order data' });
const result = await ordersModel.applyOrder(req.body);
if (result.data === 'Invalid order data') return res.status(400).send(result);
if (result.data === 'Internal error') return res.status(500).send(result);
res.status(200).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
}
const ordersController = new OrdersController();
export default ordersController;

View file

@ -0,0 +1,67 @@
import CreateMessageBody from '../interfaces/bodies/chats/CreateMessageBody.js';
import ChatSocketData from '../interfaces/special/socket-data/ChatSocketData.js';
import DepositSocketData from '../interfaces/special/socket-data/DepositSocketData.js';
import chatsModel from '../models/Chats.js';
class ProcessController {
async createMessage(data: CreateMessageBody) {
try {
const isFull =
data.chat_id &&
data.message &&
((data.message.type !== 'img' &&
data.message.text &&
data.message.text?.length <= 10000) ||
(data.message.type === 'img' && data.message.url));
if (!isFull) return { success: false, data: 'Invalid message data' };
const result = await chatsModel.createMessage({
chat_id: data.chat_id,
userData: data.userData,
message: {
...data.message,
success: false,
fail: false,
},
} as CreateMessageBody);
return result;
} catch (err) {
console.log(err);
return { success: false, data: 'Unhandled error' };
}
}
async changeDeposit(data: DepositSocketData) {
try {
const isFull = data.chat_id && data.deposit_state;
if (!isFull) return { success: false, data: 'Invalid deposit state data' };
const result = await chatsModel.changeDeposit(data);
return result;
} catch (err) {
console.log(err);
return { success: false, data: 'Unhandled error' };
}
}
async setWatched(data: ChatSocketData) {
try {
const isFull = data.chat_id;
if (!isFull) return { success: false, data: 'Invalid watched data' };
const result = await chatsModel.setWatched(data);
return result;
} catch (err) {
console.log(err);
return { success: false, data: 'Unhandled error' };
}
}
}
const processController = new ProcessController();
export default processController;

View file

@ -0,0 +1,75 @@
import { Request, Response } from 'express';
import exchangeModel from '../models/ExchangeTransactions.js';
import ConfirmTransactionBody from '../interfaces/bodies/exchange-transactions/ConfirmTransactionBody.js';
import GetActiveTxByOrdersIdsBody from '../interfaces/bodies/exchange-transactions/GetActiveTxByOrdersIdsBody.js';
import Order from '../schemes/Order.js';
class TransactionsController {
async confirmTransaction(req: Request, res: Response) {
try {
if (!(req.body as ConfirmTransactionBody).transactionId) {
return res.status(400).json({ success: false, data: 'Invalid transaction data' });
}
const result = await exchangeModel.confirmTransaction(
req.body as ConfirmTransactionBody,
);
if (
result.data === 'Transaction is not pending' ||
result.data === 'You are not a participant of this transaction'
) {
return res.status(400).send(result);
}
if (result.data === 'Internal error') {
return res.status(500).send(result);
}
return res.status(200).send(result);
} catch (err) {
console.log(err);
return { success: false, data: 'Internal error' };
}
}
async getActiveTxByOrdersIds(req: Request, res: Response) {
try {
const body = req.body as GetActiveTxByOrdersIdsBody;
const { firstOrderId, secondOrderId, userData } = body;
const userRow = await Order.findOne({ where: { address: userData.address } });
if (!userRow) {
throw new Error('JWT token of non-existent user');
}
const firstOrderRow = await Order.findOne({
where: { id: firstOrderId, user_id: userRow.id },
});
const secondOrderRow = await Order.findOne({
where: { id: secondOrderId, user_id: userRow.id },
});
if (!firstOrderRow && !secondOrderRow) {
return res
.status(400)
.send({ success: false, data: 'None of the orders belong to this user' });
}
const tx = await exchangeModel.getActiveTxByOrdersIds(firstOrderId, secondOrderId);
if (!tx) {
return res.status(400).send({ success: false, data: 'Invalid order data' });
}
return res.status(200).send({ success: true, data: tx });
} catch (err) {
console.log(err);
return res.status(500).send({ success: false, data: 'Internal error' });
}
}
}
const transactionsController = new TransactionsController();
export default transactionsController;

View file

@ -0,0 +1,98 @@
import { Request, Response } from 'express';
import configModel from '../models/Config.js';
import userModel from '../models/User.js';
import GetUserBody from '../interfaces/bodies/user/GetUserBody.js';
import SetFavouriteCurrsBody from '../interfaces/bodies/user/SetFavouriteCurrsBody.js';
class UserController {
async getUser(req: Request, res: Response) {
try {
if (!req.body.userData?.address)
return res.status(400).send({ success: false, data: 'Invalid user data' });
const result = await userModel.getUser({
address: req.body.userData.address,
} as GetUserBody);
if (result.success) {
return res.status(200).send(result);
}
if (result.data === 'User not registered') {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async getNotificationsAmount(req: Request, res: Response) {
try {
if (!req.body.userData?.address)
return res.status(400).send({ success: false, data: 'Invalid user data' });
const result = await userModel.getNotificationsAmount({
address: req.body.userData.address,
} as GetUserBody);
if (result.success) {
return res.status(200).send(result);
}
if (result.data === 'User not found') {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
async setFavouriteCurrencies(req: Request, res: Response) {
try {
if (!req.body.data)
return res.status(400).send({ success: false, data: 'Invalid currencies data' });
try {
JSON.stringify(req.body.data);
const configGetResult = await configModel.get();
if (!configGetResult.success) return res.status(400).send(configGetResult);
const { currencies } = configGetResult.data;
if (
(req.body as SetFavouriteCurrsBody).data.find(
(e) => !currencies.find((curr) => curr.code === e),
)
)
throw new Error();
} catch {
return res.status(400).send({ success: false, data: 'Invalid currencies data' });
}
const result = await userModel.setFavouriteCurrencies(req.body);
if (result.success) {
return res.status(200).send(result);
}
if (result.data === 'User not registered') {
return res.status(400).send(result);
}
return res.status(500).send(result);
} catch (err) {
console.log(err);
res.status(500).send({ success: false, data: 'Unhandled error' });
}
}
}
const userController = new UserController();
export default userController;

29
src/database.ts Normal file
View file

@ -0,0 +1,29 @@
import 'dotenv/config';
import pg from 'pg';
async function initdb() {
const pool = new pg.Pool({
user: process.env.PGUSER,
password: process.env.PGPASSWORD,
host: process.env.PGHOST,
database: 'postgres',
port: parseInt(process.env.PGPORT || '5432', 10),
keepAlive: true,
idleTimeoutMillis: 0,
max: 100,
});
try {
await pool.query(`CREATE DATABASE "${process.env.PGDATABASE}" `);
} catch (error) {
if ((error as { code: string }).code === '42P04') {
console.log('Database already exists, skipping creation');
} else {
throw error;
}
}
await pool.end();
}
export default initdb;

View file

@ -0,0 +1,12 @@
import UserData from '../../common/UserData';
interface CreateBody {
userData: UserData;
number: string;
chatData: {
pay: string;
receive: string;
};
}
export default CreateBody;

View file

@ -0,0 +1,10 @@
import Message from '../../common/Message';
import UserData from '../../common/UserData';
interface CreateMessageBody {
chat_id: string;
userData: UserData;
message: Message;
}
export default CreateMessageBody;

View file

@ -0,0 +1,8 @@
import UserData from '../../common/UserData';
interface DeleteChatBody {
userData: UserData;
id: string;
}
export default DeleteChatBody;

View file

@ -0,0 +1,7 @@
import UserData from '../../common/UserData';
interface GetAllChatsBody {
userData: UserData;
}
export default GetAllChatsBody;

View file

@ -0,0 +1,9 @@
import UserData from '../../common/UserData';
interface GetChatBody {
userData: UserData;
id: string;
chat_id?: undefined;
}
export default GetChatBody;

View file

@ -0,0 +1,8 @@
import UserData from '../../common/UserData';
interface ConfirmTransactionBody {
transactionId: string;
userData: UserData;
}
export default ConfirmTransactionBody;

View file

@ -0,0 +1,7 @@
import UserData from '@/interfaces/common/UserData';
export default interface GetActiveTxByOrdersIdsBody {
userData: UserData;
firstOrderId: number;
secondOrderId: number;
}

View file

@ -0,0 +1,10 @@
import UserData from '../../common/UserData';
interface DeleteBody {
userData: UserData;
offerData: {
number: string;
};
}
export default DeleteBody;

View file

@ -0,0 +1,18 @@
import OfferType from '../../common/OfferType';
interface PageData {
type: OfferType;
page: number;
input_currency_id?: string;
target_currency_id?: string;
price?: number;
priceDescending?: boolean;
}
interface GetPageBody {
data: PageData;
}
export default GetPageBody;
export { type PageData };

View file

@ -0,0 +1,23 @@
import OfferType from '../../common/OfferType';
import UserData from '../../common/UserData';
interface OfferData {
price: number;
min: number;
max: number;
deposit_seller: number;
deposit_buyer: number;
type: OfferType;
comment?: string;
input_currency_id: string;
target_currency_id: string;
deposit_currency_id: string;
number?: string;
}
interface UpdateBody {
userData: UserData;
offerData: OfferData;
}
export default UpdateBody;

View file

@ -0,0 +1,14 @@
import UserData from '../../common/UserData';
interface OrderData {
id: string;
connected_order_id: string;
hex_raw_proposal: string;
}
interface ApplyOrderBody {
userData: UserData;
orderData: OrderData;
}
export default ApplyOrderBody;

View file

@ -0,0 +1,8 @@
import UserData from '../../common/UserData';
interface CancelOrderBody {
orderId: string;
userData: UserData;
}
export default CancelOrderBody;

View file

@ -0,0 +1,18 @@
import OfferType from '../../common/OfferType';
import Side from '../../common/Side';
import UserData from '../../common/UserData';
interface OrderData {
type: OfferType;
side: Side;
price: string;
amount: string;
pairId: string;
}
interface CreateOrderBody {
userData: UserData;
orderData: OrderData;
}
export default CreateOrderBody;

View file

@ -0,0 +1,8 @@
import Period from '../../common/Period';
interface GetCandlesBody {
pairId: string;
period: Period;
}
export default GetCandlesBody;

View file

@ -0,0 +1,5 @@
interface GetChartOrdersBody {
pairId: string;
}
export default GetChartOrdersBody;

View file

@ -0,0 +1,7 @@
import UserData from '../../common/UserData';
interface GetUserOrdersBody {
userData: UserData;
}
export default GetUserOrdersBody;

View file

@ -0,0 +1,8 @@
import UserData from '../../common/UserData';
interface GetUserOrdersPageBody {
userData: UserData;
pairId: string;
}
export default GetUserOrdersPageBody;

View file

@ -0,0 +1,6 @@
export default interface AuthData {
address: string;
alias: string;
signature: string;
message: string;
}

View file

@ -0,0 +1,5 @@
interface GetUserBody {
address: string;
}
export default GetUserBody;

View file

@ -0,0 +1,8 @@
import UserData from '../../common/UserData';
interface SetFavouriteCurrsBody {
userData: UserData;
data: string[];
}
export default SetFavouriteCurrsBody;

View file

@ -0,0 +1,9 @@
interface Candle {
timestamp: string;
shadow_top: number | undefined;
shadow_bottom: number | undefined;
body_first: number | undefined;
body_second: number | undefined;
}
export default Candle;

View file

@ -0,0 +1,3 @@
type CurrencyType = 'fiat' | 'crypto' | 'deposit';
export default CurrencyType;

View file

@ -0,0 +1,12 @@
interface Message {
type?: 'img';
url?: string;
text?: string;
timestamp: number;
fromOwner?: boolean;
success: boolean;
fail: boolean;
system?: boolean;
}
export default Message;

View file

@ -0,0 +1,3 @@
type OfferType = 'buy' | 'sell';
export default OfferType;

View file

@ -0,0 +1,7 @@
export default interface PairStats {
rate: number;
coefficient: number;
high: number;
low: number;
volume: number;
}

View file

@ -0,0 +1,3 @@
type Period = '1h' | '1d' | '1w' | '1m';
export default Period;

View file

@ -0,0 +1,3 @@
type Side = 'limit' | 'market';
export default Side;

View file

@ -0,0 +1,8 @@
interface UserData {
alias?: string;
address: string;
id?: undefined;
favourite_currencies?: undefined;
}
export default UserData;

View file

@ -0,0 +1,11 @@
interface CandleRow {
id: string;
pair_id: string;
timestamp: string;
shadow_top: number;
shadow_bottom: number;
body_first: number;
body_second: number;
}
export default CandleRow;

View file

@ -0,0 +1,18 @@
type DepositState = null | 'default' | 'deposit' | 'confirmed' | 'canceled';
interface ChatRow {
id: string;
offer_number: string;
buyer_id: string;
chat_history: string;
status: string;
pay: number;
receive: number;
owner_deposit: DepositState;
opponent_deposit: DepositState;
view_list: string[];
}
export default ChatRow;
export { type DepositState };

View file

@ -0,0 +1,11 @@
import CurrencyType from '../common/CurrecnyType';
interface CurrencyRow {
id: string;
name: string;
code: string;
type: CurrencyType;
asset_id: string | null;
}
export default CurrencyRow;

View file

@ -0,0 +1,12 @@
interface ExchangeRow {
id: string;
buy_order_id: string;
sell_order_id: string;
amount: number;
timestamp: string;
status: string;
creator: string;
hex_raw_proposal: string;
}
export default ExchangeRow;

View file

@ -0,0 +1,13 @@
interface MessagesRow {
id: string;
type: string;
url: string | null;
text: string | null;
timestamp: string;
from_owner: boolean;
success: boolean;
fail: boolean;
system: boolean;
}
export default MessagesRow;

View file

@ -0,0 +1,21 @@
import OfferType from '../common/OfferType';
interface OfferRow {
id: string;
user_id: string;
price: number;
min: number;
max: number;
deposit_seller: number;
deposit_buyer: number;
type: OfferType;
input_currency_id: string;
target_currency_id: string;
comment: string | null;
number: string;
offer_status: 'default' | 'process' | 'hidden' | 'finished';
deposit_currency_id: string;
timestamp: string;
}
export default OfferRow;

View file

@ -0,0 +1,17 @@
import OfferType from '../common/OfferType';
interface OrderRow {
id: string;
type: OfferType;
timestamp: string;
side: string;
price: number;
amount: number;
total: number;
pair_id: string;
user_id: string;
status: string;
left: number;
}
export default OrderRow;

View file

@ -0,0 +1,7 @@
interface TradingPairRow {
id: string;
first_currency_id: string;
second_currency_id: string;
}
export default TradingPairRow;

View file

@ -0,0 +1,8 @@
interface UserRow {
id: string;
alias: string;
address: string;
favourite_currencies: string[];
}
export default UserRow;

View file

@ -0,0 +1,5 @@
/* eslint-disable no-unused-vars */
export enum PairSortOption {
VOLUME_LOW_TO_HIGH = 'VOLUME_LOW_TO_HIGH',
VOLUME_HIGH_TO_LOW = 'VOLUME_HIGH_TO_LOW',
}

View file

@ -0,0 +1,6 @@
interface ErrorResponse {
success: false;
data: string;
}
export default ErrorResponse;

View file

@ -0,0 +1,47 @@
import Currency from '../../../schemes/Currency';
interface UserDataWithId {
id: number;
alias: string;
address: string;
favourite_currencies: string[] | undefined;
}
interface ChatData {
id: number;
user_id: number;
price: number;
min: number;
max: number;
deposit_seller: number;
deposit_buyer: number;
type: string;
input_currency: Currency | null;
target_currency: Currency | null;
comment: string | null;
number: string;
offer_status: string;
deposit_currency: Currency | null;
timestamp: bigint;
creator_data: UserDataWithId;
buyer_data: UserDataWithId;
offer_number: string;
buyer_id: number;
chunk_count: number;
status: string;
pay: number;
receive: number;
owner_deposit: string | null;
opponent_deposit: string | null;
view_list: number[];
favourite_currencies?: undefined;
}
interface GetChatResponse {
success: true;
data: ChatData;
}
export default GetChatResponse;

View file

@ -0,0 +1,10 @@
import Currency from '../../../schemes/Currency';
interface GetConfigRes {
success: true;
data: {
currencies: Currency[];
};
}
export default GetConfigRes;

View file

@ -0,0 +1,11 @@
interface GetStatsRes {
success: true;
data: {
opened: number;
volume_24: number;
volume_7: number;
volume_30: number;
};
}
export default GetStatsRes;

View file

@ -0,0 +1,17 @@
import UserData from '../../common/UserData';
interface ApplyTip {
id: number;
left: string;
price: string;
user: UserData;
timestamp?: number;
type: string;
total: string;
connected_order_id: number;
transaction: boolean;
hex_raw_proposal?: string;
isInstant?: boolean;
}
export default ApplyTip;

View file

@ -0,0 +1,16 @@
interface PairStats {
rate: number;
coefficient: number;
high: number;
low: number;
volume: number;
}
interface GetPairStatsRes {
success: true;
data: PairStats;
}
export default GetPairStatsRes;
export type { PairStats };

View file

@ -0,0 +1,8 @@
import UserData from '../../common/UserData';
interface AuthorizedData {
chat_id: string;
userData: UserData;
}
export default AuthorizedData;

View file

@ -0,0 +1,8 @@
import UserData from '../../common/UserData';
interface ChatSocketData {
chat_id: string;
userData: UserData;
}
export default ChatSocketData;

View file

@ -0,0 +1,7 @@
import AuthorizedData from './AuthorizedData';
interface DepositSocketData extends AuthorizedData {
deposit_state: string;
}
export default DepositSocketData;

View file

@ -0,0 +1,8 @@
import Message from '../../common/Message';
import AuthorizedData from './AuthorizedData';
interface MessageSocketData extends AuthorizedData {
message: Message;
}
export default MessageSocketData;

View file

@ -0,0 +1,21 @@
import PairData from '@/interfaces/common/PairData';
import UserData from '../../common/UserData';
interface OrderData {
id: number;
type: string;
timestamp: number;
side: string;
price: number;
amount: number;
total: number;
pair_id: number;
user_id?: undefined;
status: string;
left: number;
user: UserData;
pair: PairData;
immediateMatch: boolean;
}
export default OrderData;

View file

@ -0,0 +1,5 @@
interface SocketData {
id: string;
}
export default SocketData;

View file

@ -0,0 +1,5 @@
import UserData from '@/interfaces/common/UserData';
export default interface UserSocketData {
userData: UserData;
}

View file

@ -0,0 +1,50 @@
import AuthData from '@/interfaces/bodies/user/AuthData';
import axios from 'axios';
async function validateWallet(authData: AuthData) {
async function fetchZanoApi(method: string, params: object) {
try {
return await axios
.post('http://195.201.107.230:33341/json_rpc', {
id: 0,
jsonrpc: '2.0',
method,
params,
})
.then((res) => res.data);
} catch (error) {
console.log(error);
}
}
const { message, address, alias, signature } = authData;
if (!message || !alias || !signature) {
return false;
}
const response = await fetchZanoApi('validate_signature', {
buff: Buffer.from(message).toString('base64'),
alias,
sig: signature,
});
const aliasOk = response?.result?.status === 'OK';
if (!aliasOk) {
return false;
}
const aliasDetailsResponse = await fetchZanoApi('get_alias_details', {
alias,
});
const aliasDetails = aliasDetailsResponse?.result?.alias_details;
const aliasAddress = aliasDetails?.address;
const addressOk = !!aliasAddress && aliasAddress === address;
return aliasOk && addressOk;
}
export default validateWallet;

View file

@ -0,0 +1,41 @@
import { NextFunction, Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import User from '@/schemes/User';
import UserData from '../interfaces/common/UserData';
class Middleware {
async verifyToken(req: Request, res: Response, next: NextFunction) {
try {
const userData = jwt.verify(req.body.token, process.env.JWT_SECRET || '') as UserData;
req.body.userData = userData;
next();
} catch {
res.status(401).send({ success: false, data: 'Unauthorized (JWT)' });
}
}
async verifyAdmin(req: Request, res: Response, next: NextFunction) {
const userAlias = req?.body?.userData?.alias || null;
console.log(req?.body?.userData);
const userField = await User.findOne({
where: {
alias: userAlias,
},
});
const isAdmin = userField && userField.isAdmin;
const isOwner = process.env.OWNER_ALIAS && process.env.OWNER_ALIAS === userField?.alias;
if (isAdmin || isOwner) {
next();
} else {
res.status(401).send({ success: false, data: 'Unauthorized' });
}
}
}
const middleware = new Middleware();
export default middleware;

65
src/middleware/socket.ts Normal file
View file

@ -0,0 +1,65 @@
import { Event } from 'socket.io';
import jwt from 'jsonwebtoken';
import chatsModel from '../models/Chats.js';
import UserData from '../interfaces/common/UserData.js';
async function socketMiddleware(event: Event, next: (_err?: Error | undefined) => void) {
const [path, data] = event;
const skipPaths = [
'in-account',
'in-trading',
'out-trading',
'in-dex-notifications',
'out-dex-notifications',
'error',
'leave',
'disconnect',
];
const isSkip = skipPaths.includes(path);
if (isSkip) return next();
let userData: UserData;
try {
userData = jwt.verify(data.token, process.env.JWT_SECRET || '') as UserData;
} catch {
return next(new Error('Unauthorized'));
}
data.userData = userData;
const result = await chatsModel.getChat({ id: data.chat_id, userData });
if (!result.success) return next(new Error(result.data));
next();
}
export function verifyUser(paths: string[]) {
async function middleware(event: Event, next: (_err?: Error | undefined) => void) {
const [path, data] = event;
if (!paths.includes(path)) {
return next();
}
let userData;
try {
userData = jwt.verify(data.token, process.env.JWT_SECRET || '') as UserData;
} catch {
return next(new Error('Unauthorized'));
}
data.userData = userData;
next();
}
return middleware;
}
export default socketMiddleware;

179
src/models/Candles.ts Normal file
View file

@ -0,0 +1,179 @@
import { Op } from 'sequelize';
import Period from '../interfaces/common/Period.js';
import dexModel from './Dex.js';
import Transaction from '../schemes/Transaction.js';
import Order from '../schemes/Order.js';
class CandlesModel {
async getCandles(pairId: string, period: Period) {
try {
const pairRow = await dexModel.getPairRow(parseInt(pairId, 10));
if (!pairRow) return { success: false, data: 'Invalid pair data' };
const buyOrders = await Order.findAll({
where: {
pair_id: pairRow.id,
type: 'buy',
},
});
const buyOrdersIds = buyOrders.map((order) => order.id);
interface TransactionData {
timestamp: string;
amount: string;
id: number;
price: string;
buy_order_id: number;
}
const transactions = (await Transaction.findAll({
where: {
buy_order_id: {
[Op.in]: buyOrdersIds,
},
status: 'confirmed',
},
order: [['timestamp', 'ASC']],
attributes: ['timestamp', 'amount', 'id', 'amount', 'buy_order_id'],
}).then((transactions) =>
transactions
.map((transaction) => transaction.toJSON())
.map((transaction) => ({
...transaction,
price: buyOrders.find((order) => order.id === transaction.buy_order_id)
?.price,
})),
)) as TransactionData[];
const aggregationPeriod = (() => {
switch (period) {
case '1h':
return 3600000;
case '1d':
return 86400000;
case '1w':
return 604800000;
case '1m':
return 2592000000;
default:
return 3600000;
}
})();
interface ResultCandle {
pair_id: number;
timestamp: number;
shadow_top: number;
shadow_bottom: number;
body_first: number;
body_second: number;
}
const foundCandles = transactions.reduce((acc: ResultCandle[], transaction) => {
const currentTimestamp = parseInt(transaction.timestamp, 10);
const lastCadle = acc[acc.length - 1];
if (!lastCadle) {
return [
{
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: parseFloat(transaction.price),
shadow_bottom: parseFloat(transaction.price),
body_first: parseFloat(transaction.price),
body_second: parseFloat(transaction.price),
},
];
}
if (lastCadle.timestamp + aggregationPeriod < currentTimestamp) {
// creata new candle
const prevCandleEnding = lastCadle.body_second;
return [
...acc,
{
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: parseFloat(transaction.price),
shadow_bottom: parseFloat(transaction.price),
body_first: prevCandleEnding,
body_second: parseFloat(transaction.price),
},
];
}
// add to existing candle
const newCandle = {
...lastCadle,
shadow_top: Math.max(lastCadle.shadow_top, parseFloat(transaction.price)),
shadow_bottom: Math.min(lastCadle.shadow_bottom, parseFloat(transaction.price)),
body_second: parseFloat(transaction.price),
};
acc[acc.length - 1] = newCandle;
return acc;
}, [] as ResultCandle[]);
const endTimestamp = Date.now();
const completeCandles = [];
let currentTimestamp =
foundCandles[0]?.timestamp || endTimestamp - aggregationPeriod * 10;
let lastRealCandle = {
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: foundCandles[0]?.body_second || 0,
shadow_bottom: foundCandles[0]?.body_second || 0,
body_first: foundCandles[0]?.body_second || 0,
body_second: foundCandles[0]?.body_second || 0,
};
for (let i = 0; i < foundCandles.length; i++) {
const candle = foundCandles[i];
// Fill gaps with "empty" candles that replicate the last real candle's values
while (currentTimestamp < candle.timestamp) {
completeCandles.push({
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: lastRealCandle.body_second,
shadow_bottom: lastRealCandle.body_second,
body_first: lastRealCandle.body_second,
body_second: lastRealCandle.body_second,
});
currentTimestamp += aggregationPeriod;
}
// Add the actual candle
completeCandles.push(candle);
lastRealCandle = candle; // Update last real candle to the current one
currentTimestamp = candle.timestamp + aggregationPeriod;
}
// Fill any remaining gaps up to the current time (endTimestamp) with the last known real candle's values
while (currentTimestamp <= endTimestamp) {
completeCandles.push({
pair_id: pairRow.id,
timestamp: currentTimestamp,
shadow_top: lastRealCandle.body_second,
shadow_bottom: lastRealCandle.body_second,
body_first: lastRealCandle.body_second,
body_second: lastRealCandle.body_second,
});
currentTimestamp += aggregationPeriod;
}
return { success: true, data: completeCandles || [] };
} catch (err) {
console.log(err);
return { success: false, data: 'Internal error' };
}
}
}
const candlesModel = new CandlesModel();
export default candlesModel;

Some files were not shown because too many files have changed in this diff Show more