Compare commits
77 Commits
a65bc94dac
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ed03740852 | |||
| a3c1dfad2d | |||
| 9b410ace09 | |||
| 72b061749d | |||
| a42f228984 | |||
| 47b33a152d | |||
| ecffd976ac | |||
| f0f431b34e | |||
| b2457f0617 | |||
| 148b37dca3 | |||
| 1b83dad6b3 | |||
| a1bbb17b5d | |||
| 916c23e092 | |||
| 2c5116c5de | |||
| 441a514acc | |||
| 6f7388bb10 | |||
| fd7b9713ea | |||
| 7b5421e6e7 | |||
| 21f324aa12 | |||
| eeee9d9853 | |||
| 38fb66c694 | |||
| 14b8b50812 | |||
| f35b30e337 | |||
| 22f2e648a2 | |||
| 7973a1e8b7 | |||
| c50824adb6 | |||
| 257c476cc1 | |||
| 815109dec2 | |||
| 0673b01ac8 | |||
| 556554fbf3 | |||
| 94c7b6ddb4 | |||
| 715a43c097 | |||
| 0442b08623 | |||
| 0392e70b78 | |||
| a6bce1613b | |||
| 7626504480 | |||
| e86fbfa8e9 | |||
| 7d04d23a89 | |||
| dabf6dd60f | |||
| b1a864ce31 | |||
| 75a197947f | |||
| d446d5ab11 | |||
| 5a4a67e726 | |||
| 8750951de5 | |||
| cd84e7c3c9 | |||
| 5876efe29f | |||
| b9975cda10 | |||
| 826177c445 | |||
| 3baa74174d | |||
| b2ed48492b | |||
| ebf70dd10c | |||
| 488e9f45f1 | |||
| f5f53c6af4 | |||
| 2e49346533 | |||
| 0fdc45a031 | |||
| 956d79c3dc | |||
| 2397ebfe45 | |||
| 366531c185 | |||
| fdea1402c6 | |||
| a38092b1a9 | |||
| f67685afc3 | |||
| d9ba46fb5d | |||
| e706d8adbb | |||
| 5d8f0235d2 | |||
| 43c5f3bd52 | |||
| 273b877079 | |||
| 0545cd6e97 | |||
| 19a8ffbbd4 | |||
| a689766f37 | |||
| bb4f7d4607 | |||
| a9bf23ddbb | |||
| 43891434c5 | |||
| 99a6cc1742 | |||
| 28dd64b1cf | |||
| cfcba95445 | |||
| 61101fcf0a | |||
| a7a1628f3c |
395
.dependency-cruiser.cjs
Normal file
@@ -0,0 +1,395 @@
|
||||
/** @type {import('dependency-cruiser').IConfiguration} */
|
||||
module.exports = {
|
||||
forbidden: [
|
||||
{
|
||||
name: 'no-circular',
|
||||
severity: 'warn',
|
||||
comment:
|
||||
'This dependency is part of a circular relationship. You might want to revise ' +
|
||||
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
|
||||
from: {},
|
||||
to: {
|
||||
circular: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-orphans',
|
||||
comment:
|
||||
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
|
||||
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
|
||||
"add an exception for it in your dependency-cruiser configuration. By default " +
|
||||
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
|
||||
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
|
||||
severity: 'warn',
|
||||
from: {
|
||||
orphan: true,
|
||||
pathNot: [
|
||||
'(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files
|
||||
'[.]d[.]ts$', // TypeScript declaration files
|
||||
'(^|/)tsconfig[.]json$', // TypeScript config
|
||||
'(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs
|
||||
]
|
||||
},
|
||||
to: {},
|
||||
},
|
||||
{
|
||||
name: 'no-deprecated-core',
|
||||
comment:
|
||||
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
|
||||
"bound to exist - node doesn't deprecate lightly.",
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'core'
|
||||
],
|
||||
path: [
|
||||
'^v8/tools/codemap$',
|
||||
'^v8/tools/consarray$',
|
||||
'^v8/tools/csvparser$',
|
||||
'^v8/tools/logreader$',
|
||||
'^v8/tools/profile_view$',
|
||||
'^v8/tools/profile$',
|
||||
'^v8/tools/SourceMap$',
|
||||
'^v8/tools/splaytree$',
|
||||
'^v8/tools/tickprocessor-driver$',
|
||||
'^v8/tools/tickprocessor$',
|
||||
'^node-inspect/lib/_inspect$',
|
||||
'^node-inspect/lib/internal/inspect_client$',
|
||||
'^node-inspect/lib/internal/inspect_repl$',
|
||||
'^async_hooks$',
|
||||
'^punycode$',
|
||||
'^domain$',
|
||||
'^constants$',
|
||||
'^sys$',
|
||||
'^_linklist$',
|
||||
'^_stream_wrap$'
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-deprecated',
|
||||
comment:
|
||||
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
|
||||
'version of that module, or find an alternative. Deprecated modules are a security risk.',
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'deprecated'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-non-package-json',
|
||||
severity: 'error',
|
||||
comment:
|
||||
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
|
||||
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
|
||||
"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " +
|
||||
"in your package.json.",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-no-pkg',
|
||||
'npm-unknown'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-unresolvable',
|
||||
comment:
|
||||
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
|
||||
'module: add it to your package.json. In all other cases you likely already know what to do.',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: {
|
||||
couldNotResolve: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-duplicate-dep-types',
|
||||
comment:
|
||||
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
||||
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
|
||||
"maintenance problems later on.",
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
moreThanOneDependencyType: true,
|
||||
// as it's pretty common to have a type import be a type only import
|
||||
// _and_ (e.g.) a devDependency - don't consider type-only dependency
|
||||
// types for this rule
|
||||
dependencyTypesNot: ["type-only"]
|
||||
}
|
||||
},
|
||||
|
||||
/* rules you might want to tweak for your specific situation: */
|
||||
|
||||
{
|
||||
name: 'not-to-spec',
|
||||
comment:
|
||||
'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' +
|
||||
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
|
||||
'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: {
|
||||
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-dev-dep',
|
||||
severity: 'error',
|
||||
comment:
|
||||
"This module depends on an npm package from the 'devDependencies' section of your " +
|
||||
'package.json. It looks like something that ships to production, though. To prevent problems ' +
|
||||
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
|
||||
'section of your package.json. If this module is development only - add it to the ' +
|
||||
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
|
||||
from: {
|
||||
path: '^(src)',
|
||||
pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
||||
},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-dev',
|
||||
],
|
||||
// type only dependencies are not a problem as they don't end up in the
|
||||
// production code or are ignored by the runtime.
|
||||
dependencyTypesNot: [
|
||||
'type-only'
|
||||
],
|
||||
pathNot: [
|
||||
'node_modules/@types/'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'optional-deps-used',
|
||||
severity: 'info',
|
||||
comment:
|
||||
"This module depends on an npm package that is declared as an optional dependency " +
|
||||
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
|
||||
"If you're using an optional dependency here by design - add an exception to your" +
|
||||
"dependency-cruiser configuration.",
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-optional'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'peer-deps-used',
|
||||
comment:
|
||||
"This module depends on an npm package that is declared as a peer dependency " +
|
||||
"in your package.json. This makes sense if your package is e.g. a plugin, but in " +
|
||||
"other cases - maybe not so much. If the use of a peer dependency is intentional " +
|
||||
"add an exception to your dependency-cruiser configuration.",
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: [
|
||||
'npm-peer'
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
options: {
|
||||
|
||||
/* Which modules not to follow further when encountered */
|
||||
doNotFollow: {
|
||||
/* path: an array of regular expressions in strings to match against */
|
||||
path: ['node_modules']
|
||||
},
|
||||
|
||||
/* Which modules to exclude */
|
||||
// exclude : {
|
||||
// /* path: an array of regular expressions in strings to match against */
|
||||
// path: '',
|
||||
// },
|
||||
|
||||
/* Which modules to exclusively include (array of regular expressions in strings)
|
||||
dependency-cruiser will skip everything not matching this pattern
|
||||
*/
|
||||
// includeOnly : [''],
|
||||
|
||||
/* List of module systems to cruise.
|
||||
When left out dependency-cruiser will fall back to the list of _all_
|
||||
module systems it knows of. It's the default because it's the safe option
|
||||
It might come at a performance penalty, though.
|
||||
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
|
||||
|
||||
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
|
||||
are widely used, you can limit the moduleSystems to those.
|
||||
*/
|
||||
|
||||
// moduleSystems: ['cjs', 'es6'],
|
||||
|
||||
/*
|
||||
false: don't look at JSDoc imports (the default)
|
||||
true: dependency-cruiser will detect dependencies in JSDoc-style
|
||||
import statements. Implies "parser": "tsc", so the dependency-cruiser
|
||||
will use the typescript parser for JavaScript files.
|
||||
|
||||
For this to work the typescript compiler will need to be installed in the
|
||||
same spot as you're running dependency-cruiser from.
|
||||
*/
|
||||
// detectJSDocImports: true,
|
||||
|
||||
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
|
||||
to open it on your online repo or `vscode://file/${process.cwd()}/` to
|
||||
open it in visual studio code),
|
||||
*/
|
||||
// prefix: `vscode://file/${process.cwd()}/`,
|
||||
|
||||
/* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
|
||||
true: also detect dependencies that only exist before typescript-to-javascript compilation
|
||||
"specify": for each dependency identify whether it only exists before compilation or also after
|
||||
*/
|
||||
// tsPreCompilationDeps: false,
|
||||
|
||||
/* list of extensions to scan that aren't javascript or compile-to-javascript.
|
||||
Empty by default. Only put extensions in here that you want to take into
|
||||
account that are _not_ parsable.
|
||||
*/
|
||||
// extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
|
||||
|
||||
/* if true combines the package.jsons found from the module up to the base
|
||||
folder the cruise is initiated from. Useful for how (some) mono-repos
|
||||
manage dependencies & dependency definitions.
|
||||
*/
|
||||
// combinedDependencies: false,
|
||||
|
||||
/* if true leave symlinks untouched, otherwise use the realpath */
|
||||
// preserveSymlinks: false,
|
||||
|
||||
/* TypeScript project file ('tsconfig.json') to use for
|
||||
(1) compilation and
|
||||
(2) resolution (e.g. with the paths property)
|
||||
|
||||
The (optional) fileName attribute specifies which file to take (relative to
|
||||
dependency-cruiser's current working directory). When not provided
|
||||
defaults to './tsconfig.json'.
|
||||
*/
|
||||
tsConfig: {
|
||||
fileName: 'jsconfig.json'
|
||||
},
|
||||
|
||||
/* Webpack configuration to use to get resolve options from.
|
||||
|
||||
The (optional) fileName attribute specifies which file to take (relative
|
||||
to dependency-cruiser's current working directory. When not provided defaults
|
||||
to './webpack.conf.js'.
|
||||
|
||||
The (optional) `env` and `arguments` attributes contain the parameters
|
||||
to be passed if your webpack config is a function and takes them (see
|
||||
webpack documentation for details)
|
||||
*/
|
||||
// webpackConfig: {
|
||||
// fileName: 'webpack.config.js',
|
||||
// env: {},
|
||||
// arguments: {}
|
||||
// },
|
||||
|
||||
/* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
|
||||
for compilation
|
||||
*/
|
||||
// babelConfig: {
|
||||
// fileName: '.babelrc',
|
||||
// },
|
||||
|
||||
/* List of strings you have in use in addition to cjs/ es6 requires
|
||||
& imports to declare module dependencies. Use this e.g. if you've
|
||||
re-declared require, use a require-wrapper or use window.require as
|
||||
a hack.
|
||||
*/
|
||||
// exoticRequireStrings: [],
|
||||
|
||||
/* options to pass on to enhanced-resolve, the package dependency-cruiser
|
||||
uses to resolve module references to disk. The values below should be
|
||||
suitable for most situations
|
||||
|
||||
If you use webpack: you can also set these in webpack.conf.js. The set
|
||||
there will override the ones specified here.
|
||||
*/
|
||||
enhancedResolveOptions: {
|
||||
/* What to consider as an 'exports' field in package.jsons */
|
||||
exportsFields: ["exports"],
|
||||
/* List of conditions to check for in the exports field.
|
||||
Only works when the 'exportsFields' array is non-empty.
|
||||
*/
|
||||
conditionNames: ["import", "require", "node", "default", "types"],
|
||||
/* The extensions, by default are the same as the ones dependency-cruiser
|
||||
can access (run `npx depcruise --info` to see which ones that are in
|
||||
_your_ environment). If that list is larger than you need you can pass
|
||||
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
|
||||
up module resolution, which is the most expensive step.
|
||||
*/
|
||||
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
|
||||
/* What to consider a 'main' field in package.json */
|
||||
mainFields: ["module", "main", "types", "typings"],
|
||||
/* A list of alias fields in package.jsons
|
||||
|
||||
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
|
||||
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
|
||||
documentation.
|
||||
|
||||
Defaults to an empty array (= don't use alias fields).
|
||||
*/
|
||||
// aliasFields: ["browser"],
|
||||
},
|
||||
|
||||
/* skipAnalysisNotInRules will make dependency-cruiser execute
|
||||
analysis strictly necessary for checking the rule set only.
|
||||
|
||||
See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#skipanalysisnotinrules
|
||||
for details
|
||||
*/
|
||||
skipAnalysisNotInRules: true,
|
||||
|
||||
reporterOptions: {
|
||||
dot: {
|
||||
/* pattern of modules that can be consolidated in the detailed
|
||||
graphical dependency graph. The default pattern in this configuration
|
||||
collapses everything in node_modules to one folder deep so you see
|
||||
the external modules, but their innards.
|
||||
*/
|
||||
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
||||
|
||||
/* Options to tweak the appearance of your graph.See
|
||||
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
|
||||
for details and some examples. If you don't specify a theme
|
||||
dependency-cruiser falls back to a built-in one.
|
||||
*/
|
||||
// theme: {
|
||||
// graph: {
|
||||
// /* splines: "ortho" gives straight lines, but is slow on big graphs
|
||||
// splines: "true" gives bezier curves (fast, not as nice as ortho)
|
||||
// */
|
||||
// splines: "true"
|
||||
// },
|
||||
// }
|
||||
},
|
||||
archi: {
|
||||
/* pattern of modules that can be consolidated in the high level
|
||||
graphical dependency graph. If you use the high level graphical
|
||||
dependency graph reporter (`archi`) you probably want to tweak
|
||||
this collapsePattern to your situation.
|
||||
*/
|
||||
collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)',
|
||||
|
||||
/* Options to tweak the appearance of your graph. If you don't specify a
|
||||
theme for 'archi' dependency-cruiser will use the one specified in the
|
||||
dot section above and otherwise use the default one.
|
||||
*/
|
||||
// theme: { },
|
||||
},
|
||||
"text": {
|
||||
"highlightFocused": true
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
// generated: dependency-cruiser@16.10.1 on 2025-04-24T20:50:32.854Z
|
||||
@@ -1 +1,2 @@
|
||||
VITE_WS_URL=wss://ws.beta.dexorder.trade
|
||||
VITE_WS_URL=wss://ws.dexorder.com
|
||||
VITE_SHARE_URL=https://app.dexorder.com
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
VITE_WS_URL=ws://localhost:3001
|
||||
REQUIRE_AUTH=NOAUTH
|
||||
VITE_SHARE_URL=http://localhost:3001
|
||||
VITE_REQUIRE_APPROVAL=NO
|
||||
|
||||
2
bin/depcruise
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
npx depcruise src
|
||||
@@ -12,7 +12,16 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron&family=Saira+Semi+Condensed&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-L6F3Z6SBC7"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-L6F3Z6SBC7');
|
||||
</script>
|
||||
<div id="app"></div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script>
|
||||
<script src="/charting_library/charting_library.js"></script>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -13,15 +13,19 @@
|
||||
"lint": "eslint . --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@isaacs/ttlcache": "^1.4.1",
|
||||
"@mdi/font": "6.9.96",
|
||||
"color": "^4.2.3",
|
||||
"core-js": "^3.29.0",
|
||||
"ethers": "^6.7.1",
|
||||
"flexsearch": "^0.7.43",
|
||||
"lru-cache": "^11.0.2",
|
||||
"luxon": "^3.4.4",
|
||||
"pinia": "2.1.6",
|
||||
"pinia-plugin-persistedstate": "^4.1.3",
|
||||
"roboto-fontface": "*",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"uuid": "^11.1.0",
|
||||
"vue": "^3.2.0",
|
||||
"vue-router": "^4.0.0",
|
||||
"vue-scroll-picker": "^1.2.2",
|
||||
@@ -30,6 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"dependency-cruiser": "^16.10.1",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"sass": "^1.60.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 611 B |
|
Before Width: | Height: | Size: 122 KiB |
BIN
public/howitworks.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
1
public/logo/dexorder_full_darkmode.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1487.72 264.73"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#35d721;}</style></defs><title>Asset 13</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_2-2" data-name="Layer 2"><path class="cls-1" d="M305.33,264.49c-37.72,0-56.58-20.37-56.58-60.73v-72c0-40.37,18.86-60.74,56.58-60.74h62.25V37.6L396.25.05V264.49ZM367.58,100.4H306.47C286.85,100.4,277,111,277,132.84V203c0,21.51,9.81,32.45,29.43,32.45h61.11Z"/><path class="cls-1" d="M472.26,264.59c-37.72,0-56.58-20.37-56.58-60.74v-72c0-40.36,18.86-60.73,56.58-60.73h34.33c38.1,0,57,20.37,57,60.73v47.91H443.22V203.1c0,21.5,9.8,32.44,29,32.44h91.29l-18.75,29Zm64.13-133.54c0-21.51-9.81-32.45-29.42-32.45H472.26c-19.24,0-29,10.94-29,32.45v23.76h93.17Z"/><path class="cls-1" d="M789,264.73c-50.17,0-62.62-16.22-62.62-65.64V136.85c0-51.68,15.09-65.64,62.62-65.64H816.2c49.8,0,62.62,16.6,62.62,65.64v62.24c0,43.76-12.45,65.64-51.68,65.64ZM850.53,132c0-20-6.79-31.31-24.52-31.31H778.86c-16.22,0-24.15,8.67-24.15,26.78v77c0,19.61,6.42,31.31,24.15,31.31H826c16.22,0,24.52-9.06,24.52-26.79Z"/><path class="cls-1" d="M927.06,263.88l-.4-131.68c0-21.88,9.81-32.44,29.42-32.44h61.12V70.34H955c-37.72,0-56.58,20.37-56.58,60.73l-.12,132.81Z"/><path class="cls-1" d="M1397.58,264.45l-.4-131.67c0-21.88,9.81-32.45,29.42-32.45h61.12V70.91h-62.25c-37.72,0-56.58,20.37-56.58,60.73l-.12,132.81Z"/><polygon class="cls-1" points="721.45 71.07 689.08 71.07 644.02 142.86 598.4 71.07 567.54 71.07 628.31 167.89 567.54 264.71 598.4 264.71 644.02 192.92 689.08 264.71 721.45 264.71 659.93 167.89 721.45 71.07"/><path class="cls-1" d="M1258.05,264.44c-37.72,0-56.58-20.37-56.58-60.73V131.65c0-40.36,18.86-60.73,56.58-60.73h34.33c38.1,0,57,20.37,57,60.73v47.91H1229V203c0,21.5,9.81,32.44,29,32.44h91.29v29.05Zm64.13-133.54c0-21.5-9.81-32.44-29.42-32.44h-34.71c-19.24,0-29,10.94-29,32.44v23.77h93.18Z"/><path class="cls-1" d="M1091.12,264.44c-37.72,0-56.58-20.37-56.58-60.73V131.65c0-40.36,18.86-60.73,56.58-60.73h62.25V37.55L1182,0V264.44Zm62.25-164.1h-61.12c-19.61,0-29.42,10.57-29.42,32.45V203c0,21.5,9.81,32.44,29.42,32.44h61.12Z"/><polygon class="cls-2" points="99.15 0.05 0 129.65 53.91 129.65 53.78 248.92 145.01 129.65 197.74 129.65 99.15 0.05"/><polygon class="cls-2" points="95.07 264.49 145.02 199.22 144.98 264.49 95.07 264.49"/><polygon class="cls-2" points="53.79 264.49 145.04 145.2 145.04 156.67 62.55 264.49 53.79 264.49"/><polygon class="cls-2" points="74.39 264.49 145.02 172.16 145.02 183.62 83.16 264.49 74.39 264.49"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1
public/logo/dexorder_full_lightmode.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1487.72 264.73"><defs><style>.cls-1{fill:#0f0f0f;}.cls-2{fill:#35d721;}</style></defs><title>Asset 12</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_2-2" data-name="Layer 2"><path class="cls-1" d="M305.33,264.49c-37.72,0-56.58-20.37-56.58-60.73v-72c0-40.37,18.86-60.74,56.58-60.74h62.25V37.6L396.25.05V264.49ZM367.58,100.4H306.47C286.85,100.4,277,111,277,132.84V203c0,21.51,9.81,32.45,29.43,32.45h61.11Z"/><path class="cls-1" d="M472.26,264.59c-37.72,0-56.58-20.37-56.58-60.74v-72c0-40.36,18.86-60.73,56.58-60.73h34.33c38.1,0,57,20.37,57,60.73v47.91H443.22V203.1c0,21.5,9.8,32.44,29,32.44h91.29l-18.75,29Zm64.13-133.54c0-21.51-9.81-32.45-29.42-32.45H472.26c-19.24,0-29,10.94-29,32.45v23.76h93.17Z"/><path class="cls-1" d="M789,264.73c-50.17,0-62.62-16.22-62.62-65.64V136.85c0-51.68,15.09-65.64,62.62-65.64H816.2c49.8,0,62.62,16.6,62.62,65.64v62.24c0,43.76-12.45,65.64-51.68,65.64ZM850.53,132c0-20-6.79-31.31-24.52-31.31H778.86c-16.22,0-24.15,8.67-24.15,26.78v77c0,19.61,6.42,31.31,24.15,31.31H826c16.22,0,24.52-9.06,24.52-26.79Z"/><path class="cls-1" d="M927.06,263.88l-.4-131.68c0-21.88,9.81-32.44,29.42-32.44h61.12V70.34H955c-37.72,0-56.58,20.37-56.58,60.73l-.12,132.81Z"/><path class="cls-1" d="M1397.58,264.45l-.4-131.67c0-21.88,9.81-32.45,29.42-32.45h61.12V70.91h-62.25c-37.72,0-56.58,20.37-56.58,60.73l-.12,132.81Z"/><polygon class="cls-1" points="721.45 71.07 689.08 71.07 644.02 142.86 598.4 71.07 567.54 71.07 628.31 167.89 567.54 264.71 598.4 264.71 644.02 192.92 689.08 264.71 721.45 264.71 659.93 167.89 721.45 71.07"/><path class="cls-1" d="M1258.05,264.44c-37.72,0-56.58-20.37-56.58-60.73V131.65c0-40.36,18.86-60.73,56.58-60.73h34.33c38.1,0,57,20.37,57,60.73v47.91H1229V203c0,21.5,9.81,32.44,29,32.44h91.29v29.05Zm64.13-133.54c0-21.5-9.81-32.44-29.42-32.44h-34.71c-19.24,0-29,10.94-29,32.44v23.77h93.18Z"/><path class="cls-1" d="M1091.12,264.44c-37.72,0-56.58-20.37-56.58-60.73V131.65c0-40.36,18.86-60.73,56.58-60.73h62.25V37.55L1182,0V264.44Zm62.25-164.1h-61.12c-19.61,0-29.42,10.57-29.42,32.45V203c0,21.5,9.81,32.44,29.42,32.44h61.12Z"/><polygon class="cls-2" points="99.15 0.05 0 129.65 53.91 129.65 53.78 248.92 145.01 129.65 197.74 129.65 99.15 0.05"/><polygon class="cls-2" points="95.07 264.49 145.02 199.22 144.98 264.49 95.07 264.49"/><polygon class="cls-2" points="53.79 264.49 145.04 145.2 145.04 156.67 62.55 264.49 53.79 264.49"/><polygon class="cls-2" points="74.39 264.49 145.02 172.16 145.02 183.62 83.16 264.49 74.39 264.49"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/logo/ico_black_clip.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
public/logo/ico_black_fill.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/logo/ico_green_clip.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
public/logo/ico_green_fill.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/logo/ico_white_clip.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
public/logo/ico_white_fill.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
11
public/uniswap-logo.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="641" height="640" viewBox="0 0 641 640" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M224.534 123.226C218.692 122.32 218.445 122.213 221.195 121.791C226.464 120.98 238.905 122.085 247.479 124.123C267.494 128.881 285.707 141.069 305.148 162.714L310.313 168.465L317.701 167.277C348.828 162.275 380.493 166.25 406.978 178.485C414.264 181.851 425.752 188.552 427.187 190.274C427.645 190.822 428.485 194.355 429.053 198.124C431.02 211.164 430.036 221.16 426.047 228.625C423.877 232.688 423.756 233.975 425.215 237.452C426.38 240.227 429.627 242.28 432.843 242.276C439.425 242.267 446.509 231.627 449.791 216.823L451.095 210.943L453.678 213.868C467.846 229.92 478.974 251.811 480.885 267.393L481.383 271.455L479.002 267.762C474.903 261.407 470.785 257.08 465.512 253.591C456.006 247.301 445.955 245.161 419.337 243.758C395.296 242.491 381.69 240.438 368.198 236.038C345.244 228.554 333.672 218.587 306.405 182.812C294.294 166.923 286.808 158.131 279.362 151.051C262.442 134.964 245.816 126.527 224.534 123.226Z" fill="#FF007A"/>
|
||||
<path d="M432.61 158.704C433.215 148.057 434.659 141.033 437.562 134.62C438.711 132.081 439.788 130.003 439.954 130.003C440.12 130.003 439.621 131.877 438.844 134.167C436.733 140.392 436.387 148.905 437.84 158.811C439.686 171.379 440.735 173.192 454.019 186.769C460.25 193.137 467.497 201.168 470.124 204.616L474.901 210.886L470.124 206.405C464.282 200.926 450.847 190.24 447.879 188.712C445.89 187.688 445.594 187.705 444.366 188.927C443.235 190.053 442.997 191.744 442.84 199.741C442.596 212.204 440.897 220.204 436.797 228.203C434.58 232.529 434.23 231.606 436.237 226.723C437.735 223.077 437.887 221.474 437.876 209.408C437.853 185.167 434.975 179.339 418.097 169.355C413.821 166.826 406.776 163.178 402.442 161.249C398.107 159.32 394.664 157.639 394.789 157.514C395.267 157.038 411.727 161.842 418.352 164.39C428.206 168.181 429.833 168.672 431.03 168.215C431.832 167.909 432.22 165.572 432.61 158.704Z" fill="#FF007A"/>
|
||||
<path d="M235.883 200.175C224.022 183.846 216.684 158.809 218.272 140.093L218.764 134.301L221.463 134.794C226.534 135.719 235.275 138.973 239.369 141.459C250.602 148.281 255.465 157.263 260.413 180.328C261.862 187.083 263.763 194.728 264.638 197.317C266.047 201.483 271.369 211.214 275.696 217.534C278.813 222.085 276.743 224.242 269.853 223.62C259.331 222.67 245.078 212.834 235.883 200.175Z" fill="#FF007A"/>
|
||||
<path d="M418.223 321.707C362.793 299.389 343.271 280.017 343.271 247.331C343.271 242.521 343.437 238.585 343.638 238.585C343.84 238.585 345.985 240.173 348.404 242.113C359.644 251.128 372.231 254.979 407.076 260.062C427.58 263.054 439.119 265.47 449.763 269C483.595 280.22 504.527 302.99 509.518 334.004C510.969 343.016 510.118 359.915 507.766 368.822C505.91 375.857 500.245 388.537 498.742 389.023C498.325 389.158 497.917 387.562 497.81 385.389C497.24 373.744 491.355 362.406 481.472 353.913C470.235 344.257 455.137 336.569 418.223 321.707Z" fill="#FF007A"/>
|
||||
<path d="M379.31 330.978C378.615 326.846 377.411 321.568 376.633 319.25L375.219 315.036L377.846 317.985C381.481 322.065 384.354 327.287 386.789 334.241C388.647 339.549 388.856 341.127 388.842 349.753C388.828 358.221 388.596 359.996 386.88 364.773C384.174 372.307 380.816 377.649 375.181 383.383C365.056 393.688 352.038 399.393 333.253 401.76C329.987 402.171 320.47 402.864 312.103 403.299C291.016 404.395 277.138 406.661 264.668 411.04C262.875 411.67 261.274 412.052 261.112 411.89C260.607 411.388 269.098 406.326 276.111 402.948C285.999 398.185 295.842 395.586 317.897 391.913C328.792 390.098 340.043 387.897 342.9 387.021C369.88 378.749 383.748 357.402 379.31 330.978Z" fill="#FF007A"/>
|
||||
<path d="M404.719 376.105C397.355 360.273 395.664 344.988 399.698 330.732C400.13 329.209 400.824 327.962 401.242 327.962C401.659 327.962 403.397 328.902 405.103 330.05C408.497 332.335 415.303 336.182 433.437 346.069C456.065 358.406 468.966 367.959 477.74 378.873C485.423 388.432 490.178 399.318 492.467 412.593C493.762 420.113 493.003 438.206 491.074 445.778C484.99 469.653 470.85 488.406 450.682 499.349C447.727 500.952 445.075 502.269 444.788 502.275C444.501 502.28 445.577 499.543 447.18 496.191C453.965 482.009 454.737 468.214 449.608 452.859C446.467 443.457 440.064 431.985 427.135 412.596C412.103 390.054 408.417 384.054 404.719 376.105Z" fill="#FF007A"/>
|
||||
<path d="M196.519 461.525C217.089 444.157 242.682 431.819 265.996 428.032C276.043 426.399 292.78 427.047 302.084 429.428C316.998 433.245 330.338 441.793 337.276 451.978C344.057 461.932 346.966 470.606 349.995 489.906C351.189 497.519 352.489 505.164 352.882 506.895C355.156 516.897 359.583 524.892 365.067 528.907C373.779 535.283 388.78 535.68 403.536 529.924C406.041 528.947 408.215 528.271 408.368 528.424C408.903 528.955 401.473 533.93 396.23 536.548C389.177 540.071 383.568 541.434 376.115 541.434C362.6 541.434 351.379 534.558 342.016 520.539C340.174 517.78 336.032 509.516 332.813 502.176C322.928 479.628 318.046 472.759 306.568 465.242C296.579 458.701 283.697 457.53 274.006 462.282C261.276 468.523 257.724 484.791 266.842 495.101C270.465 499.198 277.223 502.732 282.749 503.419C293.086 504.705 301.97 496.841 301.97 486.404C301.97 479.627 299.365 475.76 292.808 472.801C283.852 468.76 274.226 473.483 274.272 481.897C274.292 485.484 275.854 487.737 279.45 489.364C281.757 490.408 281.811 490.491 279.929 490.1C271.712 488.396 269.787 478.49 276.394 471.913C284.326 464.018 300.729 467.502 306.362 478.279C308.728 482.805 309.003 491.82 306.94 497.264C302.322 509.448 288.859 515.855 275.201 512.368C265.903 509.994 262.117 507.424 250.906 495.876C231.425 475.809 223.862 471.92 195.777 467.536L190.395 466.696L196.519 461.525Z" fill="#FF007A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.6202 12.0031C114.678 90.9638 214.977 213.901 219.957 220.784C224.068 226.467 222.521 231.576 215.478 235.58C211.561 237.807 203.508 240.063 199.476 240.063C194.916 240.063 189.779 237.867 186.038 234.318C183.393 231.81 172.721 215.874 148.084 177.646C129.233 148.396 113.457 124.131 113.027 123.725C112.032 122.785 112.049 122.817 146.162 183.854C167.582 222.181 174.813 235.731 174.813 237.543C174.813 241.229 173.808 243.166 169.261 248.238C161.681 256.694 158.293 266.195 155.847 285.859C153.104 307.902 145.394 323.473 124.026 350.122C111.518 365.722 109.471 368.581 106.315 374.869C102.339 382.786 101.246 387.221 100.803 397.219C100.335 407.79 101.247 414.619 104.477 424.726C107.304 433.575 110.255 439.417 117.8 451.104C124.311 461.188 128.061 468.683 128.061 471.614C128.061 473.947 128.506 473.95 138.596 471.672C162.741 466.219 182.348 456.629 193.375 444.877C200.199 437.603 201.801 433.586 201.853 423.618C201.887 417.098 201.658 415.733 199.896 411.982C197.027 405.877 191.804 400.801 180.292 392.932C165.209 382.621 158.767 374.32 156.987 362.904C155.527 353.537 157.221 346.928 165.565 329.44C174.202 311.338 176.342 303.624 177.79 285.378C178.725 273.589 180.02 268.94 183.407 265.209C186.939 261.317 190.119 260 198.861 258.805C213.113 256.858 222.188 253.171 229.648 246.297C236.119 240.334 238.827 234.588 239.243 225.938L239.558 219.382L235.942 215.166C222.846 199.896 40.85 0 40.044 0C39.8719 0 44.1813 5.40178 49.6202 12.0031ZM135.412 409.18C138.373 403.937 136.8 397.195 131.847 393.902C127.167 390.79 119.897 392.256 119.897 396.311C119.897 397.548 120.582 398.449 122.124 399.243C124.72 400.579 124.909 402.081 122.866 405.152C120.797 408.262 120.964 410.996 123.337 412.854C127.162 415.849 132.576 414.202 135.412 409.18Z" fill="#FF007A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M248.552 262.244C241.862 264.299 235.358 271.39 233.344 278.826C232.116 283.362 232.813 291.319 234.653 293.776C237.625 297.745 240.499 298.791 248.282 298.736C263.518 298.63 276.764 292.095 278.304 283.925C279.567 277.229 273.749 267.948 265.736 263.874C261.601 261.772 252.807 260.938 248.552 262.244ZM266.364 276.172C268.714 272.834 267.686 269.225 263.69 266.785C256.08 262.138 244.571 265.983 244.571 273.173C244.571 276.752 250.572 280.656 256.074 280.656C259.735 280.656 264.746 278.473 266.364 276.172Z" fill="#FF007A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
@@ -1,8 +1,14 @@
|
||||
<template>
|
||||
<router-view/>
|
||||
<support-chat/>
|
||||
<welcome-dialog v-model="prefs.newbie"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SupportChat from "@/components/SupportChat.vue";
|
||||
import WelcomeDialog from "@/components/WelcomeDialog.vue";
|
||||
import {usePrefStore} from "@/store/store.js";
|
||||
|
||||
const prefs = usePrefStore()
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {provider as walletProvider} from "@/blockchain/provider.js";
|
||||
import {ethers} from "ethers";
|
||||
import {AbiURLCache} from "../common.js";
|
||||
import {provider as walletProvider} from "@/blockchain/wallet.js";
|
||||
|
||||
export const abiCache = new AbiURLCache('/contract/out/')
|
||||
|
||||
@@ -31,7 +31,7 @@ export async function queryHelperContract(helper, provider) {
|
||||
// use newContract(addr, 'IVaultImpl', provider, 'IVault') to get the ABI from IVault.sol/IVaultImpl.json
|
||||
export async function newContract(addr, name, provider) {
|
||||
const abi = await abiCache.get(name)
|
||||
console.log(`${name} ABI`, abi)
|
||||
// console.log(`${name} ABI`, abi)
|
||||
return new ethers.Contract(addr, abi, provider)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,13 +10,31 @@ export function subOHLC( chainId, pool, period ) {
|
||||
// console.log('subOHLC', chainId, pool, period, ckey, ohlcSubCounts[ckey])
|
||||
if (!(ckey in ohlcSubCounts) || ohlcSubCounts[ckey] === 0) {
|
||||
ohlcSubCounts[ckey] = 1
|
||||
console.log('subscribing OHLCs', chainId, key)
|
||||
// console.log('subscribing OHLCs', chainId, key)
|
||||
socket.emit('subOHLCs', chainId, [key])
|
||||
} else
|
||||
ohlcSubCounts[ckey]++
|
||||
}
|
||||
|
||||
|
||||
export function refreshOHLCSubs() {
|
||||
const keys = []
|
||||
let chainId = null
|
||||
for (const key of Object.keys(ohlcSubCounts)) {
|
||||
const [curChainId, pool, period] = key.split('|')
|
||||
if (chainId === null)
|
||||
chainId = curChainId
|
||||
else if (chainId !== curChainId) {
|
||||
console.error('refreshOHLCSubs: mixed chainIds')
|
||||
continue
|
||||
}
|
||||
keys.push(`${pool}|${period}`)
|
||||
}
|
||||
// console.log('refreshing OHLC subs', keys)
|
||||
socket.emit('subOHLCs', chainId, keys)
|
||||
}
|
||||
|
||||
|
||||
export function unsubOHLC( chainId, pool, period ) {
|
||||
const key = `${pool}|${period}`
|
||||
const ckey = `${chainId}|${key}`
|
||||
@@ -27,7 +45,7 @@ export function unsubOHLC( chainId, pool, period ) {
|
||||
} else {
|
||||
ohlcSubCounts[ckey]--
|
||||
if (ohlcSubCounts[ckey] === 0) {
|
||||
console.log('unsubscribing OHLCs', chainId, key)
|
||||
// console.log('unsubscribing OHLCs', chainId, key)
|
||||
// noinspection JSCheckFunctionSignatures
|
||||
socket.emit('unsubOHLCs', chainId, [key])
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {uint32max, uint64max} from "@/misc.js";
|
||||
import {decodeIEE754, encodeIEE754} from "@/common.js";
|
||||
import {encodeIEE754} from "@/common.js";
|
||||
|
||||
|
||||
export const MAX_FRACTION = 65535;
|
||||
export const NO_CONDITIONAL_ORDER = uint64max;
|
||||
@@ -7,6 +8,10 @@ export const NO_OCO = uint64max;
|
||||
export const DISTANT_PAST = 0
|
||||
export const DISTANT_FUTURE = uint32max
|
||||
|
||||
export const MIN_EXECUTION_TIME = 60 // give at least one full minute for each tranche to trigger
|
||||
export const DEFAULT_SLIPPAGE = 0.0030;
|
||||
export const MIN_SLIPPAGE = 0.0001;
|
||||
|
||||
// struct SwapOrder {
|
||||
// address tokenIn;
|
||||
// address tokenOut;
|
||||
@@ -31,7 +36,7 @@ export function newOrder(tokenIn, tokenOut, exchange, fee, amount, amountIsInput
|
||||
if (!tranches)
|
||||
tranches = [newTranche({marketOrder: true})] // todo this is just a swap: issue warning?
|
||||
if( minFillAmount === null )
|
||||
minFillAmount = amount / 100n // default to min trade size of 1%
|
||||
minFillAmount = amount / 1000n // default to min trade size of 0.1%
|
||||
return {
|
||||
tokenIn, tokenOut, route:{exchange, fee},
|
||||
amount, minFillAmount, amountIsInput,
|
||||
@@ -81,11 +86,13 @@ export function newTranche({
|
||||
rateLimitFraction = 0,
|
||||
rateLimitPeriod = 0,
|
||||
} = {}) {
|
||||
if( minIntercept === 0 && minSlope === 0 && maxIntercept === 0 && maxSlope === 0 )
|
||||
marketOrder = true
|
||||
if( marketOrder ) {
|
||||
if (minIntercept !== 0 || minSlope !== 0 || maxIntercept !== 0 || maxSlope !== 0)
|
||||
console.warn('Ignoring line information in a market order')
|
||||
throw Error('Cannot set line information on a market order')
|
||||
if (slippage === 0)
|
||||
slippage = DEFAULT_SLIPPAGE
|
||||
else if (slippage < MIN_SLIPPAGE)
|
||||
slippage = MIN_SLIPPAGE
|
||||
minIntercept = encodeIEE754(slippage) // this is the slippage field for market orders
|
||||
minSlope = 0
|
||||
maxIntercept = 0
|
||||
@@ -137,6 +144,7 @@ export function parseElaboratedOrderStatus(chainId, status) {
|
||||
|
||||
|
||||
export function parseOrderStatus(chainId, status) {
|
||||
// console.log('parseOrderStatus', status)
|
||||
let [
|
||||
order,
|
||||
fillFeeHalfBps,
|
||||
@@ -151,31 +159,35 @@ export function parseOrderStatus(chainId, status) {
|
||||
order = parseOrder(order)
|
||||
filledIn = BigInt(filledIn)
|
||||
filledOut = BigInt(filledOut)
|
||||
trancheStatus = trancheStatus.map((obj)=>parseTrancheStatus(obj))
|
||||
const filled = order.amountIsInput ? filledIn : filledOut
|
||||
trancheStatus = trancheStatus.map((obj)=>parseTrancheStatus(obj, order.amountIsInput))
|
||||
const result = {
|
||||
chainId, order, fillFeeHalfBps, state, startTime, startPrice, ocoGroup,
|
||||
filledIn, filledOut, trancheStatus,
|
||||
filledIn, filledOut, filled, trancheStatus,
|
||||
};
|
||||
console.log('SwapOrderStatus', result)
|
||||
// console.log('SwapOrderStatus', result)
|
||||
return result
|
||||
}
|
||||
|
||||
function parseFill(obj) {
|
||||
let [tx, time, filledIn, filledOut, fee] = obj
|
||||
// time = new Date(time * 1000)
|
||||
filledIn = BigInt(filledIn)
|
||||
filledOut = BigInt(filledOut)
|
||||
const filled = obj.amountIsInput ? filledIn : filledOut
|
||||
fee = BigInt(fee)
|
||||
return {tx, time, filledIn, filledOut, fee}
|
||||
return {tx, time, filledIn, filledOut, filled, fee}
|
||||
}
|
||||
|
||||
function parseTrancheStatus(obj) {
|
||||
function parseTrancheStatus(obj, amountIsInput) {
|
||||
let [filledIn, filledOut, activationTime, startTime, endTime, rawFills,] = obj
|
||||
filledIn = BigInt(filledIn)
|
||||
filledOut = BigInt(filledOut)
|
||||
const fills = []
|
||||
for (const fill of rawFills)
|
||||
fills.push(parseFill(fill))
|
||||
return {filledIn, filledOut, activationTime, startTime, endTime, fills}
|
||||
fills.push(parseFill(fill, amountIsInput))
|
||||
const filled = amountIsInput ? filledIn : filledOut
|
||||
return {filledIn, filledOut, filled, activationTime, startTime, endTime, fills}
|
||||
}
|
||||
|
||||
export function parseOrder(order) {
|
||||
@@ -245,4 +257,3 @@ export function parseFeeSchedule(sched) {
|
||||
fillFee: fillFeeHalfBps/1_000_000 // fillFee is a multiplier on the filled volume. 0.0001 = 0.1% of the output token taken as a fee
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {socket} from "@/socket.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {Exchange} from "@/blockchain/orderlib.js";
|
||||
import {uniswapV3PoolAddress} from "@/blockchain/uniswap.js";
|
||||
import {FixedNumber} from "ethers";
|
||||
import {provider} from "@/blockchain/wallet.js";
|
||||
import {newContract} from "@/blockchain/contract.js";
|
||||
import {provider} from "@/blockchain/provider.js";
|
||||
import {socket} from "@/socket.js";
|
||||
|
||||
const subscriptionCounts = {} // key is route and value is a subscription counter
|
||||
export const WIDE_PRICE_FORMAT = {decimals:38, width:512, signed:false}; // 38 decimals is 127 bits
|
||||
|
||||
3
src/blockchain/provider.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export let provider = null
|
||||
|
||||
export function setProvider(p) {provider = p}
|
||||
@@ -2,7 +2,8 @@ import {Exchange} from "@/blockchain/orderlib.js";
|
||||
import {useOrderStore, useStore} from "@/store/store.js";
|
||||
import {queryHelperContract} from "@/blockchain/contract.js";
|
||||
import {SingletonCoroutine} from "@/misc.js";
|
||||
import {provider} from "@/blockchain/wallet.js";
|
||||
|
||||
import {provider} from "@/blockchain/provider.js";
|
||||
|
||||
|
||||
export async function findRoute(helper, chainId, tokenA, tokenB) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {socket} from "@/socket.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {metadataMap} from "@/version.js";
|
||||
import {provider} from "@/blockchain/wallet.js";
|
||||
import {newContract} from "@/blockchain/contract.js";
|
||||
import {provider} from "@/blockchain/provider.js";
|
||||
import {socket} from "@/socket.js";
|
||||
|
||||
|
||||
// synchronous version may return null but will trigger a lookup
|
||||
@@ -31,7 +31,16 @@ export async function getToken(chainId, addr) {
|
||||
return found
|
||||
if (!(addr in s.tokens))
|
||||
await addExtraToken(chainId, addr)
|
||||
return s.tokens[addr]
|
||||
let result = s.tokens[addr]
|
||||
if (!result) {
|
||||
result = {
|
||||
n: addr,
|
||||
a: addr,
|
||||
s: addr,
|
||||
d: 0,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -61,21 +70,28 @@ export async function addExtraToken(chainId, addr) {
|
||||
}
|
||||
else {
|
||||
if( provider===null ) {
|
||||
console.log('warning: token lookup cancelled due to null provider', addr)
|
||||
console.warn('warning: token lookup cancelled due to null provider', addr)
|
||||
resolve(null)
|
||||
}
|
||||
else {
|
||||
const token = await newContract(addr, 'IERC20Metadata', provider)
|
||||
Promise.all( [token.name(), token.symbol(), token.decimals()] ).then((name,symbol,decimals)=>{
|
||||
info = {
|
||||
a: addr,
|
||||
n: name,
|
||||
s: symbol,
|
||||
d: decimals,
|
||||
for( let tries=1; tries<=5; tries++ ) {
|
||||
try {
|
||||
const token = await newContract(addr, 'IERC20Metadata', provider)
|
||||
const [name, symbol, decimals] = await Promise.all([token.name(), token.symbol(), token.decimals()])
|
||||
info = {
|
||||
a: addr,
|
||||
n: name,
|
||||
s: symbol,
|
||||
d: decimals,
|
||||
}
|
||||
s.addToken(chainId, info)
|
||||
resolve(info)
|
||||
break
|
||||
}
|
||||
s.addToken(chainId, info)
|
||||
resolve(info)
|
||||
})
|
||||
catch (e) {
|
||||
console.warn(`Could not lookup token ${addr}`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
270
src/blockchain/transaction.js
Normal file
@@ -0,0 +1,270 @@
|
||||
import {provider} from "@/blockchain/provider.js";
|
||||
import {TransactionState, TransactionType} from "@/blockchain/transactionDecl.js";
|
||||
import {sleep, uuid} from "@/misc.js";
|
||||
import {vaultContract} from "@/blockchain/contract.js";
|
||||
import {switchChain, useWalletStore} from "@/blockchain/wallet.js";
|
||||
import {toRaw} from "vue";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {placementFee} from "@/fees.js";
|
||||
import {router} from "@/router/router.js";
|
||||
|
||||
|
||||
export class Transaction {
|
||||
constructor(chainId, type) {
|
||||
this.id = uuid()
|
||||
this.type = type
|
||||
this.state = TransactionState.Created
|
||||
this.tx = null
|
||||
this.chainId = chainId
|
||||
this.owner = null
|
||||
this.vault = null
|
||||
this.error = null
|
||||
}
|
||||
|
||||
submit() {
|
||||
console.log('submitting transaction', this.type)
|
||||
const ws = useWalletStore();
|
||||
if ( ws.transaction !== null ) {
|
||||
console.error('Transaction already in progress', ws.transaction)
|
||||
return
|
||||
}
|
||||
ws.transaction = this
|
||||
}
|
||||
|
||||
// "propose" means attach the transaction to a specific vault
|
||||
propose(owner, vault) {
|
||||
console.log('transaction bind', owner, vault)
|
||||
if (this.vault !== null && this.vault !== vault) {
|
||||
this.failed('proposed vault did not match withdrawl vault', vault, this.vault)
|
||||
return
|
||||
}
|
||||
this.owner = owner
|
||||
this.vault = vault
|
||||
this.send().catch(this.catchSend.bind(this))
|
||||
this.state = TransactionState.Proposed
|
||||
}
|
||||
|
||||
async createTx(vaultContract) {
|
||||
throw Error('unimplemented')
|
||||
}
|
||||
|
||||
signed(tx) {
|
||||
this.tx = tx
|
||||
this.state = TransactionState.Signed
|
||||
}
|
||||
|
||||
rejected() {
|
||||
this.tx = null
|
||||
this.chainId = null
|
||||
this.owner = null
|
||||
this.vault = null
|
||||
this.end(TransactionState.Rejected)
|
||||
console.log('transaction rejected', this.id)
|
||||
}
|
||||
|
||||
failed(e) {
|
||||
this.error = e
|
||||
this.end(TransactionState.Error)
|
||||
console.log('transaction failed', this.id, e)
|
||||
}
|
||||
|
||||
mined(receipt) {
|
||||
this.receipt = receipt
|
||||
this.end(TransactionState.Mined)
|
||||
console.log('mined transaction', this.id, receipt)
|
||||
}
|
||||
|
||||
isOpen() {
|
||||
return this.state >= TransactionState.Rejected
|
||||
}
|
||||
|
||||
isClosed() {
|
||||
return this.state < TransactionState.Rejected
|
||||
}
|
||||
|
||||
|
||||
end(state) {
|
||||
this.state = state
|
||||
useWalletStore().transaction = null
|
||||
}
|
||||
|
||||
|
||||
async send() {
|
||||
console.log('sendTransaction', this)
|
||||
try {
|
||||
await switchChain(this.chainId)
|
||||
} catch (e) {
|
||||
if (e.code === 4001) {
|
||||
this.rejected()
|
||||
return null
|
||||
} else {
|
||||
this.failed(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
let signer
|
||||
try {
|
||||
signer = await provider.getSigner();
|
||||
} catch (e) {
|
||||
// {
|
||||
// "code": -32002,
|
||||
// "message": "Already processing eth_requestAccounts. Please wait."
|
||||
// }
|
||||
this.rejected()
|
||||
return null
|
||||
}
|
||||
let contract
|
||||
try {
|
||||
contract = await vaultContract(this.vault, signer)
|
||||
} catch (e) {
|
||||
this.failed('vault contract was null while sending order transaction')
|
||||
return null
|
||||
}
|
||||
try {
|
||||
const tx = toRaw(await this.createTx(contract))
|
||||
this.signed(tx)
|
||||
tx.wait().then(this.mined.bind(this)).catch(this.failed.bind(this))
|
||||
console.log(`sent transaction`, tx)
|
||||
}
|
||||
catch (e) {
|
||||
this.failed(e)
|
||||
return null
|
||||
}
|
||||
return this.tx
|
||||
}
|
||||
|
||||
|
||||
catchSend(e) {
|
||||
this.error = e
|
||||
if (e.info?.error?.code === 4001) {
|
||||
console.log(`wallet refused transaction`, this.id)
|
||||
this.rejected()
|
||||
} else {
|
||||
this.failed(e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class PlaceOrderTransaction extends Transaction {
|
||||
constructor(chainId, order) {
|
||||
super(chainId, TransactionType.PlaceOrder)
|
||||
this.order = order
|
||||
this.placementTime = Date.now()/1000
|
||||
this.fee = null // dexorder place and gas fee total
|
||||
}
|
||||
|
||||
|
||||
async createTx(vaultContract) {
|
||||
const tries = 65;
|
||||
let i;
|
||||
let success = false
|
||||
for (i=0; !success && i<tries; i++ ) {
|
||||
try {
|
||||
console.error('getting placement fee', vaultContract, this.order)
|
||||
this.fee = await placementFee(vaultContract, this.order)
|
||||
success = true
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('failed to get placement fee', e)
|
||||
await sleep(1000)
|
||||
}
|
||||
}
|
||||
if (!success)
|
||||
throw Error('failed to get placement fee')
|
||||
console.log('placing order', this.id, this.fee, this.order)
|
||||
return await vaultContract.placeDexorder(this.order, {value: this.fee.reduce((a, b) => a + b)})
|
||||
}
|
||||
|
||||
|
||||
end(state) {
|
||||
super.end(state)
|
||||
if (state === TransactionState.Mined) {
|
||||
useChartOrderStore().resetOrders()
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
router.push({name: 'Status'})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CancelOrderTransaction extends Transaction {
|
||||
constructor(chainId, index) {
|
||||
super(chainId, TransactionType.CancelOrder)
|
||||
this.index = index
|
||||
}
|
||||
|
||||
|
||||
async createTx(vaultContract) {
|
||||
return await vaultContract.cancelDexorder(this.index)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CancelAllTransaction extends Transaction {
|
||||
constructor(chainId, vault) {
|
||||
super(chainId, TransactionType.CancelAll)
|
||||
this.vault = vault
|
||||
}
|
||||
|
||||
|
||||
async createTx(vaultContract) {
|
||||
return await vaultContract.cancelAllDexorders()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class WithdrawTransaction extends Transaction {
|
||||
constructor(chainId, vault, token, amount) {
|
||||
super(chainId, TransactionType.Withdraw)
|
||||
this.token = token
|
||||
this.amount = amount
|
||||
this.vault = vault
|
||||
}
|
||||
|
||||
|
||||
async createTx(vaultContract) {
|
||||
return await vaultContract['withdraw(address,uint256)'](this.token.a, this.amount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class WithdrawNativeTransaction extends Transaction {
|
||||
constructor(chainId, vault, amount) {
|
||||
super(chainId, TransactionType.WithdrawNative)
|
||||
this.amount = amount
|
||||
this.vault = vault
|
||||
}
|
||||
|
||||
|
||||
async createTx(vaultContract) {
|
||||
return await vaultContract['withdraw(uint256)'](this.amount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class WrapTransaction extends Transaction {
|
||||
constructor(chainId, vault, amount) {
|
||||
super(chainId, TransactionType.Wrap)
|
||||
this.vault = vault
|
||||
this.amount = amount
|
||||
}
|
||||
|
||||
async createTx(vaultContract) {
|
||||
return await vaultContract.wrap(this.amount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class UnwrapTransaction extends Transaction {
|
||||
constructor(chainId, vault, amount) {
|
||||
super(chainId, TransactionType.Unwrap)
|
||||
this.amount = amount
|
||||
}
|
||||
|
||||
async createTx(vaultContract) {
|
||||
return await vaultContract.unwrap(this.amount)
|
||||
}
|
||||
}
|
||||
17
src/blockchain/transactionDecl.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export const TransactionState = {
|
||||
Created: 0, // user requested a transaction
|
||||
Proposed: 1, // tx is sent to the wallet
|
||||
Signed: 2, // tx is awaiting blockchain mining
|
||||
Rejected: 3, // user refused to sign the tx
|
||||
Error: 3, // unknown error sending the tx to the wallet
|
||||
Mined: 4, // transaction has been confirmed on-chain
|
||||
}
|
||||
export const TransactionType = {
|
||||
PlaceOrder: 1,
|
||||
CancelOrder: 2,
|
||||
CancelAll: 3,
|
||||
Wrap: 4,
|
||||
Unwrap: 5,
|
||||
WithdrawNative: 6,
|
||||
Withdraw: 7,
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
import {provider, setProvider} from "@/blockchain/provider.js";
|
||||
import {BrowserProvider, ethers} from "ethers";
|
||||
import {useStore} from "@/store/store";
|
||||
import {socket} from "@/socket.js";
|
||||
import {SingletonCoroutine, timestamp, uuid} from "@/misc.js";
|
||||
import {errorSuggestsMissingVault, SingletonCoroutine} from "@/misc.js";
|
||||
import {newContract, vaultAddress, vaultContract} from "@/blockchain/contract.js";
|
||||
import {defineStore} from "pinia";
|
||||
import {ref} from "vue";
|
||||
import {computed, ref} from "vue";
|
||||
import {metadataMap, version} from "@/version.js";
|
||||
|
||||
|
||||
export let provider = null
|
||||
import {TransactionState, TransactionType} from "@/blockchain/transactionDecl.js";
|
||||
import {track} from "@/track.js";
|
||||
import {socket} from "@/socket.js";
|
||||
|
||||
|
||||
export const useWalletStore = defineStore('wallet', ()=>{
|
||||
// this is what the wallet is logged into. it could be different than the application's store.chainId.
|
||||
const chainId = ref(0)
|
||||
|
||||
// Pending Order Format
|
||||
// OLD Pending Order Format
|
||||
// {
|
||||
// chainId: 31337, // must never be null, even if no wallet plugin exists. chosen by app, not wallet.
|
||||
// placementTime: Date.now(),
|
||||
@@ -26,8 +26,30 @@ export const useWalletStore = defineStore('wallet', ()=>{
|
||||
// }
|
||||
const pendingOrders = ref([])
|
||||
|
||||
// NEW Format is a single Transaction class
|
||||
const _tx = ref(null)
|
||||
const transaction = computed({
|
||||
get() {return _tx.value},
|
||||
set(v) {
|
||||
_tx.value = v;
|
||||
if (v===null) {
|
||||
console.log('clear transaction')
|
||||
if (progressionInvoker!==null) {
|
||||
clearTimeout(progressionInvoker)
|
||||
progressionInvoker = null
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('set transaction', v)
|
||||
transactionProgressor.invoke();
|
||||
if (progressionInvoker===null)
|
||||
progressionInvoker = setInterval(()=>transactionProgressor.invoke(), 1000)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
chainId, pendingOrders,
|
||||
chainId, pendingOrders, transaction,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,6 +57,7 @@ export const useWalletStore = defineStore('wallet', ()=>{
|
||||
export function onChainChanged(chainId) {
|
||||
console.log('onChainChanged', chainId)
|
||||
chainId = Number(chainId)
|
||||
socket.emit('chain', chainId)
|
||||
const store = useStore()
|
||||
const ws = useWalletStore()
|
||||
if( chainId !== ws.chainId ) {
|
||||
@@ -44,7 +67,7 @@ export function onChainChanged(chainId) {
|
||||
console.log('app chain changed', chainId)
|
||||
store.chainId = chainId
|
||||
store.account = null
|
||||
provider = new BrowserProvider(window.ethereum, chainId)
|
||||
setProvider(new BrowserProvider(window.ethereum, chainId))
|
||||
updateAccounts(chainId, provider)
|
||||
}
|
||||
else {
|
||||
@@ -68,10 +91,14 @@ function changeAccounts(chainId, accounts) {
|
||||
const addr = accounts[0]
|
||||
if (addr !== store.account) {
|
||||
console.log('account logged in', addr)
|
||||
track('login', {chainId, address: addr})
|
||||
store.account = addr
|
||||
store.vaults = []
|
||||
discoverVaults(addr)
|
||||
flushTransactions()
|
||||
// one of these two methods will call flushTransactions()
|
||||
if (useWalletStore().transaction!==null)
|
||||
ensureVault()
|
||||
else
|
||||
discoverVaults(addr)
|
||||
socket.emit('address', chainId, addr)
|
||||
}
|
||||
}
|
||||
@@ -94,17 +121,17 @@ export function detectChain() {
|
||||
try {
|
||||
window.ethereum.on('chainChanged', onChainChanged);
|
||||
window.ethereum.on('accountsChanged', onAccountsChanged);
|
||||
new ethers.BrowserProvider(window.ethereum).getNetwork().then((network)=>{
|
||||
const chainId = network.chainId
|
||||
onChainChanged(chainId)
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
console.log('Could not connect change hooks to wallet', e)
|
||||
return
|
||||
}
|
||||
new ethers.BrowserProvider(window.ethereum).getNetwork().then((network)=>{
|
||||
const chainId = network.chainId
|
||||
onChainChanged(chainId)
|
||||
})
|
||||
}
|
||||
|
||||
detectChain()
|
||||
|
||||
const errorHandlingProxy = {
|
||||
get(target, prop, proxy) {
|
||||
@@ -148,7 +175,16 @@ export async function connectWallet(chainId) {
|
||||
await updateAccounts(chainId, p)
|
||||
}
|
||||
catch (e) {
|
||||
if (e.reason!=='rejected') {
|
||||
console.log('connectWallet error', e.reason, e)
|
||||
if (e.reason==='rejected') {
|
||||
const ws = useWalletStore();
|
||||
const tx = ws.transaction
|
||||
if (tx) {
|
||||
tx.state = TransactionState.Rejected
|
||||
ws.transaction = null
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error(e, e.reason)
|
||||
throw e
|
||||
}
|
||||
@@ -165,7 +201,8 @@ function discoverVaults(owner) {
|
||||
doDiscoverVaults.invoke(owner)
|
||||
}
|
||||
|
||||
const doDiscoverVaults = new SingletonCoroutine(_discoverVaults, 50, false)
|
||||
const doDiscoverVaults = new SingletonCoroutine(_discoverVaults, 50)
|
||||
|
||||
async function _discoverVaults(owner) {
|
||||
const result = []
|
||||
const versions = []
|
||||
@@ -182,7 +219,6 @@ async function _discoverVaults(owner) {
|
||||
// console.log(`vault ${num} at`, addr)
|
||||
if (addr === null) // no more vaults
|
||||
break
|
||||
console.log('provider', provider)
|
||||
if (!provider) {
|
||||
console.log('No provider')
|
||||
return // do not change whatever was already found
|
||||
@@ -194,21 +230,23 @@ async function _discoverVaults(owner) {
|
||||
result.push(addr)
|
||||
versions.push(version)
|
||||
} catch (e) {
|
||||
if (e.value === '0x' && e.code === 'BAD_DATA' || e.revert === null && e.code === 'CALL_EXCEPTION')
|
||||
if (errorSuggestsMissingVault(e))
|
||||
console.log(`no vault ${num} at ${addr}`)
|
||||
else
|
||||
console.error(`discoverVaults failed`, e)
|
||||
return // do not change what was already found todo is this correct?
|
||||
}
|
||||
}
|
||||
console.log('new account === owner?', s.account, owner)
|
||||
if( s.account === owner ) { // double-check the account since it could have changed during our await
|
||||
s.vaults = result
|
||||
s.vaultVersions = versions
|
||||
if( useWalletStore().pendingOrders.length ) {
|
||||
if( useWalletStore().transaction ) {
|
||||
const num = 0 // todo multiple vaults
|
||||
if (result.length)
|
||||
flushOrders(result[0])
|
||||
flushWalletTransactions(s.chainId, owner, num, result[0])
|
||||
else
|
||||
ensureVault2(s.chainId, owner, 0)
|
||||
ensureVault2(s.chainId, owner, num)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +286,7 @@ async function doEnsureVault(chainId, owner, num) {
|
||||
if (s.vaults.length <= num)
|
||||
await _discoverVaults(owner)
|
||||
if( s.vaults[num] )
|
||||
flushOrders(s.vaults[num])
|
||||
flushWalletTransactions(chainId, owner, num, s.vaults[num])
|
||||
else {
|
||||
console.log(`requesting vault ${owner} ${num}`)
|
||||
socket.emit('ensureVault', chainId, owner, num)
|
||||
@@ -258,53 +296,6 @@ async function doEnsureVault(chainId, owner, num) {
|
||||
const ensureVaultRoutine = new SingletonCoroutine(doEnsureVault, 100)
|
||||
|
||||
|
||||
export const PendingOrderState = {
|
||||
Submitted: -100, // user clicked Place Order but the tx isn't sent to the wallet yet
|
||||
Signing: 0, // tx is sent to the wallet
|
||||
Rejected: -101, // user refused to sign the tx
|
||||
Sent: -102, // tx is awaiting blockchain mining
|
||||
}
|
||||
|
||||
// single order placement selector
|
||||
const placementFeeSelector = 'placementFee((address,address,(uint8,uint24),uint256,uint256,bool,bool,bool,uint64,(uint16,bool,bool,bool,bool,bool,bool,bool,bool,uint16,uint24,uint32,uint32,(uint32,uint32),(uint32,uint32))[]),(uint8,uint8,uint8,uint8,uint8))'
|
||||
|
||||
export async function placementFee(vault, order, window=300) {
|
||||
// If the fees are about to change within `window` seconds of now, we send the higher native amount of the two fees.
|
||||
// If the fees sent are too much, the vault will refund the sender.
|
||||
const v = await vaultContract(vault, provider)
|
||||
const feeManagerAddr = await v.feeManager()
|
||||
const feeManager = await newContract(feeManagerAddr, 'IFeeManager', provider)
|
||||
const [sched, changeTimestamp] = await Promise.all([feeManager.fees(), feeManager.proposedFeeActivationTime()])
|
||||
console.log('sched', order, sched)
|
||||
let [orderFee, gasFee] = await v[placementFeeSelector](order, [...sched])
|
||||
console.log('placementFee', orderFee, gasFee)
|
||||
if (Number(changeTimestamp) - timestamp() < window) {
|
||||
const nextSched = await feeManager.proposedFees()
|
||||
const [nextOrderFee, nextGasFee] = await v[placementFeeSelector](order, [...nextSched])
|
||||
if (nextOrderFee + nextGasFee > orderFee + gasFee)
|
||||
[orderFee, gasFee] = [nextOrderFee, nextGasFee]
|
||||
}
|
||||
return [orderFee, gasFee]
|
||||
}
|
||||
|
||||
|
||||
export async function pendOrder(order, fee=null) {
|
||||
const s = useStore()
|
||||
const pend = {
|
||||
id: uuid(),
|
||||
chainId: s.chainId,
|
||||
placementTime: Date.now()/1000,
|
||||
fee: fee, // dexorder place and gas fee total
|
||||
vault: s.vaults.length ? s.vaults[0] : null,
|
||||
state: PendingOrderState.Submitted,
|
||||
order
|
||||
};
|
||||
useWalletStore().pendingOrders.splice(0,0, pend)
|
||||
console.log('pended order', pend.id, JSON.stringify(order))
|
||||
ensureVault()
|
||||
}
|
||||
|
||||
|
||||
export async function cancelOrder(vault, orderIndex) {
|
||||
console.log('cancel order', vault, orderIndex)
|
||||
pendTransaction(async (signer)=> {
|
||||
@@ -317,27 +308,75 @@ export async function cancelOrder(vault, orderIndex) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function cancelAll(vault) {
|
||||
pendTransaction(async (signer)=> {
|
||||
const contract = await vaultContract(vault, signer)
|
||||
if( contract === null ) {
|
||||
console.error('vault contract was null while canceling order', vault)
|
||||
return null
|
||||
async function progressTransactions() {
|
||||
const s = useStore()
|
||||
const ws = useWalletStore();
|
||||
console.log('progressTransactions', ws.transaction)
|
||||
if( ws.transaction===null )
|
||||
return
|
||||
if( s.account === null ) {
|
||||
let signer = null
|
||||
try {
|
||||
console.log('account is null. requesting sign-in.')
|
||||
signer = await provider.getSigner()
|
||||
}
|
||||
return await contract.cancelAllDexorders()
|
||||
})
|
||||
catch (e) {
|
||||
console.log('signer error', e.code, e.info?.error?.code)
|
||||
if (e?.info?.error?.code === 4001) {
|
||||
console.log('signer rejected')
|
||||
signer = null
|
||||
}
|
||||
else
|
||||
throw e
|
||||
}
|
||||
if (signer === null) {
|
||||
console.log('setting tx state to rejected')
|
||||
ws.transaction.state = TransactionState.Rejected
|
||||
ws.transaction = null
|
||||
return
|
||||
}
|
||||
}
|
||||
if( s.vault === null ) {
|
||||
console.log('vault is null. requesting vault creation.')
|
||||
ensureVault()
|
||||
return
|
||||
}
|
||||
if( ws.transaction.state < TransactionState.Proposed )
|
||||
ws.transaction.propose(s.account, s.vault)
|
||||
if( ws.transaction.type === TransactionType.PlaceOrder ) {
|
||||
flushWalletTransactions(s.chainId, s.account, 0, s.vault)
|
||||
}
|
||||
else {
|
||||
console.log('flushing transaction', ws.transaction.type)
|
||||
if (ws.transaction.state < TransactionState.Proposed) {
|
||||
pendTransaction(async (signer) => {
|
||||
if (signer.address !== ws.transaction.owner) {
|
||||
console.error('signer address does not match transaction owner', signer.address, ws.transaction.owner)
|
||||
return
|
||||
}
|
||||
const contract = await vaultContract(ws.transaction.vault, signer)
|
||||
return await ws.transaction.createTx(contract)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function flushOrders(vault) {
|
||||
const transactionProgressor = new SingletonCoroutine(progressTransactions, 10)
|
||||
|
||||
let progressionInvoker = null
|
||||
|
||||
|
||||
export function flushWalletTransactions(chainId, owner, num, vault) {
|
||||
const ws = useWalletStore();
|
||||
let needsFlush = false
|
||||
console.log('flushWalletTransactions', chainId, owner, num, vault)
|
||||
let needsFlush = ws.transaction !== null && ws.transaction.type !== TransactionType.PlaceOrder
|
||||
for( const pend of ws.pendingOrders ) {
|
||||
if (pend.vault === null)
|
||||
pend.vault = vault
|
||||
if (pend.state === PendingOrderState.Submitted) {
|
||||
console.log('flushing order', pend.id)
|
||||
pendOrderAsTransaction(pend)
|
||||
pend.state = PendingOrderState.Signing
|
||||
setPendState(pend, PendingOrderState.Signing)
|
||||
needsFlush = true
|
||||
}
|
||||
}
|
||||
@@ -363,7 +402,7 @@ function pendOrderAsTransaction(pend) {
|
||||
catch (e) {
|
||||
if(e.code===4001) {
|
||||
console.log('user refused chain switch')
|
||||
pend.state = PendingOrderState.Rejected
|
||||
setPendState(pend, PendingOrderState.Rejected)
|
||||
return null
|
||||
}
|
||||
else {
|
||||
@@ -378,7 +417,7 @@ function pendOrderAsTransaction(pend) {
|
||||
console.log('placing order', pend.id, pend.fee, pend.order)
|
||||
const tx = await contract.placeDexorder(pend.order, {value:pend.fee})
|
||||
pend.tx = tx
|
||||
pend.state = PendingOrderState.Sent
|
||||
setPendState(pend, PendingOrderState.Sent)
|
||||
console.log(`order ${pend.id} sent transaction`, tx)
|
||||
tx.wait().then((txReceipt)=>{
|
||||
console.log('mined order', pend.id, txReceipt)
|
||||
@@ -391,7 +430,7 @@ function pendOrderAsTransaction(pend) {
|
||||
(e) => {
|
||||
if( e.info?.error?.code === 4001 ) {
|
||||
console.log(`wallet refused order`, pend.id)
|
||||
pend.state = PendingOrderState.Rejected
|
||||
setPendState(pend, PendingOrderState.Rejected)
|
||||
return true // returning true means we handled the error. any other return value will dump to console.
|
||||
}
|
||||
})
|
||||
@@ -399,6 +438,7 @@ function pendOrderAsTransaction(pend) {
|
||||
|
||||
|
||||
export function pendTransaction(sender, errHandler) {
|
||||
console.log('pendTransaction')
|
||||
const s = useStore()
|
||||
s.transactionSenders.push([sender,errHandler])
|
||||
flushTransactions()
|
||||
@@ -413,13 +453,32 @@ export function flushTransactions() {
|
||||
|
||||
async function asyncFlushTransactions() {
|
||||
const s = useStore()
|
||||
const ws = useWalletStore()
|
||||
console.log('flushTransactions', ws.transaction, s.vault)
|
||||
if (ws.transaction !== null) {
|
||||
if (s.vault === null) {
|
||||
console.log('transaction doesn\'t have a vault. creating one.')
|
||||
await ensureVault()
|
||||
if (s.vault === null) {
|
||||
console.error('vault could not be created')
|
||||
const tx = ws.transaction
|
||||
if (tx) {
|
||||
tx.state = TransactionState.Error
|
||||
ws.transaction = null
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if( provider === null ) {
|
||||
console.log('warning: asyncFlushOrders() cancelled due to null provider')
|
||||
return
|
||||
}
|
||||
const senders = s.transactionSenders
|
||||
if (!senders.length)
|
||||
if (!senders.length) {
|
||||
console.log('no transactionSenders!')
|
||||
return
|
||||
}
|
||||
console.log(`flushing ${senders.length} transactions`)
|
||||
let signer
|
||||
try {
|
||||
@@ -464,18 +523,18 @@ function doSendTransaction(sender, signer, errHandler) {
|
||||
export async function detectUpgrade() {
|
||||
if (!provider) {
|
||||
console.log('no provider!')
|
||||
return 0
|
||||
return null
|
||||
}
|
||||
const s = useStore()
|
||||
if (!s.vault) {
|
||||
console.log('no vault logged in')
|
||||
return 0
|
||||
return null
|
||||
}
|
||||
|
||||
const info = version.chainInfo[s.chainId]
|
||||
if (!info) {
|
||||
console.log(`couldn't get chainInfo for ${s.chainId}`)
|
||||
return 0
|
||||
return null
|
||||
}
|
||||
try {
|
||||
console.log('factory', info.factory)
|
||||
@@ -497,7 +556,7 @@ export async function detectUpgrade() {
|
||||
catch (e) {
|
||||
console.log('ignorable error while querying for an upgrade', e)
|
||||
}
|
||||
return 0
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -535,7 +594,7 @@ const _chainInfos = {
|
||||
1337: {
|
||||
"chainId": "0x539",
|
||||
"chainName": "Dexorder Alpha Testnet",
|
||||
"rpcUrls": ["https://rpc.alpha.dexorder.trade"],
|
||||
"rpcUrls": ["https://rpc.alpha.dexorder.com"],
|
||||
"nativeCurrency": {
|
||||
"name": "Test Ethereum",
|
||||
"symbol": "TETH",
|
||||
@@ -577,3 +636,26 @@ export async function addNetwork(chainId) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function addNetworkAndConnectWallet(chainId) {
|
||||
try {
|
||||
await switchChain(chainId)
|
||||
} catch (e) {
|
||||
if (e.code === 4001) {
|
||||
// explicit user rejection
|
||||
return
|
||||
} else if (e.code === 4902) {
|
||||
try {
|
||||
await addNetwork(chainId)
|
||||
} catch (e) {
|
||||
console.log(`Could not add network ${chainId}`)
|
||||
}
|
||||
} else
|
||||
console.log('switchChain() failure', e)
|
||||
}
|
||||
try {
|
||||
await connectWallet(chainId)
|
||||
} catch (e) {
|
||||
if (e.code !== 4001)
|
||||
console.log('connectWallet() failed', e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,4 +52,8 @@ export function dirtyItems(a, b) {
|
||||
for (const k of dirtyKeys(a, b))
|
||||
result[k] = b[k]
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
export function copyPoints(points) {
|
||||
return points.map((p)=>({time: p.time, price: p.price}))
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {invokeCallbacks, prototype} from "@/common.js";
|
||||
import {DataFeed, feelessTickerKey, getAllSymbols, lookupSymbol} from "@/charts/datafeed.js";
|
||||
import {intervalToSeconds, SingletonCoroutine} from "@/misc.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {DataFeed, defaultSymbol, feelessTickerKey, getAllSymbols, lookupSymbol} from "@/charts/datafeed.js";
|
||||
import {intervalToSeconds, secondsToInterval, SingletonCoroutine, toHuman, toPrecision} from "@/misc.js";
|
||||
import {usePrefStore, useStore} from "@/store/store.js";
|
||||
import {tvCustomThemes} from "../../theme.js";
|
||||
|
||||
export let widget = null
|
||||
export let chart = null
|
||||
export let crosshairPoint = null
|
||||
export let defaultShapeHandler = null // if set, then TV events that dont have a registered shape handler get passed directly to this function
|
||||
let symbolChangedCbs = [] // callbacks for TV's chart.onSymbolChanged()
|
||||
|
||||
|
||||
const s = useStore()
|
||||
const co = useChartOrderStore()
|
||||
const prefs = usePrefStore()
|
||||
|
||||
export function addSymbolChangedCallback(cb) {
|
||||
symbolChangedCbs.push(cb)
|
||||
@@ -23,14 +25,13 @@ export function removeSymbolChangedCallback(cb) {
|
||||
}
|
||||
|
||||
function symbolChanged(symbol) {
|
||||
if (symbol===null)
|
||||
co.selectedSymbol = null
|
||||
else {
|
||||
const info = lookupSymbol(symbol.ticker)
|
||||
symbolChangedCbs.forEach((cb) => cb(info))
|
||||
co.selectedSymbol = info
|
||||
}
|
||||
const info = symbol===null ? (defaultSymbol===null?'default':defaultSymbol) : lookupSymbol(symbol.ticker)
|
||||
co.selectedSymbol = info
|
||||
// console.log('setting prefs ticker', info.ticker)
|
||||
prefs.selectedTicker = info.ticker
|
||||
symbolChangedCbs.forEach((cb) => cb(info))
|
||||
updateFeeDropdown()
|
||||
// console.log('symbol changed', info)
|
||||
}
|
||||
|
||||
|
||||
@@ -56,15 +57,24 @@ export async function setSymbolTicker(ticker) {
|
||||
}
|
||||
|
||||
|
||||
function changeInterval(interval, _timeframe) {
|
||||
co.intervalSecs = intervalToSeconds(interval)
|
||||
DataFeed.intervalChanged(co.intervalSecs)
|
||||
function changeInterval(interval) {
|
||||
const secs = intervalToSeconds(interval)
|
||||
co.intervalSecs = secs
|
||||
prefs.selectedTimeframe = interval
|
||||
DataFeed.intervalChanged(secs)
|
||||
}
|
||||
|
||||
|
||||
export function changeIntervalSecs(secs) {
|
||||
const interval = secondsToInterval(secs);
|
||||
co.intervalSecs = secs
|
||||
prefs.selectedTimeframe = interval
|
||||
DataFeed.intervalChanged(secs)
|
||||
}
|
||||
|
||||
function dataLoaded() {
|
||||
const range = chartMeanRange()
|
||||
console.log('new mean range', range,)
|
||||
// console.log('new mean range', range,)
|
||||
co.meanRange = range
|
||||
}
|
||||
|
||||
@@ -82,68 +92,100 @@ const subscribeEvents = [
|
||||
*/
|
||||
|
||||
|
||||
let feeDropdown = null
|
||||
let poolButtonTextElement = null
|
||||
|
||||
export function initFeeDropdown(w) {
|
||||
widget = w
|
||||
widget.createDropdown(
|
||||
{
|
||||
title: 'Fees',
|
||||
tooltip: 'Choose Fee Tier',
|
||||
items: [/*{title: 'Automatic Fee Selection', onSelect: () => {log('autofees')}}*/],
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"><g fill="none" stroke="currentColor"><circle cx="10" cy="10" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path stroke-linecap="square" d="M17.5 7.5l-7 13"/></g></svg>`,
|
||||
}
|
||||
).then(dropdown => {
|
||||
feeDropdown = dropdown;
|
||||
updateFeeDropdown()
|
||||
})
|
||||
function initFeeDropdown() {
|
||||
const button = widget.createButton()
|
||||
button.setAttribute('title', 'See Pool Info and Choose Fee');
|
||||
button.addEventListener('click', function () {
|
||||
co.showPoolSelection = true
|
||||
});
|
||||
button.id = 'pool-button'
|
||||
|
||||
button.style.height = '34px';
|
||||
button.style.display = 'flex';
|
||||
button.style.alignItems = 'center';
|
||||
button.addEventListener('mouseover', () => {
|
||||
button.style.backgroundColor = 'rgb(60,60,60)';
|
||||
});
|
||||
button.addEventListener('mouseout', () => {
|
||||
button.style.backgroundColor = '';
|
||||
});
|
||||
button.style.margin = '2px 0';
|
||||
button.style.borderRadius = '4px';
|
||||
button.classList.add('pool-button')
|
||||
|
||||
let img = document.createElement('img');
|
||||
img.src = '/arbitrum-logo.svg';
|
||||
img.style.width = '1em';
|
||||
img.style.marginRight = '0.2em';
|
||||
button.appendChild(img);
|
||||
|
||||
img = document.createElement('img');
|
||||
img.src = '/uniswap-logo.svg';
|
||||
img.style.height = '1.25em';
|
||||
img.style.marginRight = '0.2em';
|
||||
img.style.backgroundColor = 'white';
|
||||
img.style.borderRadius = '50%';
|
||||
button.appendChild(img);
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.style.marginY = 'auto';
|
||||
button.appendChild(span);
|
||||
poolButtonTextElement = span
|
||||
|
||||
updateFeeDropdown()
|
||||
}
|
||||
|
||||
export function updateFeeDropdown() {
|
||||
if (feeDropdown === null) return
|
||||
if (poolButtonTextElement===null) return
|
||||
const symbolItem = useChartOrderStore().selectedSymbol
|
||||
let items
|
||||
if (symbolItem === null)
|
||||
items = [{title: '0.00%'}]
|
||||
else {
|
||||
const feeGroup = symbolItem.feeGroup
|
||||
items = feeGroup.map((p) => {
|
||||
const [_addr, fee] = p
|
||||
return {
|
||||
title: (fee / 10000).toFixed(2) + '%',
|
||||
onSelect: ()=>{
|
||||
if (fee !== symbolItem.fee)
|
||||
selectPool(fee)
|
||||
},
|
||||
}
|
||||
})
|
||||
let text = ''
|
||||
text += (symbolItem.fee / 10000).toFixed(2) + '%'
|
||||
const index = symbolItem.feeGroup.findIndex((p) => p[1] === symbolItem.fee)
|
||||
if (symbolItem.liquiditySymbol) {
|
||||
const liq = symbolItem.liquidities[index]
|
||||
if (symbolItem.liquiditySymbol === 'USD')
|
||||
text += ` $${toHuman(liq)}`
|
||||
else
|
||||
text = ` ${toHuman(liq)} ${symbolItem.liquiditySymbol}`
|
||||
}
|
||||
feeDropdown.applyOptions({items})
|
||||
poolButtonTextElement.textContent = text
|
||||
}
|
||||
|
||||
function selectPool(fee) {
|
||||
const co = useChartOrderStore();
|
||||
const s = co.selectedSymbol;
|
||||
const ticker = feelessTickerKey(s.ticker) + '|' + fee
|
||||
if (ticker !== s.ticker)
|
||||
setSymbolTicker(ticker).catch((e)=>console.error('Could not change TV symbol to', ticker))
|
||||
export function initTVButtons() {
|
||||
initFeeDropdown();
|
||||
}
|
||||
|
||||
export function initWidget(el) {
|
||||
getAllSymbols()
|
||||
const symbol = prefs.selectedTicker === null ? 'default' : prefs.selectedTicker
|
||||
const interval = prefs.selectedTimeframe === null ? '15' : prefs.selectedTimeframe
|
||||
widget = window.tvWidget = new TradingView.widget({
|
||||
|
||||
// Widget Options
|
||||
// https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.ChartingLibraryWidgetOptions
|
||||
library_path: "/charting_library/",
|
||||
// debug: true,
|
||||
autosize: true,
|
||||
symbol: 'default',
|
||||
interval: '15',
|
||||
symbol,
|
||||
interval,
|
||||
container: el,
|
||||
datafeed: DataFeed, // use this for ohlc
|
||||
locale: "en",
|
||||
disabled_features: [],
|
||||
enabled_features: ['saveload_separate_drawings_storage'],
|
||||
drawings_access: {type: 'white', tools: [],}, // show no tools
|
||||
disabled_features: ['main_series_scale_menu','display_market_status',],
|
||||
enabled_features: ['saveload_separate_drawings_storage','snapshot_trading_drawings','show_exchange_logos','show_symbol_logos',],
|
||||
// drawings_access: {type: 'white', tools: [],}, // show no tools
|
||||
custom_themes: tvCustomThemes,
|
||||
theme: useStore().theme,
|
||||
timezone: prefs.timezone,
|
||||
|
||||
// Chart Overrides
|
||||
// https://www.tradingview.com/charting-library-docs/latest/customization/overrides/chart-overrides
|
||||
overrides: {
|
||||
"mainSeriesProperties.priceAxisProperties.log": false,
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// debug dump all events
|
||||
@@ -154,14 +196,24 @@ export function initWidget(el) {
|
||||
widget.subscribe('onSelectedLineToolChanged', onSelectedLineToolChanged)
|
||||
widget.subscribe('mouse_down', mouseDown)
|
||||
widget.subscribe('mouse_up', mouseUp)
|
||||
widget.headerReady().then(()=>initFeeDropdown(widget))
|
||||
widget.headerReady().then(()=>initTVButtons())
|
||||
widget.onChartReady(initChart)
|
||||
console.log('tv widget initialized')
|
||||
}
|
||||
|
||||
|
||||
export function onChartReady(f) {
|
||||
if (co.chartReady)
|
||||
f(widget, chart)
|
||||
else
|
||||
chartInitCbs.push(f)
|
||||
}
|
||||
|
||||
let chartInitCbs = []
|
||||
|
||||
|
||||
function initChart() {
|
||||
console.log('init chart')
|
||||
// console.log('init chart')
|
||||
chart = widget.activeChart()
|
||||
const themeName = useStore().theme;
|
||||
widget.changeTheme(themeName).catch((e)=>console.warn(`Could not change theme to ${themeName}`, e))
|
||||
@@ -184,7 +236,12 @@ function initChart() {
|
||||
}
|
||||
changeInterval(widget.symbolInterval().interval)
|
||||
co.chartReady = true
|
||||
console.log('chart ready')
|
||||
setTimeout(()=>{
|
||||
for (const cb of chartInitCbs)
|
||||
cb(widget, chart)
|
||||
chartInitCbs = []
|
||||
}, 1)
|
||||
// console.log('chart ready')
|
||||
}
|
||||
|
||||
|
||||
@@ -229,13 +286,14 @@ let drawingCallbacks = null
|
||||
|
||||
export function drawShape(shapeType, ...callbacks) {
|
||||
// puts the chart into a line-drawing mode for a new shape
|
||||
console.log('drawShape', callbacks, shapeType.name, shapeType.code)
|
||||
// console.log('drawShape', callbacks, shapeType.name, shapeType.code)
|
||||
if( drawingCallbacks )
|
||||
invokeCallbacks(drawingCallbacks, 'onUndraw')
|
||||
drawingCallbacks = callbacks
|
||||
drawingTool = null
|
||||
previousDrawingTool = widget.selectedLineTool()
|
||||
co.drawing = true
|
||||
co.drew = false
|
||||
widget.selectLineTool(shapeType.code)
|
||||
invokeCallbacks(callbacks, 'onDraw')
|
||||
}
|
||||
@@ -294,7 +352,7 @@ const shapeCallbacks = {}
|
||||
|
||||
function onSelectedLineToolChanged() {
|
||||
const tool = widget.selectedLineTool();
|
||||
console.log('line tool changed', tool)
|
||||
// console.log('line tool changed', tool)
|
||||
if (drawingTool===null)
|
||||
drawingTool = tool
|
||||
else if (tool!==drawingTool && co.drawing)
|
||||
@@ -344,9 +402,9 @@ function doHandleCrosshairMovement(point) {
|
||||
}
|
||||
const points = structuredClone(shape.getPoints());
|
||||
const lpbe = shape._model._linePointBeingEdited
|
||||
points[lpbe] = point
|
||||
// console.log('drag calling onPoints', points, shape, lpbe)
|
||||
invokeCallbacks(shapeCallbacks[shapeId], 'onPoints', shapeId, shape, points)
|
||||
points[lpbe===null?0:lpbe] = point
|
||||
// console.log('calling onDrag', points, shape)
|
||||
invokeCallbacks(shapeCallbacks[shapeId], 'onDrag', shapeId, shape, points)
|
||||
}
|
||||
}
|
||||
else if (draggingShapeIds.length > 0) {
|
||||
@@ -430,9 +488,11 @@ function doHandleDrawingEvent(id, event) {
|
||||
const props = shape.getProperties()
|
||||
if (id in shapeCallbacks)
|
||||
invokeCallbacks(shapeCallbacks[id], 'onProps', id, shape, props)
|
||||
else
|
||||
// otherwise it's an event on a shape we don't "own"
|
||||
else {
|
||||
// otherwise it's an event on a shape we don't "own" that could be being drawn
|
||||
co.drew = true
|
||||
console.log('warning: ignoring setProperties on TV shape', id, props)
|
||||
}
|
||||
} else if (event === 'move') {
|
||||
if (id in shapeCallbacks) {
|
||||
invokeCallbacks(shapeCallbacks[id], 'onMove', id, shape)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {provider} from "@/blockchain/provider.js";
|
||||
import {convertTvResolution, loadOHLC} from './ohlc.js';
|
||||
import {metadata} from "@/version.js";
|
||||
import FlexSearch from "flexsearch";
|
||||
@@ -5,7 +6,10 @@ import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {subOHLC, unsubOHLC} from "@/blockchain/ohlcs.js";
|
||||
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||
import {timestamp} from "@/misc.js";
|
||||
import {timestamp, withTimeout} from "@/common.js";
|
||||
import {erc20Contract} from "@/blockchain/contract.js";
|
||||
import {track} from "@/track.js";
|
||||
|
||||
|
||||
const DEBUG_LOGGING = false
|
||||
const log = DEBUG_LOGGING ? console.log : ()=>{}
|
||||
@@ -19,6 +23,7 @@ const log = DEBUG_LOGGING ? console.log : ()=>{}
|
||||
const quoteSymbols = [
|
||||
'USDT',
|
||||
'USDC',
|
||||
'USDC.e',
|
||||
'TUSD',
|
||||
'GUSD',
|
||||
'BUSD',
|
||||
@@ -59,7 +64,7 @@ const configurationData = {
|
||||
value: 'UNIv3',
|
||||
name: 'Uniswap v3',
|
||||
desc: 'Uniswap v3',
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/e/e7/Uniswap_Logo.svg',
|
||||
logo: '/uniswap-logo.svg',
|
||||
},
|
||||
],
|
||||
// The `symbols_types` arguments are used for the `searchSymbols` method if a user selects this symbol type
|
||||
@@ -106,8 +111,10 @@ export function feelessTickerKey(ticker) {
|
||||
|
||||
function addSymbol(chainId, p, base, quote, inverted) {
|
||||
const symbol = base.s + '/' + quote.s
|
||||
const fee = `${(p.f/10000).toFixed(2)}%`
|
||||
const exchange = ['Uniswap v2', 'Uniswap v3'][p.e] + ' ' + fee
|
||||
// const fee = `${(p.f/10000).toFixed(2)}%`
|
||||
// const exchange = ['Uniswap v2', 'Uniswap v3'][p.e] + ' ' + fee
|
||||
const exchange = ['Uniswap v2', 'Uniswap v3'][p.e]
|
||||
const exchange_logo = '/uniswap-logo.svg'
|
||||
const full_name = exchange + ':' + symbol // + '%' + formatFee(fee)
|
||||
const ticker = tickerKey(chainId, p.e, base.a, quote.a, p.f)
|
||||
// add the search index only if this is the natural, noninverted base/quote pair
|
||||
@@ -118,7 +125,7 @@ function addSymbol(chainId, p, base, quote, inverted) {
|
||||
const symbolInfo = {
|
||||
key: ticker, ticker,
|
||||
chainId, address: p.a, exchangeId: p.e,
|
||||
full_name, symbol, description, exchange, type, inverted, base, quote, decimals, x:p.x, fee:p.f,
|
||||
full_name, symbol, description, exchange, exchange_logo, type, inverted, base, quote, decimals, x:p.x, fee:p.f,
|
||||
};
|
||||
_symbols[ticker] = symbolInfo
|
||||
const feelessKey = feelessTickerKey(ticker)
|
||||
@@ -129,8 +136,13 @@ function addSymbol(chainId, p, base, quote, inverted) {
|
||||
else
|
||||
feeGroups[feelessKey] = [[symbolInfo.address, symbolInfo.fee]]
|
||||
symbolInfo.feeGroup = feeGroups[feelessKey]
|
||||
if (defaultSymbol===null && !invertedDefault(symbolInfo.base.a, symbolInfo.quote.a))
|
||||
// if (defaultSymbol===null) {
|
||||
// console.log(`invertedDefault(${symbolInfo.base.s}, ${symbolInfo.quote.s})`,invertedDefault(symbolInfo.base.a, symbolInfo.quote.a))
|
||||
// }
|
||||
if (defaultSymbol===null && !invertedDefault(symbolInfo.base.a, symbolInfo.quote.a)) {
|
||||
console.log('setting default symbol', symbolInfo.base.s, symbolInfo.quote.s, symbolInfo.base.a, symbolInfo.quote.a)
|
||||
defaultSymbol = _symbols[ticker]
|
||||
}
|
||||
log('new symbol', ticker, _symbols[ticker])
|
||||
}
|
||||
|
||||
@@ -240,7 +252,6 @@ function invertTicker(ticker) {
|
||||
}
|
||||
|
||||
export function lookupSymbol(ticker) { // lookup by ticker which is "0xbaseAddress/0xquoteAddress"
|
||||
// todo tim lookup default base/quote pool
|
||||
const symbols = getAllSymbols();
|
||||
if (!(ticker in symbols)) {
|
||||
// check the inverted symbol
|
||||
@@ -324,6 +335,14 @@ class RealtimeSubscription {
|
||||
}
|
||||
|
||||
|
||||
async function getLiquidities(markToken, symbolItem) {
|
||||
const token = await erc20Contract(markToken.a, provider)
|
||||
const liquidities = await Promise.all(symbolItem.feeGroup.map(
|
||||
async ([addr, fee]) => await token.balanceOf(addr)
|
||||
))
|
||||
return liquidities;
|
||||
}
|
||||
|
||||
export const DataFeed = {
|
||||
onReady(callback) {
|
||||
log('[onReady]: Method call');
|
||||
@@ -348,6 +367,8 @@ export const DataFeed = {
|
||||
result.push(_symbols[ticker])
|
||||
seen[ticker] = true
|
||||
}
|
||||
if (userInput.length>=3)
|
||||
track('search', {search_term: userInput})
|
||||
onResultReadyCallback(result);
|
||||
},
|
||||
|
||||
@@ -368,22 +389,59 @@ export const DataFeed = {
|
||||
onResolveErrorCallback,
|
||||
extension
|
||||
) {
|
||||
log('[resolveSymbol]: Method call', symbolName);
|
||||
console.log('resolveSymbol', symbolName);
|
||||
const symbols = getAllSymbols();
|
||||
const symbolItem = symbolName === 'default' ? defaultSymbol : symbols[symbolName]
|
||||
if (symbolName==='default') {
|
||||
console.log('using default symbol', defaultSymbol)
|
||||
}
|
||||
if (!symbolItem) {
|
||||
log('[resolveSymbol]: Cannot resolve symbol', symbolName);
|
||||
console.log('[resolveSymbol]: Cannot resolve symbol', symbolName);
|
||||
onResolveErrorCallback('cannot resolve symbol');
|
||||
return;
|
||||
}
|
||||
const co = useChartOrderStore();
|
||||
co.selectedSymbol = symbolItem
|
||||
const feelessKey = feelessTickerKey(symbolItem.ticker)
|
||||
const symbolsByFee = feeGroups[feelessKey]
|
||||
symbolsByFee.sort((a,b)=>a.fee-b.fee)
|
||||
const pool = symbolsByFee[Math.floor((symbolsByFee.length - 1)/2)] // median rounded down
|
||||
// noinspection JSValidateTypes
|
||||
co.selectedPool = pool // todo remove
|
||||
|
||||
let ticker = symbolItem.ticker
|
||||
try {
|
||||
if (!symbolItem.liquiditySymbol) {
|
||||
// fetch liquidities and cache on the symbolItem
|
||||
const inv = invertedDefault(symbolItem.base.a, symbolItem.quote.a)
|
||||
const markToken = inv ? symbolItem.base : symbolItem.quote
|
||||
const mark = useStore().markPrice(markToken.a)
|
||||
const liquidities = await withTimeout(
|
||||
getLiquidities(markToken, symbolItem),
|
||||
3000,
|
||||
'liquidity fetch timeout'
|
||||
)
|
||||
symbolItem.liquidities = liquidities.map(l => Number(l / 10n ** BigInt(markToken.d)))
|
||||
if (mark) {
|
||||
symbolItem.liquidities = symbolItem.liquidities.map(l => l * mark)
|
||||
symbolItem.liquiditySymbol = 'USD'
|
||||
} else {
|
||||
symbolItem.liquiditySymbol = symbolItem.quote.s
|
||||
}
|
||||
}
|
||||
const liqsAndFees = []
|
||||
for (let i=0; i<symbolItem.feeGroup.length; i++) {
|
||||
const [addr, fee] = symbolItem.feeGroup[i]
|
||||
const liq = symbolItem.liquidities[i]
|
||||
liqsAndFees.push([liq, fee])
|
||||
if (fee === symbolItem.fee)
|
||||
symbolItem.liquidity = liq
|
||||
}
|
||||
liqsAndFees.sort((a,b) => b[0] - a[0])
|
||||
const highestLiquidityFee = liqsAndFees[0][1]
|
||||
// console.log('liquidities', liqsAndFees)
|
||||
// console.log('best liquidity', highestLiquidityFee, liqsAndFees[0][0], symbolItem.liquiditySymbol)
|
||||
ticker = feelessTickerKey(ticker) + '|' + highestLiquidityFee
|
||||
}
|
||||
catch (error) {
|
||||
// use the median fee group instead
|
||||
console.log('liquidity fetch error', error)
|
||||
}
|
||||
|
||||
// LibrarySymbolInfo
|
||||
// https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.LibrarySymbolInfo
|
||||
const symbolInfo = {
|
||||
@@ -405,13 +463,13 @@ export const DataFeed = {
|
||||
// volume_precision: 2,
|
||||
data_status: 'streaming',
|
||||
};
|
||||
log('[resolveSymbol]: Symbol resolved', symbolName);
|
||||
// console.log('[resolveSymbol]: Symbol resolved', symbolName);
|
||||
onSymbolResolvedCallback(symbolInfo)
|
||||
},
|
||||
|
||||
async getBars(symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) {
|
||||
const { from, to, firstDataRequest } = periodParams;
|
||||
log('[getBars]: Method call', symbolInfo, resolution, from, to);
|
||||
log('[getBars]: Method call', symbolInfo, resolution, new Date(from*1000), new Date(to*1000));
|
||||
try {
|
||||
// todo need to consider the selected fee tier
|
||||
await getAllSymbols()
|
||||
@@ -683,4 +741,4 @@ export const DataFeed = {
|
||||
|
||||
|
||||
let _rolloverBumper = null
|
||||
let defaultSymbol = null
|
||||
export let defaultSymbol = null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||
import {LRUCache} from "lru-cache";
|
||||
|
||||
|
||||
// support for Dexorder OHLC data files
|
||||
@@ -43,19 +44,36 @@ function singleFile(resName) {
|
||||
}
|
||||
|
||||
|
||||
function nextDay(timestamp) {
|
||||
function addDay(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + 1) / 1000
|
||||
}
|
||||
|
||||
function addMonth(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
const result = Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) / 1000
|
||||
// console.log('addMonth', timestamp, result, new Date(timestamp*1000), new Date(result*1000))
|
||||
return result
|
||||
}
|
||||
|
||||
function addYear(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear() + 1, date.getUTCMonth(), date.getUTCDate()) / 1000
|
||||
}
|
||||
|
||||
function nextDay(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + 1, 0, 0, 0) / 1000
|
||||
}
|
||||
|
||||
function nextMonth(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) / 1000
|
||||
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1, 0, 0, 0) / 1000
|
||||
}
|
||||
|
||||
function nextYear(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear() + 1, date.getUTCMonth(), date.getUTCDate()) / 1000
|
||||
return Date.UTC(date.getUTCFullYear() + 1, 1, 1, 0, 0, 0) / 1000
|
||||
}
|
||||
|
||||
|
||||
@@ -66,21 +84,21 @@ function never(_timestamp) {
|
||||
|
||||
// noinspection PointlessArithmeticExpressionJS
|
||||
const resolutions = [
|
||||
{ seconds: 1 * 60, name: '1m', tvRes: '1', filename: dailyFile( '1m'), nextStart: nextDay, },
|
||||
{ seconds: 3 * 60, name: '3m', tvRes: '3', filename: dailyFile( '3m'), nextStart: nextDay, },
|
||||
{ seconds: 5 * 60, name: '5m', tvRes: '5', filename: dailyFile( '5m'), nextStart: nextDay, },
|
||||
{ seconds: 10 * 60, name: '10m', tvRes: '10', filename: dailyFile('10m'), nextStart: nextDay, },
|
||||
{ seconds: 15 * 60, name: '15m', tvRes: '15', filename: dailyFile('15m'), nextStart: nextDay, },
|
||||
{ seconds: 30 * 60, name: '30m', tvRes: '30', filename: dailyFile('30m'), nextStart: nextDay, },
|
||||
{ seconds: 60 * 60, name: '1H', tvRes: '60', filename: monthlyFile( '1H'), nextStart: nextMonth, },
|
||||
{ seconds: 120 * 60, name: '2H', tvRes: '120', filename: monthlyFile( '2H'), nextStart: nextMonth, },
|
||||
{ seconds: 240 * 60, name: '4H', tvRes: '240', filename: monthlyFile( '4H'), nextStart: nextMonth, },
|
||||
{ seconds: 480 * 60, name: '8H', tvRes: '480', filename: monthlyFile( '8H'), nextStart: nextMonth, },
|
||||
{ seconds: 720 * 60, name: '12H', tvRes: '720', filename: monthlyFile('12H'), nextStart: nextMonth, },
|
||||
{ seconds: 1440 * 60, name: '1D', tvRes: '1D', filename: yearlyFile( '1D'), nextStart: nextYear, },
|
||||
{ seconds: 2880 * 60, name: '2D', tvRes: '2D', filename: yearlyFile( '2D'), nextStart: nextYear, },
|
||||
{ seconds: 4320 * 60, name: '3D', tvRes: '3D', filename: yearlyFile( '3D'), nextStart: nextYear, },
|
||||
{ seconds: 10080 * 60, name: '1W', tvRes: '1W', filename: singleFile( '1W'), nextStart: never, },
|
||||
{ seconds: 1 * 60, name: '1m', tvRes: '1', filename: dailyFile( '1m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 3 * 60, name: '3m', tvRes: '3', filename: dailyFile( '3m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 5 * 60, name: '5m', tvRes: '5', filename: dailyFile( '5m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 10 * 60, name: '10m', tvRes: '10', filename: dailyFile('10m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 15 * 60, name: '15m', tvRes: '15', filename: dailyFile('15m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 30 * 60, name: '30m', tvRes: '30', filename: dailyFile('30m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 60 * 60, name: '1H', tvRes: '60', filename: monthlyFile( '1H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 120 * 60, name: '2H', tvRes: '120', filename: monthlyFile( '2H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 240 * 60, name: '4H', tvRes: '240', filename: monthlyFile( '4H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 480 * 60, name: '8H', tvRes: '480', filename: monthlyFile( '8H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 720 * 60, name: '12H', tvRes: '720', filename: monthlyFile('12H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 1440 * 60, name: '1D', tvRes: '1D', filename: yearlyFile( '1D'), add: addYear, nextStart: nextYear, },
|
||||
{ seconds: 2880 * 60, name: '2D', tvRes: '2D', filename: yearlyFile( '2D'), add: addYear, nextStart: nextYear, },
|
||||
{ seconds: 4320 * 60, name: '3D', tvRes: '3D', filename: yearlyFile( '3D'), add: addYear, nextStart: nextYear, },
|
||||
{ seconds: 10080 * 60, name: '1W', tvRes: '1W', filename: singleFile( '1W'), add: (x)=>x, nextStart: never, },
|
||||
]
|
||||
|
||||
const tvResMap = {}
|
||||
@@ -93,14 +111,21 @@ for (const res of resolutions)
|
||||
|
||||
|
||||
const seriesStarts = {}
|
||||
const ohlcCache = new LRUCache({max:20,ttl:3600*1000,})
|
||||
|
||||
|
||||
async function getUrl(url) {
|
||||
let result = ohlcCache[url]
|
||||
if (result)
|
||||
return result
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
// console.log('got response', response)
|
||||
if (response.ok)
|
||||
return await response.text()
|
||||
if (response.ok) {
|
||||
result = await response.text()
|
||||
ohlcCache[url] = result
|
||||
return result
|
||||
}
|
||||
else
|
||||
console.error(`could not fetch ${url}: status ${response.statusText}`)
|
||||
}
|
||||
@@ -119,12 +144,15 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
let baseUrl
|
||||
let latest = null // latest time, price
|
||||
|
||||
function fill(end, period) {
|
||||
function fill(end) {
|
||||
const period = res.seconds
|
||||
if (latest===null) return
|
||||
const [latestTime, price] = latest
|
||||
end = ohlcStart(end, period)
|
||||
const start = ohlcStart(latestTime+period, period);
|
||||
for (let now= start; now < end; now += period ) {
|
||||
const start = ohlcStart(latestTime+period, period)
|
||||
// if (start<end)
|
||||
// console.log('filling', latestTime, price, new Date(start*1000), new Date(end*1000))
|
||||
for (let now=start; now < end; now += period ) {
|
||||
bars.push({time:now * 1000, open:price, high:price, low:price, close:price})
|
||||
latest = [now, price]
|
||||
}
|
||||
@@ -144,13 +172,13 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
|
||||
const res = tvResMap[tvRes]
|
||||
const fetches = []
|
||||
let start = from
|
||||
if (!(baseUrl in seriesStarts)) {
|
||||
try {
|
||||
// console.log('getting quote', baseUrl+'quote.csv')
|
||||
const response = await getUrl(baseUrl+'quote.csv')
|
||||
if (response.length) {
|
||||
seriesStarts[baseUrl] = parseInt(response.split(',')[0])
|
||||
const [start,end,price] = response.split(',')
|
||||
seriesStarts[baseUrl] = parseInt(start)
|
||||
// console.log(`Series ${baseUrl} starts at ${new Date(start*1000)}`)
|
||||
}
|
||||
else {
|
||||
@@ -161,85 +189,97 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
let start = from
|
||||
if (baseUrl in seriesStarts)
|
||||
start = Math.max(start, seriesStarts[baseUrl])
|
||||
|
||||
for(let now = start; now < to; now = res.nextStart(now)) {
|
||||
const end = res.nextStart(to)
|
||||
for(let now = start; now < end; now = res.nextStart(now)) {
|
||||
const url = baseUrl + res.filename(contract, now);
|
||||
const prom = getUrl(url)
|
||||
fetches.push(prom);
|
||||
}
|
||||
|
||||
const responses = await Promise.all(fetches)
|
||||
for (const response of responses) {
|
||||
if (response.length) {
|
||||
let lineNum = 0
|
||||
response.split('\n').forEach((line) => {
|
||||
lineNum++
|
||||
const row = line.split(',')
|
||||
let time, open, high, low, close=null
|
||||
switch (row.length) {
|
||||
case 1:
|
||||
if (row[0].length !== 0)
|
||||
console.log(`Warning: weird nonempty row at OHLC line ${lineNum}: "${line}"`)
|
||||
let finished = false
|
||||
for( let ri=0; !finished && ri<responses.length; ri++ ) {
|
||||
let lineNum = 0
|
||||
const rows = responses[ri].split('\n')
|
||||
for( let rj=0; rj<rows.length; rj++) {
|
||||
const line = rows[rj];
|
||||
if (line.trim().length === 0)
|
||||
continue
|
||||
const row = line.split(',')
|
||||
let time, open, high, low, close=null
|
||||
switch (row.length) {
|
||||
case 1:
|
||||
if (row[0].length !== 0)
|
||||
console.log(`Warning: weird nonempty row at OHLC line ${lineNum}: "${line}"`)
|
||||
break
|
||||
case 2:
|
||||
time = parseInt(row[0])
|
||||
let price = parseFloat(row[1])
|
||||
if (inverted)
|
||||
price = 1/price
|
||||
open = latest === null ? price : latest[1]
|
||||
high = low = close = price
|
||||
break
|
||||
case 3:
|
||||
time = parseInt(row[0])
|
||||
if (time < from || time >= to)
|
||||
break
|
||||
case 2:
|
||||
time = parseInt(row[0])
|
||||
if (time < start || time >= to)
|
||||
break
|
||||
let price = parseFloat(row[1])
|
||||
if (inverted)
|
||||
price = 1/price
|
||||
open = latest === null ? price : latest[1]
|
||||
high = low = close = price
|
||||
break
|
||||
case 3:
|
||||
time = parseInt(row[0])
|
||||
if (time < start || time >= to)
|
||||
break
|
||||
open = parseFloat(row[1])
|
||||
close = parseFloat(row[2])
|
||||
if (inverted) {
|
||||
open = 1/open
|
||||
close = 1/close
|
||||
}
|
||||
high = Math.max(open, close)
|
||||
low = Math.min(open,close)
|
||||
if (latest!==null)
|
||||
open = latest[1]
|
||||
break
|
||||
case 5:
|
||||
time = parseInt(row[0])
|
||||
if (time < start || time >= to)
|
||||
break
|
||||
open = parseFloat(row[1])
|
||||
high = parseFloat(row[2])
|
||||
low = parseFloat(row[3])
|
||||
close = parseFloat(row[4])
|
||||
if (inverted) {
|
||||
open = 1/open
|
||||
const h = high
|
||||
high = 1/low
|
||||
low = 1/h
|
||||
close = 1/close
|
||||
}
|
||||
break
|
||||
default:
|
||||
console.log(`Warning: could not parse line ${lineNum} of OHLC file:\n${line}`)
|
||||
open = parseFloat(row[1])
|
||||
close = parseFloat(row[2])
|
||||
if (inverted) {
|
||||
open = 1/open
|
||||
close = 1/close
|
||||
}
|
||||
high = Math.max(open, close)
|
||||
low = Math.min(open,close)
|
||||
if (latest!==null)
|
||||
open = latest[1]
|
||||
break
|
||||
case 5:
|
||||
time = parseInt(row[0])
|
||||
if (time < from || time >= to)
|
||||
break
|
||||
open = parseFloat(row[1])
|
||||
high = parseFloat(row[2])
|
||||
low = parseFloat(row[3])
|
||||
close = parseFloat(row[4])
|
||||
if (inverted) {
|
||||
open = 1/open
|
||||
const h = high
|
||||
high = 1/low
|
||||
low = 1/h
|
||||
close = 1/close
|
||||
}
|
||||
break
|
||||
default:
|
||||
console.log(`Warning: could not parse line ${lineNum} of OHLC file:\n${line}`)
|
||||
break
|
||||
}
|
||||
if (time!==null) {
|
||||
if (time >= to) {
|
||||
console.log('time is past end of request:', time)
|
||||
finished = true
|
||||
break
|
||||
}
|
||||
if (close!==null) {
|
||||
fill(time, res.seconds)
|
||||
const bar = {time:time*1000, open, high, low, close};
|
||||
if ( time >= from ) {
|
||||
if ( latest !== null && time > latest[0] + res.seconds) {
|
||||
fill(time)
|
||||
}
|
||||
const bar = {time: time * 1000, open, high, low, close};
|
||||
bars.push(bar)
|
||||
latest = [time, close]
|
||||
}
|
||||
})
|
||||
// console.log(`processed ${lineNum} lines`)
|
||||
}
|
||||
}
|
||||
// else { console.log('response was empty') }
|
||||
// console.log(`processed ${lineNum} lines`)
|
||||
}
|
||||
fill(to, res.seconds)
|
||||
// console.log('loadOHLC prefill bars', bars)
|
||||
fill(to)
|
||||
// console.log('loadOHLC bars', bars)
|
||||
return bars
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {DISTANT_FUTURE, DISTANT_PAST} from "@/blockchain/orderlib.js";
|
||||
import {DISTANT_FUTURE, DISTANT_PAST, MAX_FRACTION} from "@/blockchain/orderlib.js";
|
||||
import {allocationText, DLine, HLine} from "@/charts/shape.js";
|
||||
import {createShape, deleteShapeId} from "@/charts/chart.js";
|
||||
import {timestamp} from "@/misc.js";
|
||||
import {createShape, deleteShapeId, widget} from "@/charts/chart.js";
|
||||
import {sideColor} from "@/misc.js";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {timestamp} from "@/common.js";
|
||||
|
||||
export class OrderShapes {
|
||||
constructor(symbol, orderStatus) {
|
||||
@@ -16,9 +17,11 @@ export class OrderShapes {
|
||||
for (const old of this.trancheShapes)
|
||||
old.delete()
|
||||
this.status = orderStatus
|
||||
this.trancheShapes = [];
|
||||
for (let i = 0; i < orderStatus.trancheStatus.length; i++)
|
||||
this.trancheShapes.push(new TrancheShapes(this.symbol, this.status, i));
|
||||
this.trancheShapes = []
|
||||
const maxAllocation = Math.max(...orderStatus.order.tranches.map((ts)=>ts.fraction)) / MAX_FRACTION
|
||||
if (orderStatus.trancheStatus !== undefined)
|
||||
for (let i = 0; i < orderStatus.trancheStatus.length; i++)
|
||||
this.trancheShapes.push(new TrancheShapes(this.symbol, this.status, i, maxAllocation));
|
||||
}
|
||||
|
||||
show() {for (const t of this.trancheShapes) t.show();}
|
||||
@@ -28,8 +31,7 @@ export class OrderShapes {
|
||||
|
||||
|
||||
class TrancheShapes {
|
||||
constructor(symbol, orderStatus, trancheIndex) {
|
||||
// todo validate base/quote
|
||||
constructor(symbol, orderStatus, trancheIndex, maxAllocation) {
|
||||
if (symbol.inverted !== orderStatus.order.inverted) {
|
||||
console.log('OrderShapes.createLine(): symbol has wrong inverson for this order')
|
||||
return
|
||||
@@ -38,14 +40,16 @@ class TrancheShapes {
|
||||
this.status = orderStatus
|
||||
this.trancheIndex = trancheIndex
|
||||
this.tranche = orderStatus.order.tranches[trancheIndex]
|
||||
this.trancheStatus = orderStatus.trancheStatus[trancheIndex]
|
||||
this.shapes = []
|
||||
this.fills = []
|
||||
this.createShapes();
|
||||
this.maxAllocation = maxAllocation
|
||||
this.createShapes()
|
||||
}
|
||||
|
||||
createShapes() {
|
||||
// todo amounts
|
||||
const t = this.tranche
|
||||
|
||||
// console.log('create tranche shapes', t)
|
||||
if (t.marketOrder) {
|
||||
if (t.startTime !== DISTANT_PAST) {
|
||||
@@ -53,8 +57,10 @@ class TrancheShapes {
|
||||
}
|
||||
} else {
|
||||
// check lines
|
||||
this.createLine(t.minLine.slope, t.minLine.intercept);
|
||||
this.createLine(t.maxLine.slope, t.maxLine.intercept);
|
||||
const amount = this.status.order.amount * BigInt(t.fraction) / BigInt(MAX_FRACTION)
|
||||
const buy = this.status.order.tokenIn === this.symbol.quote.a
|
||||
this.createLine(t.minLine.slope, t.minLine.intercept, amount, buy);
|
||||
this.createLine(t.maxLine.slope, t.maxLine.intercept, amount, !buy);
|
||||
}
|
||||
for (const f of this.status.trancheStatus[this.trancheIndex].fills)
|
||||
this.createFillPoint(f)
|
||||
@@ -72,67 +78,80 @@ class TrancheShapes {
|
||||
* 10 ** -(amountIsBase ? this.symbol.base.d : this.symbol.quote.d)
|
||||
const weight = Number(filledAmount) / Number(this.status.order.amount)
|
||||
const amountSymbol = amountIsBase ? this.symbol.base.s : this.symbol.quote.s
|
||||
console.log('fillpoint', buy, filledAmount, this.status.order, this.symbol)
|
||||
// console.log('fillpoint', buy, filledAmount, this.status.order, this.symbol)
|
||||
const time = f.time
|
||||
const out = Number(f.filledOut) / (1-this.status.order.route.fee/1000000)
|
||||
let price = out / Number(f.filledIn)
|
||||
if (buy)
|
||||
price = 1/price
|
||||
price *= scale
|
||||
console.log('price', price)
|
||||
// console.log('price', price)
|
||||
const channel = buy?'low':'high';
|
||||
const text = (buy ? 'Buy ' : 'Sell ') + allocationText(weight, amount, amountSymbol, '\n')
|
||||
const s = createShape(buy?'arrow_up':'arrow_down', {time, price}, {channel,text,lock:true})
|
||||
console.log('created fill shape at', time, price)
|
||||
const text = allocationText(buy, weight, amount, amountSymbol, amountIsBase ? null : this.symbol.base.s, 1, '\n')
|
||||
const color = sideColor(buy);
|
||||
const options = {channel,text,lock:true,overrides:{color: color, arrowColor: color}};
|
||||
const s = createShape(buy?'arrow_up':'arrow_down', {time, price}, options)
|
||||
console.log('created fill shape at', time, price, widget.activeChart().getShapeById(s).getProperties())
|
||||
this.fills.push(s)
|
||||
}
|
||||
|
||||
createLine(slope, intercept, amount) {
|
||||
createLine(slope, intercept, amountBigInt, breakout) {
|
||||
if (intercept === 0 && slope === 0) return
|
||||
const t = this.tranche
|
||||
const status = this.status
|
||||
const symbol = this.symbol
|
||||
const scale = 10**symbol.decimals;
|
||||
const buy = status.order.tokenIn === this.symbol.quote.a
|
||||
amount = Number(amount)
|
||||
* 10 ** -(buy ? this.symbol.base.d : this.symbol.quote.d)
|
||||
const decimals = buy ? this.symbol.base.d : this.symbol.quote.d;
|
||||
amountBigInt = BigInt(amountBigInt)
|
||||
const amount = Number(amountBigInt) * 10 ** - decimals
|
||||
const amountSymbol = buy ? this.symbol.base.s : this.symbol.quote.s
|
||||
const color = buy ? 'green' : 'red'
|
||||
if (intercept !== 0 || slope !== 0) {
|
||||
// console.log('tranche line', intercept, slope)
|
||||
// line active
|
||||
if (slope === 0) {
|
||||
let price = intercept
|
||||
price *= scale
|
||||
// horizontal line
|
||||
// console.log('hline', price)
|
||||
const model = {price, color, maxAllocation: status.order.amount, amount, amountSymbol};
|
||||
const s = new HLine(model, null, null, null, true)
|
||||
this.shapes.push(s)
|
||||
} else {
|
||||
// diagonal line
|
||||
let startTime = t.startTime
|
||||
if (startTime === DISTANT_PAST)
|
||||
startTime = timestamp() - useChartOrderStore().intervalSecs * 20 // 20 bars ago
|
||||
let endTime = t.endTime
|
||||
if (endTime === DISTANT_FUTURE)
|
||||
endTime = timestamp() // use "now" as the drawing point's time
|
||||
let startPrice = (intercept + slope * startTime);
|
||||
let endPrice = (intercept + slope * endTime);
|
||||
startPrice *= scale
|
||||
endPrice *= scale
|
||||
// console.log('dline', startTime, endTime, DISTANT_FUTURE, startPrice, endPrice)
|
||||
// noinspection EqualityComparisonWithCoercionJS
|
||||
const model = {
|
||||
pointA: {time: startTime, price: startPrice},
|
||||
pointB: {time: endTime, price: endPrice},
|
||||
extendLeft: t.startTime === DISTANT_PAST,
|
||||
extendRight: t.endTime === DISTANT_FUTURE,
|
||||
color,
|
||||
maxAllocation: status.order.amount, amount, amountSymbol,
|
||||
};
|
||||
const s = new DLine(model, null, null, null, true)
|
||||
this.shapes.push(s)
|
||||
const color = sideColor(buy)
|
||||
const maxAllocation = this.maxAllocation
|
||||
const allocation = t.fraction / MAX_FRACTION
|
||||
const ts = this.trancheStatus
|
||||
let sum = 0n
|
||||
for (let i=0; i<ts.fills.length; i++)
|
||||
sum += ts.fills[i].filled
|
||||
const completed = (amountBigInt - sum) < status.order.minFillAmount
|
||||
const extraText = completed ? '✓' : null
|
||||
const textLocation = breakout === buy ? 'above' : 'below'
|
||||
// line active
|
||||
if (slope === 0) {
|
||||
let price = intercept
|
||||
price *= scale
|
||||
// horizontal line
|
||||
// console.log('hline', price)
|
||||
const model = {
|
||||
price, breakout, color, extraText, textLocation,
|
||||
allocation, maxAllocation, amount, amountSymbol, buy,
|
||||
}
|
||||
const s = new HLine(model, null, null, null, true)
|
||||
this.shapes.push(s)
|
||||
} else {
|
||||
// diagonal line
|
||||
let startTime = t.startTime
|
||||
if (startTime === DISTANT_PAST)
|
||||
startTime = timestamp() - useChartOrderStore().intervalSecs * 20 // 20 bars ago
|
||||
let endTime = t.endTime
|
||||
if (endTime === DISTANT_FUTURE)
|
||||
endTime = timestamp() // use "now" as the drawing point's time
|
||||
let startPrice = (intercept + slope * startTime);
|
||||
let endPrice = (intercept + slope * endTime);
|
||||
startPrice *= scale
|
||||
endPrice *= scale
|
||||
// console.log('dline', startTime, endTime, DISTANT_FUTURE, startPrice, endPrice)
|
||||
// noinspection EqualityComparisonWithCoercionJS
|
||||
const model = {
|
||||
pointA: {time: startTime, price: startPrice},
|
||||
pointB: {time: endTime, price: endPrice},
|
||||
extendLeft: t.startTime === DISTANT_PAST,
|
||||
extendRight: t.endTime === DISTANT_FUTURE,
|
||||
breakout, color, extraText, textLocation,
|
||||
allocation, maxAllocation, amount, amountSymbol, buy,
|
||||
}
|
||||
const s = new DLine(model, null, null, null, true)
|
||||
this.shapes.push(s)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {invokeCallback, mixin} from "@/common.js";
|
||||
import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape, widget} from "@/charts/chart.js";
|
||||
import Color from "color";
|
||||
import {dirtyItems, dirtyPoints, nearestOhlcStart} from "@/charts/chart-misc.js";
|
||||
import {computeInterceptSlope} from "@/misc.js";
|
||||
import {defined, toPrecision} from "@/misc.js";
|
||||
|
||||
|
||||
//
|
||||
@@ -36,28 +36,35 @@ export const ShapeType = {
|
||||
HLine: {name: 'Horizontal Line', code: 'horizontal_line', drawingProp: 'linetoolhorzline'},
|
||||
VLine: {name: 'Vertical Line', code: 'vertical_line', drawingProp: 'linetoolvertline'},
|
||||
PriceRange: {name: 'Price Range', code: 'price_range'},
|
||||
DateRange: {name: 'Date Range', code: 'date_range', drawingProp: 'linetooldaterange'},
|
||||
}
|
||||
|
||||
|
||||
export function allocationText(weight, amount, symbol, separator = ' ') {
|
||||
export function allocationText(buy, weight, amount, baseSymbol, amountSymbol = null, parts = 1, separator = ' ') {
|
||||
const hasAmount = amount !== null && amount !== undefined && amount > 0
|
||||
if (hasAmount)
|
||||
amount = Number(amount)
|
||||
const hasWeight = weight !== null && weight !== undefined && weight !== 1
|
||||
if (hasWeight)
|
||||
weight = Number(weight)
|
||||
let text = ''
|
||||
let text = buy === undefined ? '' : buy ? 'Buy ' : 'Sell '
|
||||
if (hasWeight)
|
||||
text += `${(weight * 100).toFixed(1)}%`
|
||||
const hasSymbol = symbol !== null && symbol !== undefined
|
||||
const hasSymbol = baseSymbol !== null && baseSymbol !== undefined
|
||||
if (hasAmount && hasSymbol) {
|
||||
if (hasWeight)
|
||||
text += separator
|
||||
text += `${amount.toPrecision(3).toLocaleString('fullwide')} ${symbol}`
|
||||
if (amountSymbol!==null && amountSymbol!==baseSymbol)
|
||||
text += `${baseSymbol} worth ${toPrecision(amount,3)} ${amountSymbol}`
|
||||
else
|
||||
text += `${toPrecision(amount,3)} ${baseSymbol}`
|
||||
}
|
||||
if (parts > 1)
|
||||
text += separator + `in ${Math.round(parts)} parts`
|
||||
return text
|
||||
}
|
||||
|
||||
|
||||
export class Shape {
|
||||
|
||||
constructor(type, onModel=null, onDelete=null, props=null, readonly=false, overrides={}) {
|
||||
@@ -107,9 +114,10 @@ export class Shape {
|
||||
this.model.maxAllocation = null
|
||||
// both amount and amountSymbol must be set in order to display amount text
|
||||
this.model.amount = null
|
||||
this.model.baseSymbol = null
|
||||
this.model.amountSymbol = null
|
||||
this.model.extraText = null
|
||||
this.model.textLocation = 'above'
|
||||
this.model.textLocation = null // defaults to 'above' if not set
|
||||
|
||||
// LEAF SUBCLASSES MUST CALL setModel(model) AFTER ALL CONSTRUCTION.
|
||||
}
|
||||
@@ -119,20 +127,26 @@ export class Shape {
|
||||
//
|
||||
|
||||
setModel(model) {
|
||||
if (model.color)
|
||||
if (defined(model.color))
|
||||
this.model.color = model.color
|
||||
if (model.allocation !== null && model.allocation !== undefined)
|
||||
if (defined(model.allocation))
|
||||
this.model.allocation = model.allocation
|
||||
if (model.maxAllocation !== null && model.maxAllocation !== undefined)
|
||||
if (defined(model.maxAllocation))
|
||||
this.model.maxAllocation = model.maxAllocation
|
||||
if (model.amount !== null && model.amount !== undefined)
|
||||
if (defined(model.amount))
|
||||
this.model.amount = model.amount
|
||||
if (model.amountSymbol)
|
||||
if (defined(model.amountSymbol))
|
||||
this.model.amountSymbol = model.amountSymbol
|
||||
if (model.extraText)
|
||||
if (defined(model.baseSymbol))
|
||||
this.model.baseSymbol = model.baseSymbol
|
||||
if (defined(model.extraText))
|
||||
this.model.extraText = model.extraText
|
||||
if (model.textLocation)
|
||||
if (defined(model.breakout))
|
||||
this.model.breakout = model.breakout
|
||||
if (defined(model.textLocation))
|
||||
this.model.textLocation = model.textLocation
|
||||
if (defined(model.buy))
|
||||
this.model.buy = model.buy
|
||||
|
||||
const newProps = {}
|
||||
|
||||
@@ -141,7 +155,7 @@ export class Shape {
|
||||
newProps.textcolor = color
|
||||
|
||||
// line color
|
||||
if (this.model.allocation && this.model.maxAllocation) {
|
||||
if (defined(this.model.allocation) && defined(this.model.maxAllocation)) {
|
||||
const w = this.model.allocation / this.model.maxAllocation
|
||||
if (!w)
|
||||
newProps.linecolor = 'rgba(0,0,0,0)'
|
||||
@@ -157,7 +171,9 @@ export class Shape {
|
||||
newProps.linecolor = color
|
||||
|
||||
// text label
|
||||
let text = allocationText(this.model.allocation, this.model.amount, this.model.amountSymbol)
|
||||
let text = allocationText(this.model.buy, this.model.allocation, this.model.amount, this.model.baseSymbol, this.model.amountSymbol)
|
||||
if (this.model.breakout)
|
||||
text += ' ' + (this.model.textLocation==='above' ? '▲Breakout▲' : '▼Breakdown▼')
|
||||
if (this.model.extraText)
|
||||
text += ' '+this.model.extraText
|
||||
if (this.debug) text = `${this.id} ` + text
|
||||
@@ -199,10 +215,13 @@ export class Shape {
|
||||
const p = this.type.drawingProp
|
||||
const lc = this.model.lineColor ? this.model.lineColor : this.model.color;
|
||||
const tc = this.model.textColor ? this.model.textColor : this.model.color;
|
||||
const tl = this.model.textLocation ? this.model.textLocation : 'above';
|
||||
if (lc)
|
||||
o[p+".linecolor"] = lc
|
||||
if (tc)
|
||||
o[p+".textcolor"] = tc
|
||||
if (tl)
|
||||
o[p+".textlocation"] = tl
|
||||
return o
|
||||
}
|
||||
|
||||
@@ -312,14 +331,16 @@ export class Shape {
|
||||
}
|
||||
|
||||
// diagonals need to override this to adjust their price as well.
|
||||
pointsToTvOhlcStart(points, periodSeconds=null) {
|
||||
return points === null ? null : points.map((p) => {
|
||||
return {time: nearestOhlcStart(p.time, periodSeconds), price: p.price}
|
||||
})
|
||||
}
|
||||
pointsToTvOhlcStart(points, periodSeconds = null) {
|
||||
return points === null ? null : points.map((p) => {
|
||||
return {time: nearestOhlcStart(p.time, periodSeconds), price: p.price}
|
||||
})
|
||||
}
|
||||
|
||||
onPoints(points) {} // the control points of an existing shape were changed
|
||||
|
||||
onDrag(points) { this.onPoints(points) }
|
||||
|
||||
setProps(props) {
|
||||
if (!props || Object.keys(props).length===0) return
|
||||
if (this.debug) console.log('setProps', this.id, props)
|
||||
@@ -369,7 +390,6 @@ export class Shape {
|
||||
onUndraw() {} // drawing was canceled by clicking on a different tool
|
||||
onAddPoint() {} // the user clicked a point while drawing (that point is added to the points list)
|
||||
onMove(points) {} // the shape was moved by dragging a drawing element not the control point
|
||||
onDrag(points) {}
|
||||
onHide(props) {}
|
||||
onShow(props) {}
|
||||
onClick() {} // the shape was selected
|
||||
@@ -464,16 +484,6 @@ class ShapeTVCallbacks {
|
||||
|
||||
|
||||
export class Line extends Shape {
|
||||
onDrag(points) {
|
||||
const s = this.tvShape();
|
||||
if (this.debug) {
|
||||
console.log('shape', s.id, s)
|
||||
console.log('currentMovingPoint', s._source.currentMovingPoint())
|
||||
console.log('startMovingPoint', s._source.startMovingPoint())
|
||||
console.log('isBeingEdited', s._source.isBeingEdited())
|
||||
console.log('state', s._source.state())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -641,3 +651,17 @@ export class DLine extends Line {
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
export class DateRange extends Shape {
|
||||
constructor(model, onModel=null, onDelete=null, props=null) {
|
||||
super(ShapeType.DateRange, onModel, onDelete, props)
|
||||
}
|
||||
|
||||
|
||||
setModel(model) {
|
||||
super.setModel(model);
|
||||
if (model.startTime !== this.model.startTime || model.endTime !== this.model.endTime)
|
||||
this.setPoints([{time: model.startTime}, {time: model.endTime}])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export const NATIVE_TOKEN = '0x0000000000000000000000000000000000000001'
|
||||
export const USD_FIAT = '0x0000000000000000000000000000000000000055' // We use 0x55 (ASCII 'U') to indicate the use of fiat USD
|
||||
|
||||
export function mixin(child, ...parents) {
|
||||
// child is modified directly, assigning fields from parents that are missing in child. parents fields are
|
||||
// assigned by parents order, highest priority first
|
||||
@@ -133,3 +136,55 @@ export function abiPath(name) {
|
||||
}
|
||||
|
||||
|
||||
export function parseFill(obj) {
|
||||
let [tx, time, filledIn, filledOut, fee] = obj
|
||||
time = new Date(time * 1000)
|
||||
filledIn = BigInt(filledIn)
|
||||
filledOut = BigInt(filledOut)
|
||||
fee = BigInt(fee)
|
||||
return {tx, time, filledIn, filledOut, fee}
|
||||
}
|
||||
|
||||
export function timestamp(date = null) {
|
||||
if (date === null)
|
||||
date = new Date()
|
||||
return Math.round(date.getTime() / 1000)
|
||||
}
|
||||
|
||||
export function dateString(datetime) {
|
||||
return datetime.toLocaleString({dateStyle: 'medium', timeStyle: 'short'})
|
||||
}
|
||||
|
||||
export function logicalXOR(a, b) {
|
||||
return (a || b) && !(a && b)
|
||||
}
|
||||
|
||||
// Base62
|
||||
|
||||
// base62.js
|
||||
const base62charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
export function encodeBase62(num) {
|
||||
if (num === 0) return base62charset[0];
|
||||
let encoded = "";
|
||||
while (num > 0) {
|
||||
encoded = base62charset[num % 62] + encoded;
|
||||
num = Math.floor(num / 62);
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
export function decodeBase62(str) {
|
||||
return str.split('').reverse().reduce((acc, char, i) =>
|
||||
acc + base62charset.indexOf(char) * Math.pow(62, i), 0);
|
||||
}
|
||||
|
||||
|
||||
export function withTimeout(promise, timeoutDuration, fallbackErrorMessage) {
|
||||
return Promise.race([
|
||||
promise,
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error(fallbackErrorMessage)), timeoutDuration)
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store";
|
||||
import {usePrefStore, useStore} from "@/store/store";
|
||||
import {computed} from "vue";
|
||||
import {DateTime, Info} from "luxon";
|
||||
|
||||
const s = useStore()
|
||||
const prefs = usePrefStore()
|
||||
const props = defineProps(['modelValue'])
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const hideDetails = true
|
||||
|
||||
const now = computed(()=>DateTime.fromSeconds(props.modelValue?props.modelValue:0).setZone(s.timeZone))
|
||||
const now = computed(()=>DateTime.fromSeconds(props.modelValue?props.modelValue:0).setZone(prefs.timezone))
|
||||
|
||||
const year = computed({
|
||||
get() { return now.value.year },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<v-alert class="d-sm-none" type="info" title="Mobile Screen" text="Dexorder is designed for desktop. Mobile coming soon!" rounded="0"/>
|
||||
<v-alert v-if='!s.connected' icon="mdi-wifi-off" type="error" title="Not Connected" class="mb-3" rounded="0" density="compact"/>
|
||||
<v-alert v-for="e in s.errors" icon="mdi-alert" type="error" :title="e.title" :text="e.text" class="mb-3" :closable="e.closeable" rounded="0"/>
|
||||
</template>
|
||||
|
||||
42
src/components/ApproveRegion.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<slot v-if="s.regionApproved"/>
|
||||
<v-card v-if="s.regionApproved===null" title="Loading..." prepend-icon="mdi-clock-outline"
|
||||
text="If loading takes more than a second, please refresh your browser or contact support@dexorder.trade"/>
|
||||
<v-card v-if="s.regionApproved===false" title="Restricted" prepend-icon="mdi-alert" color="yellow-darken-1"
|
||||
text="Dexorder is not available in your region." rounded="0"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store";
|
||||
import {useRoute} from "vue-router";
|
||||
import {socket} from "@/socket.js";
|
||||
|
||||
const s = useStore()
|
||||
|
||||
let timer = null
|
||||
|
||||
const bypass = useRoute().query.approval
|
||||
|
||||
function tryApproval() {
|
||||
console.log('trying region approval')
|
||||
if (timer!==null) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
if (s.regionApproved===true) {
|
||||
console.log('region approved')
|
||||
}
|
||||
else if (s.regionApproved===null) {
|
||||
console.log('asking for region approval', bypass)
|
||||
socket.emit('approveRegion', bypass)
|
||||
timer = setTimeout(tryApproval, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
tryApproval()
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<!--
|
||||
<v-chip text="BETA" size='x-small' color="red" class="align-self-start" variant="text"/>
|
||||
-->
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<template>
|
||||
<slot v-if="s.allowed"/>
|
||||
<v-card v-if="!s.allowed" rounded="0" title="Dexorder Closed Beta">
|
||||
<v-card-item><v-text-field v-model="password" label="Beta Password" class="maxw"/></v-card-item>
|
||||
<v-card-text>
|
||||
Follow our social media for public release updates!
|
||||
</v-card-text>
|
||||
<v-card-item><social/></v-card-item>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store";
|
||||
import Social from "@/components/Social.vue";
|
||||
import {ref, watchEffect} from "vue";
|
||||
import {hashMessage} from "ethers";
|
||||
|
||||
const s = useStore()
|
||||
|
||||
const password = ref(null)
|
||||
const passHash = '0x3e6e96bd824dd0a5e5361853719ef051e939b91f3fc6fd0f685b4c414c7ba89e'
|
||||
|
||||
watchEffect(()=>{
|
||||
if (!password.value) return
|
||||
const canonical = password.value.replace(/[^a-zA-Z]/g, '').toLowerCase();
|
||||
const hash = hashMessage(canonical);
|
||||
if (hash===passHash)
|
||||
s.allowed = true
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,14 +1,16 @@
|
||||
<template>
|
||||
<v-tooltip :model-value="!error&&copied" :open-on-hover="false" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props" style="cursor: pointer" @click="copy()">
|
||||
<div class="d-inline-flex align-center" v-bind="props" style="cursor: pointer" @click="copy()">
|
||||
<slot>
|
||||
<span :style="{maxWidth:width}" style="display: inline-block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{ text }}</span>
|
||||
</slot>
|
||||
<v-btn v-bind="props" v-if="permitted" rounded variant="text" size="small" density="compact" @click="copy()"
|
||||
:class="error?'error':copied?'success':''"
|
||||
:icon="error?'mdi-close-box-outline':copied?'mdi-check-circle-outline':'mdi-content-copy'"
|
||||
:text="showText?text:''"
|
||||
/>
|
||||
<slot>{{text}}</slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<span>Copied!</span>
|
||||
</v-tooltip>
|
||||
@@ -17,7 +19,7 @@
|
||||
<script setup lang="ts">
|
||||
import {ref} from "vue";
|
||||
|
||||
const props = defineProps({text:String, showText:{default:false}})
|
||||
const props = defineProps({text:String, showText:{default:false}, width: {default:null}})
|
||||
const permitted = ref(true)
|
||||
const copied = ref(false)
|
||||
const error = ref(false)
|
||||
|
||||
65
src/components/DebugConsole.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="debug-console">
|
||||
<div v-for="(entry, idx) in logs" :key="idx" :class="['log-entry', entry.type]">
|
||||
[{{ entry.type }}] {{ entry.message }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
|
||||
const logs = ref([]);
|
||||
|
||||
function stringify(args) {
|
||||
return args.map(arg => {
|
||||
if (typeof arg === 'object') {
|
||||
try { return JSON.stringify(arg); } catch { return String(arg); }
|
||||
}
|
||||
return String(arg);
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
let original = {};
|
||||
|
||||
onMounted(() => {
|
||||
['log', 'warn', 'error', 'info'].forEach(type => {
|
||||
// Save the original method
|
||||
original[type] = console[type];
|
||||
console[type] = function(...args) {
|
||||
logs.value.push({
|
||||
type,
|
||||
message: stringify(args)
|
||||
});
|
||||
// Optionally limit log length:
|
||||
if (logs.value.length > 200) logs.value.shift();
|
||||
original[type].apply(console, args);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// Restore the original methods
|
||||
Object.keys(original).forEach(type => {
|
||||
if (original[type]) console[type] = original[type];
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.debug-console {
|
||||
background: #1a1a1a;
|
||||
color: #0f0;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
border-radius: 5px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.log-entry { margin-bottom: 2px; }
|
||||
.log-entry.warn { color: #ff0; }
|
||||
.log-entry.error { color: #f55; }
|
||||
.log-entry.info { color: #0cf; }
|
||||
</style>
|
||||
@@ -32,7 +32,8 @@ import {applyLinePoints, linePointsValue, useChartOrderStore} from "@/orderbuild
|
||||
import {newTranche} from "@/blockchain/orderlib.js";
|
||||
import TimeEntry from "@/components/TimeEntry.vue";
|
||||
import RoutePrice from "@/components/RoutePrice.vue";
|
||||
import {timestamp} from "@/misc.js";
|
||||
|
||||
import {timestamp} from "@/common.js";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
import {useStore} from "@/store/store";
|
||||
import {computed, ref} from "vue";
|
||||
import Btn from "@/components/Btn.vue";
|
||||
import {socket} from "@/socket.js";
|
||||
import {metadata} from "@/version.js";
|
||||
import {socket} from "@/socket.js";
|
||||
|
||||
const DISABLED_DURATION = 60_000;
|
||||
|
||||
|
||||
142
src/components/FloatingDiv.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div
|
||||
ref="floatingDiv"
|
||||
:style="divStyle"
|
||||
@mousedown="startDrag"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
}
|
||||
});
|
||||
|
||||
const floatingDiv = ref(null);
|
||||
const position = reactive({ x: 0, y: 0 });
|
||||
const isDragging = ref(false);
|
||||
const isFloating = ref(false);
|
||||
const dragStartOffset = reactive({ x: 0, y: 0 });
|
||||
|
||||
const divStyle = reactive({
|
||||
position: "",
|
||||
top: "",
|
||||
left: "",
|
||||
zIndex: "",
|
||||
cursor: "default",
|
||||
});
|
||||
|
||||
let activeComponent = null;
|
||||
|
||||
function positionKey() {
|
||||
return props.id ? `floating-div-pos:${props.id}` : null;
|
||||
}
|
||||
|
||||
function savePosition() {
|
||||
if (props.id) {
|
||||
localStorage.setItem(
|
||||
positionKey(),
|
||||
JSON.stringify({ x: position.x, y: position.y })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function loadPosition() {
|
||||
if (props.id) {
|
||||
const data = localStorage.getItem(positionKey());
|
||||
if (data) {
|
||||
try {
|
||||
const { x, y } = JSON.parse(data);
|
||||
position.x = x;
|
||||
position.y = y;
|
||||
divStyle.position = "fixed";
|
||||
divStyle.top = `${position.y}px`;
|
||||
divStyle.left = `${position.x}px`;
|
||||
divStyle.zIndex = 9999;
|
||||
isFloating.value = true;
|
||||
} catch {
|
||||
// Ignore corrupted data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const startDrag = (event) => {
|
||||
if (event.shiftKey || event.ctrlKey) {
|
||||
if (!isFloating.value) {
|
||||
const rect = floatingDiv.value.getBoundingClientRect();
|
||||
position.x = rect.left;
|
||||
position.y = rect.top;
|
||||
divStyle.position = "fixed";
|
||||
divStyle.top = `${position.y}px`;
|
||||
divStyle.left = `${position.x}px`;
|
||||
divStyle.zIndex = 9999;
|
||||
isFloating.value = true;
|
||||
}
|
||||
|
||||
isDragging.value = true;
|
||||
dragStartOffset.x = event.clientX - position.x;
|
||||
dragStartOffset.y = event.clientY - position.y;
|
||||
divStyle.cursor = "move";
|
||||
document.body.style.userSelect = "none";
|
||||
|
||||
activeComponent = floatingDiv;
|
||||
|
||||
window.addEventListener("mousemove", handleDrag);
|
||||
window.addEventListener("mouseup", stopDrag);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrag = (event) => {
|
||||
if (isDragging.value && activeComponent === floatingDiv) {
|
||||
position.x = event.clientX - dragStartOffset.x;
|
||||
position.y = event.clientY - dragStartOffset.y;
|
||||
divStyle.top = `${position.y}px`;
|
||||
divStyle.left = `${position.x}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const stopDrag = () => {
|
||||
if (isDragging.value && activeComponent === floatingDiv) {
|
||||
isDragging.value = false;
|
||||
divStyle.cursor = "default";
|
||||
document.body.style.userSelect = "auto";
|
||||
activeComponent = null;
|
||||
|
||||
savePosition();
|
||||
|
||||
window.removeEventListener("mousemove", handleDrag);
|
||||
window.removeEventListener("mouseup", stopDrag);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOutsideDrag = (event) => {
|
||||
if (event.key === "Shift" || event.key === "Control") {
|
||||
stopDrag();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("keyup", handleOutsideDrag);
|
||||
loadPosition();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("keyup", handleOutsideDrag);
|
||||
window.removeEventListener("mousemove", handleDrag);
|
||||
window.removeEventListener("mouseup", stopDrag);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div[ref="floatingDiv"] {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,8 @@
|
||||
<v-footer class="text-center d-flex flex-column mb-12" location="bottom">
|
||||
<social/>
|
||||
<!-- <div class="text-grey logo" style="font-size: small; margin-bottom: 0;">dexorder</div>-->
|
||||
<div class="text-grey" style="font-size: x-small">©{{ new Date().getFullYear() }} Dexorder LLC</div>
|
||||
<!-- <div class="text-grey" style="font-size: x-small">©{{ new Date().getFullYear() }} Dexorder LLC</div>-->
|
||||
<div class="text-grey" style="font-size: x-small">Dexorder Trading Services Ltd., British Virgin Islands</div>
|
||||
</v-footer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-model="os.tranches" :rules="[validateRequired,validateTranches]">
|
||||
<template v-slot:append-inner>tranches</template>
|
||||
</v-text-field>
|
||||
<v-text-field label='Skew' type="number" step="10" aria-valuemin="0" min="-100" max="100" variant="outlined"
|
||||
<v-text-field label='Balance' type="number" step="10" aria-valuemin="0" min="-100" max="100" variant="outlined"
|
||||
v-model="skew" clearable @click:clear="skew=0" suffix="%"/>
|
||||
<!-- todo deadline -->
|
||||
<v-table>
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
|
||||
<script setup>
|
||||
import {useOrderStore, useStore} from "@/store/store";
|
||||
import {routeInverted} from "@/misc.js";
|
||||
import RoutePrice from "@/components/RoutePrice.vue";
|
||||
import {validateAmount, validateRequired} from "@/validate.js";
|
||||
import {computed} from "vue";
|
||||
// noinspection ES6UnusedImports
|
||||
import {vAutoSelect} from "@/misc.js";
|
||||
import {routeInverted} from "@/misc.js";
|
||||
|
||||
const os = useOrderStore()
|
||||
const props = defineProps({
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
import PairPrice from "@/components/PairPrice.vue";
|
||||
import {computed} from "vue";
|
||||
import {computed, defineAsyncComponent} from "vue";
|
||||
import {useStore} from "@/store/store.js";
|
||||
const PairPrice = defineAsyncComponent(()=>import("@/components/PairPrice.vue"))
|
||||
|
||||
const props = defineProps(['base', 'quote', 'm', 'b', 'isBreakout', 'showBtn', 'buy'])
|
||||
const s = useStore()
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
<template>
|
||||
<span class="d-inline-flex align-center">
|
||||
<v-icon icon="mdi-arrow-up-bold" color="primary" class="arrow"/>
|
||||
<span class="logo">dexorder</span>
|
||||
<beta v-if="showTag"/>
|
||||
</span>
|
||||
<div class="d-inline-flex">
|
||||
<v-img v-if="variant==='full'" :src="`/logo/dexorder_full_${s.theme}mode.svg`" width="6em" inline/>
|
||||
<v-img v-if="variant==='icon'" :src="`/logo/ico_${s.theme==='dark'?'black':'white'}_clip.png`" :width="width" inline/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import Beta from "@/components/Beta.vue";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {computed} from "vue";
|
||||
|
||||
const s = useStore()
|
||||
const props = defineProps({
|
||||
showTag: {type: Boolean, default: false}
|
||||
variant: {type: String, default: 'full', /* 'icon', */ },
|
||||
size: {type: String, default: 'medium'},
|
||||
})
|
||||
|
||||
const width = computed(()=>{
|
||||
return {
|
||||
'x-small': '0.75em',
|
||||
'small': '1.0em',
|
||||
'medium': '1.25em',
|
||||
'large': '1.75em',
|
||||
'x-large': '2.5em',
|
||||
}[props.size] || props.size;
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -26,9 +26,8 @@
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store";
|
||||
import {computed, ref} from "vue";
|
||||
import {vaultContract} from "@/blockchain/contract.js"
|
||||
import {pendTransaction} from "@/blockchain/wallet.js";
|
||||
import {FixedNumber} from "ethers";
|
||||
import {WrapTransaction} from "@/blockchain/transaction.js";
|
||||
|
||||
const s = useStore()
|
||||
const props = defineProps(['modelValue', 'vault', 'maxAmount'])
|
||||
@@ -45,10 +44,7 @@ function wrapNative() {
|
||||
if( amount === 0n )
|
||||
return
|
||||
console.log('pending wrap', valueStr, amount)
|
||||
pendTransaction(async (signer)=>{
|
||||
const vault = await vaultContract(vaultAddr, signer)
|
||||
return await vault.wrap(amount)
|
||||
})
|
||||
new WrapTransaction(s.chainId, vaultAddr, amount).submit()
|
||||
floatAmount.value = 0
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store";
|
||||
import router from "@/router/index.js";
|
||||
import {router} from "@/router/router.js";
|
||||
const s = useStore()
|
||||
|
||||
function nav(path) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<beta-signin>
|
||||
<approve-region>
|
||||
<slot v-if="status===Status.OK" v-bind="$props"/>
|
||||
<v-card v-if="status!==Status.OK" rounded="0">
|
||||
<v-card-title>
|
||||
<!-- <v-icon icon="mdi-hand-wave" color="grey"/>-->
|
||||
Welcome to Dexorder Beta!
|
||||
Welcome to Dexorder!
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
Play with the order builder without an account by clicking on the <logo class="logo-small"/> logo or on
|
||||
@@ -33,16 +33,19 @@
|
||||
</v-card-actions>
|
||||
|
||||
</v-card>
|
||||
</beta-signin>
|
||||
<terms-of-service v-if="status===Status.OK"/>
|
||||
</approve-region>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store";
|
||||
import {computed, ref} from "vue";
|
||||
import {addNetwork, connectWallet, switchChain} from "@/blockchain/wallet.js";
|
||||
import {addNetworkAndConnectWallet} from "@/blockchain/wallet.js";
|
||||
import Btn from "@/components/Btn.vue";
|
||||
import Logo from "@/components/Logo.vue";
|
||||
import BetaSignin from "@/components/BetaSignin.vue";
|
||||
import ApproveRegion from "@/components/ApproveRegion.vue";
|
||||
import TermsOfService from "@/components/TermsOfService.vue";
|
||||
import {track} from "@/track.js";
|
||||
|
||||
const s = useStore()
|
||||
const disabled = ref(false)
|
||||
@@ -67,32 +70,7 @@ function reload() {
|
||||
async function connect() {
|
||||
disabled.value = true
|
||||
try {
|
||||
try {
|
||||
await switchChain(s.chainId)
|
||||
}
|
||||
catch (e) {
|
||||
if (e.code===4001) {
|
||||
// explicit user rejection
|
||||
return
|
||||
}
|
||||
else if (e.code===4902) {
|
||||
try {
|
||||
await addNetwork(s.chainId)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(`Could not add network ${s.chainId}`)
|
||||
}
|
||||
}
|
||||
else
|
||||
console.log('switchChain() failure',e)
|
||||
}
|
||||
try {
|
||||
await connectWallet(s.chainId)
|
||||
}
|
||||
catch (e) {
|
||||
if (e.code!==4001)
|
||||
console.log('connectWallet() failed', e)
|
||||
}
|
||||
await addNetworkAndConnectWallet(s.chainId);
|
||||
}
|
||||
finally {
|
||||
disabled.value = false
|
||||
|
||||
40
src/components/OneTimeHint.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<v-tooltip v-model="show" :close-on-content-click="true" :close-on-back="false" :close-delay="null"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, ref} from "vue";
|
||||
import {usePrefStore} from "@/store/store.js";
|
||||
|
||||
const prefs = usePrefStore()
|
||||
|
||||
const props = defineProps({
|
||||
name: {type: String, required: true},
|
||||
when: {type: Boolean, default: true}, // optional conditional for when to show
|
||||
after: {type: String, default: null}, // set to the name of another hint that must happen before this hint, to chain hints into a tutorial.
|
||||
onComplete: {type: Function, default: null},
|
||||
})
|
||||
|
||||
const forceClose = ref(false)
|
||||
const shown = ref(false)
|
||||
|
||||
const show = computed({
|
||||
get() {
|
||||
const shownBefore = prefs.hints[props.name];
|
||||
const whenOk = props.when;
|
||||
const afterOk = props.after === null || prefs.hints[props.after];
|
||||
const result = !forceClose.value && !shownBefore && whenOk && afterOk
|
||||
// console.log(`show ${props.name}? ${result} <=`, !forceClose.value, whenOk, afterOk, prefs.hints)
|
||||
if (result) {
|
||||
shown.value = true
|
||||
prefs.hints[props.name] = true
|
||||
}
|
||||
return result
|
||||
},
|
||||
set(v) { if(!v) { forceClose.value=true; if (shown.value && props.onComplete) props.onComplete(); } }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -11,7 +11,8 @@
|
||||
</div>
|
||||
</v-card-item>
|
||||
<v-card-actions class="d-flex justify-space-evenly mb-4">
|
||||
<v-btn variant="outlined" color="red" @click="nav('Assets')">Cancel</v-btn>
|
||||
<v-btn variant="outlined" color="red" @click="// noinspection JSIgnoredPromiseFromCall
|
||||
router.push({name: 'Assets'})">Cancel</v-btn>
|
||||
<v-btn variant="flat" color="green" :disabled="!valid()" @click="placeOrder">Place Dexorder</v-btn>
|
||||
</v-card-actions>
|
||||
</phone-card>
|
||||
@@ -23,14 +24,13 @@ import {useOrderStore, useStore} from "@/store/store";
|
||||
import PhoneCard from "@/components/PhoneCard.vue";
|
||||
import Amount from "@/components/Amount.vue"
|
||||
// noinspection ES6UnusedImports
|
||||
import {nav, routeInverted, SingletonCoroutine, vAutoSelect} from "@/misc.js";
|
||||
import {SingletonCoroutine, vAutoSelect} from "@/misc.js";
|
||||
import {newOrder} from "@/blockchain/orderlib.js";
|
||||
import {FixedNumber} from "ethers";
|
||||
import {pendOrder} from "@/blockchain/wallet.js";
|
||||
import router from "@/router/index.js";
|
||||
import PairChoice from "@/components/PairChoice.vue";
|
||||
import NeedsSigner from "@/components/NeedsSigner.vue";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {router} from "@/router/router.js";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
|
||||
@@ -33,10 +33,9 @@
|
||||
import TokenChoice from "@/components/TokenChoice.vue"
|
||||
import {useOrderStore, useStore} from "@/store/store";
|
||||
import RoutePrice from "@/components/RoutePrice.vue";
|
||||
import {findRoute, routeFinder} from "@/blockchain/route.js";
|
||||
import {SingletonCoroutine, routeInverted} from "@/misc.js";
|
||||
import {computed, ref} from "vue";
|
||||
import {queryHelperContract} from "@/blockchain/contract.js";
|
||||
import {routeFinder} from "@/blockchain/route.js";
|
||||
import {computed} from "vue";
|
||||
import {routeInverted} from "@/misc.js";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
|
||||
@@ -18,7 +18,6 @@ import {flipInversionPreference, inversionPreference} from "@/misc.js";
|
||||
const props = defineProps(['value', 'base', 'quote', 'showBtn'])
|
||||
|
||||
const s = useStore()
|
||||
const prefs = usePrefStore()
|
||||
|
||||
async function token(obj) {
|
||||
if (!obj) return null
|
||||
@@ -39,15 +38,13 @@ function pairPrice(chainId, baseToken, quoteToken, price) {
|
||||
if (price === null || price === undefined)
|
||||
return null
|
||||
const decimals = quoteToken.d - baseToken.d
|
||||
// console.log('pairPrice', chainId, baseToken, quoteToken, price, decimals)
|
||||
if (decimals >= 0)
|
||||
price /= 10 ** decimals
|
||||
else
|
||||
price *= 10 ** -decimals
|
||||
// console.log('adjusted pairPrice', price)
|
||||
if (inversionPreference(chainId, baseToken, quoteToken))
|
||||
const pref = inversionPreference(chainId, baseToken, quoteToken);
|
||||
if (pref)
|
||||
price = 1 / price
|
||||
// console.log('inverted?', price)
|
||||
return price
|
||||
}
|
||||
|
||||
|
||||
84
src/components/PoolSelectionDialog.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<v-dialog v-model="co.showPoolSelection" max-width="300">
|
||||
<v-card title="Pool Selection">
|
||||
<v-card-text>
|
||||
<div class="mb-2">
|
||||
<v-avatar image="/arbitrum-logo.svg" size="x-small" class="mr-1"/> Arbitrum One
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<v-avatar image="/uniswap-logo.svg" size="x-small" class="mr-1" style="background-color: white"/> Uniswap v3
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td>Pool</td><td><scanner-button :addr="co.selectedSymbol.address"/></td></tr>
|
||||
<tr><td>{{ co.selectedSymbol.base.s }}</td><td><scanner-button :addr="co.selectedSymbol.base.a"/></td></tr>
|
||||
<tr><td>{{ co.selectedSymbol.quote.s }}</td><td><scanner-button :addr="co.selectedSymbol.quote.a"/></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<v-table class="mt-4">
|
||||
<thead>
|
||||
<tr><th>Selected</th><th>Fee</th><td>Liquidity</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="([addr,fee],i) in co.selectedSymbol.feeGroup"
|
||||
:class="co.selectedSymbol.fee === fee ? 'selected' : 'selectable'" @click="selectFee(fee)">
|
||||
<td><v-icon v-if="co.selectedSymbol.fee===fee" icon="mdi-check"/></td>
|
||||
<td>{{(fee/10_000).toFixed(2)}}%</td>
|
||||
<td>{{ !co.selectedSymbol.liquiditySymbol ? '' :
|
||||
co.selectedSymbol.liquiditySymbol === 'USD' ?
|
||||
`$${toHuman(co.selectedSymbol.liquidities[i])}` :
|
||||
toHuman(co.selectedSymbol.liquidities[i]) +' '+co.selectedSymbol.liquiditySymbol }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {toHuman} from "@/misc.js";
|
||||
import ScannerButton from "@/components/ScannerButton.vue";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {feelessTickerKey} from "@/charts/datafeed.js";
|
||||
import {setSymbolTicker} from "@/charts/chart.js";
|
||||
|
||||
const co = useChartOrderStore()
|
||||
|
||||
|
||||
function selectFee(fee) {
|
||||
if (fee === co.selectedSymbol.fee) return
|
||||
const symbol = co.selectedSymbol;
|
||||
const ticker = feelessTickerKey(symbol.ticker) + '|' + fee
|
||||
if (ticker !== symbol.ticker)
|
||||
setSymbolTicker(ticker).catch((e) => console.error('Could not change TV symbol to', ticker))
|
||||
co.showPoolSelection = false
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "src/styles/vars" as *;
|
||||
|
||||
td {
|
||||
padding-right: 1em;
|
||||
}
|
||||
tr.selected {
|
||||
|
||||
}
|
||||
tr.selectable {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.pool-button {
|
||||
color: blue;
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
@@ -16,6 +16,7 @@ function pulse() {
|
||||
if (props.touch === lastValue) return
|
||||
lastValue = props.touch
|
||||
const e = element.value;
|
||||
if (!e) return
|
||||
if (!e.classList.contains('pulse')) {
|
||||
// add class for the first time
|
||||
e.classList.add('pulse')
|
||||
@@ -41,10 +42,10 @@ watchEffect(pulse)
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
background-color: gray;
|
||||
background-color: #888;
|
||||
}
|
||||
100% {
|
||||
background-color: white;
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -7,8 +7,8 @@ import {useOrderStore, useStore} from "@/store/store";
|
||||
import {subPrices, unsubPrices, WIDE_PRICE_FORMAT} from "@/blockchain/prices.js";
|
||||
import {computed, onBeforeUnmount} from "vue";
|
||||
import {FixedNumber} from "ethers";
|
||||
import {routeInverted} from "@/misc.js";
|
||||
import {subOHLCs} from "@/blockchain/ohlcs.js";
|
||||
import {routeInverted} from "@/misc.js";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
|
||||
34
src/components/ScannerButton.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="d-inline-flex align-center" style="cursor: pointer">
|
||||
<span @click='click'
|
||||
:style="{maxWidth: width, width}" style="white-space: nowrap; display: inline-block; overflow: hidden; text-overflow: ellipsis">{{addr}}</span>
|
||||
<v-icon icon="mdi-open-in-new" size="x-small"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed} from "vue";
|
||||
import {useStore} from "@/store/store.js";
|
||||
|
||||
const props = defineProps({chainId: {type: Number, default:null}, addr:String, width: {default:'5em'}})
|
||||
const s = useStore()
|
||||
|
||||
function click() {
|
||||
window.open(url.value, '_blank')
|
||||
}
|
||||
|
||||
const url = computed(()=>{
|
||||
const chain = props.chainId ? props.chainId: s.chainId
|
||||
switch (chain) {
|
||||
case 31337:
|
||||
case 1337:
|
||||
case 42161:
|
||||
return `https://arbiscan.io/address/${props.addr}`
|
||||
}
|
||||
throw new Error(`No scanner defined for chain ${chain}`)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <v-btn variant="plain" icon="mdi-linkedin" @click="open('https://www.linkedin.com/company/dexorder','dexorder_linkedin')" :size="size"/>-->
|
||||
<v-btn variant="plain" icon="mdi-discord" @click="open('https://discord.gg/fqp9JXXQyt','dexorder_discord')" :size="size"/>
|
||||
<v-btn variant="plain" icon="mdi-twitter" @click="open('https://twitter.com/Dexorder_trade','dexorder_twitter')" :size="size"/>
|
||||
<v-btn variant="plain" icon="mdi-discord" @click="open('https://discord.gg/fqp9JXXQyt','dexorder_discord')" :size="size"/>
|
||||
<v-btn variant="plain" icon="mdi-github" @click="open('https://github.com/dexorder-trade/contract','dexorder_github')" :size="size"/>
|
||||
<!-- <v-btn variant="plain" icon="mdi-facebook" @click="open('https://facebook.com','dexorder_facebook')" :size="size"/>-->
|
||||
<!-- <v-btn variant="plain" icon="mdi-reddit" @click="open('https://reddit.com','dexorder_reddit')" :size="size"/>-->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="top">
|
||||
<slot name="top"/>
|
||||
</div>
|
||||
<div class="resizer bg-grey-lighten-2" :data-direction="horizontal?'horizontal':'vertical'" ref="resizerElement"></div>
|
||||
<div class="resizer" :data-direction="horizontal?'horizontal':'vertical'" ref="resizerElement"></div>
|
||||
<div class="scrollpane">
|
||||
<slot name="bottom"/>
|
||||
</div>
|
||||
@@ -119,14 +119,16 @@ body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// todo light/dark mode
|
||||
.resizer[data-direction='horizontal'] {
|
||||
background-color: #cbd5e0;
|
||||
background-color: #444;
|
||||
cursor: ew-resize;
|
||||
height: 100%;
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
.resizer[data-direction='vertical'] {
|
||||
background-color: #444;
|
||||
cursor: ns-resize;
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
@@ -142,5 +144,7 @@ body {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 5em;
|
||||
max-height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-data-table :headers="datatableHeaders" :items="orders" item-value="id"
|
||||
:show-select="true" :show-expand="true" v-model="selected" select-strategy="single">
|
||||
:show-select="false" :show-expand="true" v-model="selected" select-strategy="single"
|
||||
:row-props="rowPropsHandler" :hover="true"
|
||||
>
|
||||
<template v-slot:item.startTime="{ value }">{{ timestampString(value) }}</template>
|
||||
<template v-slot:item.input="{ item }">
|
||||
<span v-if="item.order.amountIsInput">
|
||||
@@ -71,10 +73,6 @@
|
||||
</suspense>
|
||||
</template>
|
||||
<template v-slot:item.state="{ item }">
|
||||
<v-chip v-if="item.state===PendingOrderState.Submitted || item.state===PendingOrderState.Signing"
|
||||
prepend-icon="mdi-signature">Wallet Signing</v-chip>
|
||||
<v-chip v-if="item.state===PendingOrderState.Rejected" prepend-icon="mdi-cancel">Rejected</v-chip>
|
||||
<v-chip v-if="item.state===PendingOrderState.Sent" prepend-icon="mdi-send">Sent</v-chip>
|
||||
<v-chip v-if="item.state===OrderState.Open" class="d-none d-lg-inline-flex" prepend-icon="mdi-dots-horizontal"
|
||||
color="green">Open
|
||||
</v-chip>
|
||||
@@ -88,28 +86,18 @@
|
||||
</v-chip>
|
||||
<v-chip v-if="item.state===OrderState.Expired" prepend-icon="mdi-progress-check" color="grey-darken-1">Partial
|
||||
</v-chip>
|
||||
<v-chip v-if="item.state===OrderState.Underfunded" prepend-icon="mdi-alert" color="warning">Underfunded</v-chip>
|
||||
<v-chip v-if="item.state===OrderState.Underfunded" prepend-icon="mdi-alert" color="warning">Underfunded
|
||||
<v-tooltip :text="`This order is underfunded. Add more ${item.inSymbol} to your vault.`" location="top" activator="parent"></v-tooltip>
|
||||
</v-chip>
|
||||
<v-chip v-if="item.state===OrderState.Error" prepend-icon="mdi-alert" color="error">Error</v-chip>
|
||||
</template>
|
||||
<!-- <template v-slot:item.action="{item}">-->
|
||||
<!-- <btn v-if="item.state===OrderState.Open" icon="mdi-cancel" color="red"-->
|
||||
<!-- @click="cancelOrder(vaultAddr,item.index)">Cancel-->
|
||||
<!-- </btn>-->
|
||||
<!-- </template>-->
|
||||
<template v-slot:expanded-row="{item}">
|
||||
<template v-for="(t, i) in item.order.tranches">
|
||||
<tr>
|
||||
<td class="text-right" colspan="2">
|
||||
<div class="text-right">
|
||||
<div v-if="s.clock < item.trancheStatus[i].startTime">
|
||||
Activates {{ timestampString(item.trancheStatus[i].startTime) }}
|
||||
</div>
|
||||
<div v-if="item.trancheStatus[i].endTime<DISTANT_FUTURE">
|
||||
Expires {{ timestampString(item.trancheStatus[i].endTime) }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mx-3" v-if="t.marketOrder">market order</div>
|
||||
<div class="mx-3" v-if="t.marketOrder && t.rateLimitPeriod === 0">market order</div>
|
||||
<div class="mx-3" v-if="t.marketOrder && t.rateLimitPeriod !== 0">DCA {{Math.round(MAX_FRACTION/t.rateLimitFraction)}} parts</div>
|
||||
<line-price class="mx-3" v-if="!t.marketOrder"
|
||||
:base="item.base" :quote="item.quote"
|
||||
:b="t.minLine.intercept" :m="t.minLine.slope" :is-breakout="!item.minIsLimit"
|
||||
@@ -119,7 +107,10 @@
|
||||
:b="t.maxLine.intercept" :m="t.maxLine.slope" :is-breakout="item.minIsLimit"
|
||||
:show-btn="true"/>
|
||||
</div>
|
||||
|
||||
<div class="text-right" v-if="item.state===OrderState.Open">
|
||||
<div>{{ describeTrancheTime(item, i, true) }}</div>
|
||||
<div>{{ describeTrancheTime(item, i, false) }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<suspense>
|
||||
@@ -174,17 +165,18 @@
|
||||
<script setup>
|
||||
import LinePrice from "@/components/LinePrice.vue";
|
||||
import {useStore} from "@/store/store";
|
||||
import {computed, defineAsyncComponent, onUnmounted, ref, watch} from "vue";
|
||||
import {computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch} from "vue";
|
||||
import Btn from "@/components/Btn.vue"
|
||||
import {cancelOrder, PendingOrderState, useWalletStore} from "@/blockchain/wallet.js";
|
||||
import {timestampString} from "@/misc.js";
|
||||
import {DISTANT_FUTURE, isOpen, OrderState} from "@/blockchain/orderlib.js";
|
||||
import {cancelOrder, useWalletStore} from "@/blockchain/wallet.js";
|
||||
import {DISTANT_FUTURE, isOpen, MAX_FRACTION, OrderState} from "@/blockchain/orderlib.js";
|
||||
import Pulse from "@/components/Pulse.vue";
|
||||
import {OrderShapes} from "@/charts/ordershapes.js";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {lookupSymbol, tickerForOrder} from "@/charts/datafeed.js";
|
||||
import {setSymbol} from "@/charts/chart.js";
|
||||
import {addSymbolChangedCallback, removeSymbolChangedCallback, setSymbol} from "@/charts/chart.js";
|
||||
import {uniswapV3AveragePrice} from "@/blockchain/uniswap.js";
|
||||
import {timestampString} from "@/misc.js";
|
||||
import {metadataMap} from "@/version.js";
|
||||
|
||||
const PairPrice = defineAsyncComponent(()=>import("@/components/PairPrice.vue"))
|
||||
const TokenAmount = defineAsyncComponent(()=>import('./TokenAmount.vue'))
|
||||
@@ -214,9 +206,7 @@ watch(selected, async ()=>{
|
||||
// switch symbol
|
||||
deleteOrderShapes()
|
||||
await setSymbol(symbol)
|
||||
console.log('change symbol completed')
|
||||
}
|
||||
console.log('creating order shape', id)
|
||||
orderShapes[id] = new OrderShapes(symbol, status)
|
||||
}
|
||||
}
|
||||
@@ -228,14 +218,12 @@ function deleteOrderShapes(keepSet={}) {
|
||||
const keys = [...Object.keys(orderShapes)]
|
||||
for (const id of keys) {
|
||||
if (!(id in keepSet)) {
|
||||
console.log('deleting order shape', id)
|
||||
orderShapes[id].delete()
|
||||
delete orderShapes[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const orderShapes = {}
|
||||
|
||||
onUnmounted(()=>{
|
||||
@@ -244,7 +232,7 @@ onUnmounted(()=>{
|
||||
})
|
||||
|
||||
const datatableHeaders = [
|
||||
{title: 'Date', align: 'start', key: 'start'},
|
||||
{title: 'Date', align: 'start', key: 'startTime'},
|
||||
{title: 'Input', align: 'end', key: 'input'},
|
||||
{title: 'Output', align: 'end', key: 'output'},
|
||||
{title: 'Avg Price', align: 'end', key: 'avg'},
|
||||
@@ -252,6 +240,16 @@ const datatableHeaders = [
|
||||
// {title: '', align: 'end', key: 'action'},
|
||||
];
|
||||
|
||||
const rowPropsHandler = ({ item }) => ({
|
||||
class: selected.value.indexOf(item.id) !== -1 ? 'selected-row' : '',
|
||||
onClick: () => {
|
||||
if (selected.value.indexOf(item.id) === -1)
|
||||
selected.value = [item.id]
|
||||
else
|
||||
selected.value = []
|
||||
},
|
||||
})
|
||||
|
||||
const orders = computed(()=>{
|
||||
// example twap
|
||||
// status = [
|
||||
@@ -319,6 +317,7 @@ const orders = computed(()=>{
|
||||
result.push(st)
|
||||
st.id = `${vault}|${index}`
|
||||
st.index = parseInt(index)
|
||||
// st.startTime = timestampString(st.startTime)
|
||||
/*
|
||||
o.tranches = o.tranches.map((tranche)=>{
|
||||
const t = {...tranche}
|
||||
@@ -356,19 +355,21 @@ const orders = computed(()=>{
|
||||
for (const st of result) {
|
||||
let low, high;
|
||||
// console.log('elab', st.order)
|
||||
const buy = st.order.tokenIn > st.order.tokenOut;
|
||||
const o = st.order;
|
||||
const buy = o.tokenIn > o.tokenOut;
|
||||
if (buy) {
|
||||
low = st.order.tokenOut
|
||||
high = st.order.tokenIn
|
||||
low = o.tokenOut
|
||||
high = o.tokenIn
|
||||
}
|
||||
else {
|
||||
low = st.order.tokenIn
|
||||
high = st.order.tokenOut
|
||||
low = o.tokenIn
|
||||
high = o.tokenOut
|
||||
}
|
||||
st.base = st.order.inverted ? high : low;
|
||||
st.quote = st.order.inverted ? low : high;
|
||||
st.minIsLimit = buy === st.order.inverted // whether limit/breakout is flipped
|
||||
// console.log('buy/inverted/minIsLimit', buy, st.order.inverted, st.minIsLimit)
|
||||
st.base = o.inverted ? high : low;
|
||||
st.quote = o.inverted ? low : high;
|
||||
st.minIsLimit = buy === o.inverted // whether limit/breakout is flipped
|
||||
const found = metadataMap[st.chainId][o.tokenIn]
|
||||
st.inSymbol = found ? found.s : o.tokenIn
|
||||
// console.log('elaborated', st)
|
||||
}
|
||||
result.sort((a,b)=>b.startTime-a.startTime)
|
||||
@@ -383,7 +384,6 @@ const orders = computed(()=>{
|
||||
function describeTrancheTime(st, trancheIndex, isStart) {
|
||||
const t = st.order.tranches[trancheIndex]
|
||||
const ts = st.trancheStatus[trancheIndex]
|
||||
console.log('describeTT', t, ts)
|
||||
let result = ''
|
||||
if( t.minIsBarrier || t.maxIsBarrier )
|
||||
return 'barrier'
|
||||
@@ -401,12 +401,16 @@ function describeTrancheTime(st, trancheIndex, isStart) {
|
||||
return result
|
||||
}
|
||||
|
||||
function symbolChanged() {
|
||||
selected.value = []
|
||||
}
|
||||
|
||||
onMounted(()=>addSymbolChangedCallback(symbolChanged))
|
||||
onUnmounted(()=>removeSymbolChangedCallback(symbolChanged))
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "src/styles/vars" as *;
|
||||
|
||||
// columns
|
||||
.num {
|
||||
min-width: 1em;
|
||||
@@ -429,5 +433,12 @@ function describeTrancheTime(st, trancheIndex, isStart) {
|
||||
}
|
||||
.cancel {
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@use "src/styles/settings" as *;
|
||||
|
||||
tr.selected-row {
|
||||
background-color: #616161;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<!-- Crisp -->
|
||||
<script type="text/javascript">window.$crisp=[];window.CRISP_WEBSITE_ID="b153f30a-4b0b-49cc-8a38-989409a73acb";(function(){const d=document;const s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();</script>
|
||||
<script setup>
|
||||
if(window.location.hostname !== 'localhost') {
|
||||
window.$crisp=[];window.CRISP_WEBSITE_ID="a88f707f-adee-4960-b6de-c328c9d86945";(function(){const d=document;const s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();
|
||||
}
|
||||
</script>
|
||||
|
||||
58
src/components/TermsOfService.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<v-dialog v-model="show" max-width="500px" persistent>
|
||||
<tos-card>
|
||||
<v-card-actions class="justify-space-around mb-8">
|
||||
<v-btn variant="elevated" @click="decline" :disabled="!acceptable" prepend-icon="mdi-cancel">Decline</v-btn>
|
||||
<v-btn variant="elevated" color="primary" @click="accept" :disabled="!acceptable" prepend-icon="mdi-check">
|
||||
Accept
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</tos-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// tos = {
|
||||
// version: 1, // Number
|
||||
// date: 12345678, // Unix timestamp
|
||||
// ip: '123.231.132.213', // IP when client accepted
|
||||
// country: 'AD', // https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 Country where the IP is located
|
||||
// }
|
||||
|
||||
import {usePrefStore} from "@/store/store.js";
|
||||
import {computed, ref, watch} from "vue";
|
||||
|
||||
import TosCard from "@/components/TosCard.vue";
|
||||
import {socket} from "@/socket.js";
|
||||
|
||||
// UPDATE THIS WHEN CHANGING THE TOS
|
||||
const CURRENT_VERSION='2024-11-18' // this must be a parseable date
|
||||
|
||||
const prefs = usePrefStore()
|
||||
const ok = computed(()=>{
|
||||
console.log('check ToS', prefs.acceptedTos)
|
||||
return prefs.acceptedTos===CURRENT_VERSION
|
||||
})
|
||||
const show = computed(()=>!ok.value)
|
||||
const acceptable = computed(()=>!inFlight.value)
|
||||
const inFlight = ref(false)
|
||||
|
||||
watch(ok, (newVal, oldVal)=> {console.log('check ToS');if(newVal!==oldVal) console.log('ToS accepted', prefs.acceptedTos, prefs.acceptedTos) })
|
||||
|
||||
function accept() {
|
||||
inFlight.value = true
|
||||
socket.emit('approveTOS', new Date().toISOString(), CURRENT_VERSION, (ok)=>{
|
||||
console.log('approve TOS', ok)
|
||||
if (ok) prefs.acceptedTos = CURRENT_VERSION
|
||||
else console.warn('TOS was not approved')
|
||||
inFlight.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function decline() {
|
||||
// bounce to top-level non-app domain
|
||||
window.location.href = 'https://dexorder.trade'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
<script setup>
|
||||
// noinspection ES6UnusedImports
|
||||
import {routeInverted, SingletonCoroutine, vAutoSelect} from "@/misc.js";
|
||||
import {SingletonCoroutine, vAutoSelect} from "@/misc.js";
|
||||
import Order from "@/components/Order.vue";
|
||||
import LimitPrice from "@/components/LimitPrice.vue";
|
||||
import {timesliceTranches, applyLimitOld} from "@/orderbuild.js";
|
||||
import {applyLimitOld, timesliceTranches} from "@/orderbuild.js";
|
||||
import Tranches from "@/components/Tranches.vue";
|
||||
import {useOrderStore} from "@/store/store.js";
|
||||
|
||||
|
||||
@@ -81,13 +81,6 @@ function updateValue(v) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import {ethers} from "ethers";
|
||||
import {useStore} from "@/store/store.js";
|
||||
const s = useStore()
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "src/styles/vars" as *;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<v-list v-if="token">
|
||||
<v-list-subheader :title="token.s"/>
|
||||
<v-list-item title="Withdraw" key="withdraw" value="withdraw" prepend-icon="mdi-arrow-down-bold"
|
||||
@click="()=>onWithdraw(token.a)"/>
|
||||
@click="()=>onWithdraw(token)"/>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</td>
|
||||
|
||||
710
src/components/TosCard.vue
Normal file
@@ -0,0 +1,710 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title class="text-center">Dexorder Terms of Service</v-card-title>
|
||||
<!-- MAKE SURE TO UPDATE THE VERSION VARIABLE AS WELL -->
|
||||
<v-card-text class="text-center">Last Updated November 18, 2024</v-card-text>
|
||||
|
||||
<v-card-text>
|
||||
Please read these Terms of Service (the “<b>Terms</b>”) and our <a href="https://dexorder.com/privacy-policy" target="dexorder">Privacy Policy</a> carefully because they govern your
|
||||
use of the
|
||||
website (and all subdomains and subpages thereon) located at dexorder.com, including without limitation the
|
||||
subdomains app.dexorder.com and www.dexorder.com (collectively, the “<b>Site</b>”), and the Dexorder web
|
||||
application graphical user interface and any other services accessible via the Site (together with the Site, web
|
||||
application, and other services, collectively, the “<b>Dexorder Service</b>”) offered by Dexorder Trading Services
|
||||
Ltd.
|
||||
(“<b>Dexorder</b>,” “<b>we</b>,” “<b>our</b>,” or “<b>us</b>”).
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text>
|
||||
<b>
|
||||
BY USING THE DEXORDER SERVICE, YOU REPRESENT THAT (I) YOU ARE NOT LOCATED WITHIN THE UNITED
|
||||
STATES; AND (II) YOU ARE NOT A PERSON OR ENTITY WHO IS RESIDENT IN, A CITIZEN OF, IS LOCATED IN, IS
|
||||
INCORPORATED IN, OR HAS A REGISTERED OFFICE IN ANY RESTRICTED TERRITORY, AS DEFINED BELOW (ANY
|
||||
SUCH PERSON OR ENTITY FROM WITHIN THE UNITED STATES OR A RESTRICTED TERRITORY, IS REFERRED TO
|
||||
HEREIN AS A “RESTRICTED PERSON”).
|
||||
</b>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text>
|
||||
WHEN YOU AGREE TO THESE TERMS, YOU ARE AGREEING (WITH LIMITED EXCEPTION) TO RESOLVE ANY DISPUTE
|
||||
BETWEEN YOU AND DEXORDER THROUGH BINDING, INDIVIDUAL ARBITRATION RATHER THAN IN COURT. PLEASE
|
||||
REVIEW CAREFULLY SECTION 16 (DISPUTE RESOLUTION) BELOW FOR DETAILS REGARDING ARBITRATION. HOWEVER, IF
|
||||
YOU ARE A RESIDENT OF A JURISDICTION WHERE APPLICABLE LAW PROHIBITS ARBITRATION OF DISPUTES, THE
|
||||
AGREEMENT TO ARBITRATE IN SECTION 16 WILL NOT APPLY TO YOU, BUT THE PROVISIONS OF SECTION 15
|
||||
(GOVERNING LAW) WILL APPLY INSTEAD.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>1. Description of Dexorder Service</v-card-title>
|
||||
<v-card-text>
|
||||
(a) The Dexorder Service allows you to access an online web application graphical user
|
||||
interface (the “<b>App</b>”) which enables you to interact with a protocol consisting of a set of smart contracts
|
||||
(the
|
||||
“<b>Smart Contracts</b>”) and to create and interact with a user controlled smart contract involving digital
|
||||
assets over
|
||||
which only you have upgrade authority (a “<b>Vault</b>” and together with the Smart Contracts, the
|
||||
“<b>Protocol</b>”). You
|
||||
may use your Vault to send signals to, interact with, and initiate actions on third party smart contract
|
||||
blockchain
|
||||
protocols (“<b>Interactions</b>”) operating on decentralized exchanges (e.g., Uniswap) (“<b>DEXs</b>”). Certain
|
||||
Interactions
|
||||
may require threshold parameters to be met and that a third party transmit an oracle related activation signal
|
||||
(the “<b>Activation Signal</b>”) to the Vault in order to effectuate your commands (such party being an
|
||||
“<b>Oracle</b>”).
|
||||
Dexorder may in the ordinary course of events be an Oracle (“<b>Dexorder Oracle</b>”), but bears no obligation nor
|
||||
promise to do so on an ongoing basis, and you may send such Activation Signal yourself or utilize a third-party
|
||||
Oracle to do so. Further, Dexorder is not and does not offer a digital wallet, and has no custody or control over
|
||||
your digital wallet, which is never accessible by Dexorder, and only users, and not Dexorder, may provide or
|
||||
withdraw tokens to be held by Vault. From time to time, the Services may include making recommendations
|
||||
with respect to technical changes that only you may accept and implement.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text>
|
||||
(b) <b>Interface</b>. The Dexorder Service provides you with access to the Protocol, which is a user
|
||||
controlled, non-custodial protocol, upgradeable only by your actions and consent, deployed on the blockchains
|
||||
indicated on our Site, and provides information and interaction capabilities with other blockchain related service
|
||||
providers. All information provided in connection with your access and use of the Dexorder Service is for
|
||||
informational purposes only. You should not take, or refrain from taking, any action based on any information
|
||||
contained on the Dexorder Service or any other information that we make available at any time, including blog
|
||||
posts, data, articles, links to third-party content, Discord content, news feeds, tutorials, tweets, and videos.
|
||||
Before you make any financial, legal, technical, or other decisions involving the Dexorder Service, you should
|
||||
seek independent professional advice from a licensed and qualified individual in the area for which such advice
|
||||
would be appropriate. Because the Dexorder Service provides information about the Protocol, these Terms also
|
||||
provide some information about the use of the Protocol. This information is not intended to be comprehensive
|
||||
or address all aspects of the Protocol.
|
||||
</v-card-text>
|
||||
|
||||
|
||||
<v-card-text>
|
||||
(c) <b>Our Relationship</b>. You acknowledge and agree that Dexorder is an online platform
|
||||
provider and not a financial institution, broker dealer, exchange or money services business. Dexorder does not
|
||||
direct or control the day-to-day activities of users accessing the Dexorder Service. Neither we nor any affiliated
|
||||
entity is a party to any transaction on the blockchain network underlying the Protocol; we do not have
|
||||
possession, custody or control over any cryptoassets appearing on the Dexorder Service or on the Protocol; and
|
||||
we do not have possession, custody, or control over any user’s funds or cryptoassets. Further, we do not store,
|
||||
send, or receive any funds or cryptoassets on your behalf. You understand that when you interact with any
|
||||
Protocol smart contracts, you retain control over your cryptoassets at all times. You are solely responsible for
|
||||
evaluating any proposed technical changes and how such changes may alter current or future Interactions.
|
||||
Furthermore, you understand and acknowledge that only you have absolute and ultimate authority over the
|
||||
implementation of any such changes and the responsibility therefor. The private key associated with your Vault
|
||||
is the only private key that can control the cryptoassets in the Vault. You alone are responsible for securing
|
||||
your
|
||||
private keys. We do not have access to your private keys. Because the Protocol is non-custodial, we are not
|
||||
intermediaries, agents, advisors, or custodians, and we do not have a fiduciary relationship or obligation to you
|
||||
regarding any other decisions or activities that you affect when using the Dexorder Service or interacting with
|
||||
the Protocol. You acknowledge that we, for the avoidance of doubt, do not have any information regarding any
|
||||
users, users’ identities, or services beyond what is available, obtainable publicly via the blockchain, or shared
|
||||
by
|
||||
you when you access the Dexorder Service. We are not responsible for any activities you engage in when using
|
||||
the Dexorder Service, and you should understand the risks associated with cryptoassets, blockchain technology
|
||||
generally, and the Interface.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>
|
||||
2. Agreement to Terms
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
By using our Dexorder Service, you agree to be bound by these Terms. If you don’t agree to be bound by these
|
||||
Terms,
|
||||
do not use the Dexorder Service.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>3. Changes to these Terms or the Dexorder Service</v-card-title>
|
||||
<v-card-text>
|
||||
We may update the Terms, including any addendum terms, from time to time in our sole discretion. If we do, we’ll
|
||||
let
|
||||
you know by posting the updated Terms on the Site and/or may also send other communications. It’s important that
|
||||
you review the Terms whenever we update them or you use the Dexorder Service. If you continue to use the Dexorder
|
||||
Service after we have posted updated Terms it means that you accept and agree to the changes. If you don’t agree
|
||||
to
|
||||
be bound by the changes, you may not use the Dexorder Service anymore. Because our Dexorder Service is evolving
|
||||
over time we may change or discontinue all or any part of the Dexorder Service, at any time and without notice, at
|
||||
our
|
||||
sole discretion.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>4. Who May Use the Dexorder Service?</v-card-title>
|
||||
<v-card-text>
|
||||
(a) <u>Eligibility</u>. The Dexorder Service is only available to users in certain jurisdictions outside
|
||||
of the United States and that are at least 18 years old, capable of forming a binding contract with the Dexorder
|
||||
and not otherwise barred from using the Dexorder Service under Applicable Law. You may not attempt to access
|
||||
or use the Dexorder Service if you are not permitted to do so (including without limitation if you are a
|
||||
Restricted
|
||||
Person).
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(b) <u>Compliance</u>. You certify that you will comply with all Applicable Law when using the
|
||||
Dexorder Service. You are solely responsible for ensuring that your access and use of the Dexorder Service in
|
||||
such country, territory, or jurisdiction does not violate any Applicable Laws. You must not use any software or
|
||||
networking techniques, including use of a virtual private network (“<b>VPN</b>”) to circumvent or attempt to
|
||||
circumvent this prohibition. We reserve the right to monitor the locations from which our Dexorder Service is
|
||||
accessed. Furthermore, we reserve the right, at any time, in our sole discretion, to block access to the Dexorder
|
||||
Service, in whole or in part, from any geographic location, IP addresses, and unique device identifiers
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>5. Use of the Dexorder Service</v-card-title>
|
||||
<v-card-text>
|
||||
(a) <u>User Representations and Warranties</u>. As a condition to accessing or using the Dexorder
|
||||
Service, you represent and warrant to Dexorder that:
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(i) if you are entering into these Terms as an individual, then you are of legal age in
|
||||
the jurisdiction in which you reside and you have the legal capacity to enter into these Terms and be bound by
|
||||
them and if you are entering into these Terms as an entity, then you must have the legal authority to accept
|
||||
these Terms on that entity’s behalf, in which case “you” (except as used in this paragraph) will mean that entity;
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(ii) you are not in or residing in the United States;
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(iii) you are not in or residing in Cuba, Iran, North Korea, Syria, Belarus, Russia, and
|
||||
the Crimea, Luhansk, Donetsk, Zaporizhzhia, and Kherson regions of Ukraine, or any other country or jurisdiction
|
||||
to which the Cayman Islands, the United Kingdom, United States, the United Nations Security Council, or the
|
||||
European Union embargoes goods or imposes similar sanctions (collectively, “<b>Restricted Territories</b>”);
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(iv) you are not on any sanctions list or equivalent maintained by the Cayman
|
||||
Islands, the United Kingdom, United States, the United Nations Security Council, or the European Union
|
||||
(collectively, “<b>Sanctions Lists Persons</b>”) and you do not intend to transact with any Restricted Person or
|
||||
Sanctions List Person;
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(v) you do not, and will not, use VPN software or any other privacy or
|
||||
anonymization tools or techniques to circumvent, or attempt to circumvent, any restrictions that apply to the
|
||||
Dexorder Service;
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(vi) you have obtained all required consents from any individual whose personal
|
||||
information you transfer to us in connection with your use of the Dexorder Service; and
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(vii) all information that you provide through the Dexorder Service is current,
|
||||
complete, true, and accurate and you will maintain the security and confidentiality of your private keys
|
||||
associated with your public wallet address, passwords, API keys, passwords or other information associated with
|
||||
your Vault or otherwise, as applicable.
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(viii) your access to the Dexorder Service is not: (a) prohibited by and does not
|
||||
otherwise violate or assist you to violate any domestic or foreign law, rule, statute, regulation, by-law, order,
|
||||
protocol, code, decree, or another directive, requirement, or guideline, published or in force that applies to or
|
||||
is
|
||||
otherwise intended to govern or regulate any person, property, transaction, activity, event or other matter,
|
||||
including any rule, order, judgment, directive or other requirement or guideline issued by any domestic or
|
||||
foreign federal, provincial or state, municipal, local or other governmental, regulatory, judicial or
|
||||
administrative
|
||||
authority having jurisdiction over Dexorder, you, the Site or the Dexorder Service, or as otherwise duly enacted,
|
||||
enforceable by law, the common law or equity (collectively, “<b>Applicable Laws</b>”); or (b) contribute to or
|
||||
facilitate
|
||||
any illegal activity.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text>
|
||||
(b) <u>Limitations</u>. As a condition to accessing or using the Dexorder Service or the Site, you
|
||||
acknowledge, understand, and agree to the following:
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(i) Subject to your compliance with these Terms, Dexorder will use its commercially
|
||||
reasonable efforts to provide you with access to the Dexorder Service and to cause your Interactions to be
|
||||
executed on the applicable DEX in accordance with Dexorder’s Execution Policy located at
|
||||
<a href="https://dexorder.com/execution-policy">https://dexorder.com/execution-policy</a>
|
||||
(“<b>Execution Policy</b>”), however from time to time the Site and the Dexorder Service may be inaccessible or
|
||||
inoperable for any
|
||||
reason, including, without limitation: (a) if an Interaction repeatedly fails to be executed (such as due to an
|
||||
error
|
||||
in Interaction execution or a malfunction in the Dexorder Service); (b) equipment malfunctions; (c) periodic
|
||||
maintenance procedures or repairs that Dexorder or any of its suppliers or contractors may undertake from time
|
||||
to time; (d) causes beyond Dexorder’s control or that Dexorder could not reasonably foresee; (e) disruptions and
|
||||
temporary or permanent unavailability of underlying blockchain infrastructure; (f) unavailability of third-party
|
||||
service providers or external partners for any reason; or (g) an Activation Signal not being sent.
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(ii) the Site and the Dexorder Service may evolve, which means Dexorder may apply
|
||||
changes, replace, or discontinue (temporarily or permanently) the Dexorder Service at any time in its sole
|
||||
discretion;
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(iii) Dexorder does not act as an agent for you or any other user of the Site or the Dexorder Service;
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(iv) you are solely responsible for your use of the Dexorder Service; and
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(v) we owe no fiduciary duties or liabilities to you or any other party, and that to
|
||||
the extent any such duties or liabilities may exist at law or in equity, you hereby irrevocably disclaim, waive,
|
||||
and
|
||||
eliminate those duties and liabilities.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>6. Interactions; Fees</v-card-title>
|
||||
<v-card-text>
|
||||
(a) <u>Interactions</u>. In order to effectuate Interactions, you may need to transfer digital assets
|
||||
(e.g., tokens) to the Vault. You acknowledge that you may use the Dexorder Services to process and cause
|
||||
Interactions to be operate with an applicable DEX, including without limitation the transfer of digital assets via
|
||||
the DEX in accordance with the Interaction. For clarity, the Vault is a smart contract automatically controlled by
|
||||
the blockchain. Dexorder is an interface to that smart contract, and does not offer a digital wallet and has no
|
||||
custody or control over your digital wallet or any digital assets or cryptocurrency, which is never accessible by
|
||||
Dexorder.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(b) <u>Fees</u>.
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(i) Dexorder charges fees upfront for usage of the Dexorder Services at the time of
|
||||
user Interactions (“<b>Fees</b>”). You agree to pay all applicable Fees upfront to Dexorder, in the amounts
|
||||
communicated or presented to you via the Dexorder Service in connection with usage of the Dexorder Service.
|
||||
Each party shall be responsible for all Taxes imposed on its income or property.
|
||||
</v-card-text>
|
||||
<v-card-text class="ii">
|
||||
(ii) There may be associated fees in connection with transactions enacted on a
|
||||
blockchain. All transactions using blockchains require the payment of gas fees, which are essentially transaction
|
||||
fees paid on every transaction that occurs on the selected blockchain network. We do not collect any such fees.
|
||||
Please note that accessing the Protocol may result in you incurring gas fees, which are non-refundable, and are
|
||||
paid by you in all circumstances. You pay all gas fees incurred by you as relating to interacting with the
|
||||
Protocol.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text>
|
||||
(c) <u>Tax Records and Reporting</u>. You are solely responsible for all costs incurred by you in
|
||||
using the Dexorder Service, and for determining, collecting, reporting, and paying all applicable Taxes that you
|
||||
may be required by law to collect and remit to any governmental or regulatory agencies. As used herein,
|
||||
“<b>Taxes</b>” means the taxes, duties, levies, tariffs, and other charges imposed by any federal, state,
|
||||
multinational or
|
||||
local governmental or regulatory authority. We reserve the right to report any activity occurring using the
|
||||
Dexorder Service to relevant tax authorities as required under Applicable Law. You are solely responsible for
|
||||
maintaining all relevant Tax records and complying with any reporting requirements you may have as related to
|
||||
our Dexorder Service. You are further solely responsible for independently maintaining the accuracy of any
|
||||
record submitted to any tax authority including any information derived from the Dexorder Service.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(d) Suspensions or Terminations. In addition to the other suspension and termination rights
|
||||
in these Terms, we may suspend or terminate your access to the Dexorder Service, or any and all Interactions, at
|
||||
any time in connection with any Interaction or transaction (i) as required by Applicable Law or any governmental
|
||||
authority, (ii) if we are unable to process or execute an Interaction or transaction after several attempts (as
|
||||
described in the Execution Policy or otherwise in Dexorder’s reasonable discretion), or (iii) if we in our sole
|
||||
and
|
||||
reasonable discretion determine you are violating the terms of any third-party service provider or these Terms,
|
||||
including, without limitation, if we reasonably believe any of your representations and warranties may be untrue
|
||||
or inaccurate or you are violating or have violated any of the geographical restrictions that apply to the
|
||||
Dexorder Service, and in any case we will not be liable to you for any losses or damages you may suffer as a
|
||||
result of or in connection with the Dexorder Service being inaccessible to you at any time or for any reason. Such
|
||||
suspension or termination shall not constitute a breach of these Terms by Dexorder. In accordance with its anti-
|
||||
money laundering, anti-terrorism, anti-fraud, and other compliance policies and practices, we may impose
|
||||
limitations and controls on the ability of you or any beneficiary to utilize the Dexorder Service. Such
|
||||
limitations
|
||||
may include rejecting transaction requests, freezing funds in any case where Dexorder has such ability, or
|
||||
otherwise restricting you from using the Dexorder Service, all to the extent of our ability to do so.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>7. General Prohibitions and Dexorder’s Enforcement Rights.</v-card-title>
|
||||
<v-card-text>
|
||||
You agree not to do any of the
|
||||
following:
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(a) Engage in or induce others to engage in any form of unauthorized access, hacking, or
|
||||
social engineering, including without limitation any distributed denial or service or DDoS attack, of Dexorder,
|
||||
the
|
||||
Dexorder Service, or any users of the foregoing;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(b) Use, display, mirror or frame the Dexorder Service or any individual element within the
|
||||
Dexorder Service, Dexorder’s name, any Dexorder trademark, logo or other proprietary information, or the
|
||||
layout and design of any page or form contained on a page, without Dexorder’s express written consent;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(c) Access, tamper with, or use non-public areas of the Dexorder Service, Dexorder’s
|
||||
computer systems, or the technical delivery systems of Dexorder’s providers;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(d) Attempt to probe, scan or test the vulnerability of any Dexorder system or network or
|
||||
breach any security or authentication measures;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(e) Avoid, bypass, remove, deactivate, impair, descramble or otherwise circumvent any
|
||||
technological measure implemented by Dexorder or any of Dexorder’s providers or any other third party
|
||||
(including another user) to protect the Dexorder Service;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(f) Attempt to access or search the Dexorder Service or download content from the
|
||||
Dexorder Service using any engine, software, tool, agent, device or mechanism (including spiders, robots,
|
||||
crawlers, data mining tools or the like) other than the software and/or search agents provided by Dexorder or
|
||||
other generally available third-party web browsers;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(g) Use any meta tags or other hidden text or metadata utilizing a Dexorder trademark,
|
||||
logo, URL or product name without Dexorder’s express written consent;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(h) Forge any TCP/IP packet header or any part of the header information in any email or
|
||||
newsgroup posting, or in any way use the Dexorder Service to send altered, deceptive or false source-identifying
|
||||
information;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(i) Attempt to decipher, decompile, disassemble or reverse engineer any of the software
|
||||
used to provide the Dexorder Service;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(j) Interfere with, or attempt to interfere with, the access of any user, host or network,
|
||||
including, without limitation, sending a virus, exploiting any bug, overloading, flooding, spamming, or mail-
|
||||
bombing the Dexorder Service;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(k) Use the Dexorder Service for benchmarking or analysis in a manner that could, directly
|
||||
or indirectly, interfere with, detract from, or otherwise harm the Dexorder Service or DEX;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(l) Collect or store any personally identifiable information from the Dexorder Service from
|
||||
other users of the Dexorder Service without their express permission;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(m) Impersonate or misrepresent your affiliation with any person or entity;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(n) Create or list any counterfeit items (including digital assets);
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(o) Fabricate in any way any transaction or process related thereto;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(p) Engage or assist in any activity that violates any law, statute, ordinance, regulation, or
|
||||
sanctions program, , or that involves proceeds of any unlawful activity (including but not limited to money
|
||||
laundering, terrorist financing or deliberately engaging in activities designed to adversely affect the
|
||||
performance
|
||||
of the Dexorder Service);
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(q) Engage in deceptive or manipulative trading activities;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(r) Disguise or interfere in any way with the IP address of the computer you are using to
|
||||
access or use the Dexorder Service or that otherwise prevents us from correctly identifying the IP address of the
|
||||
computer you are using to access the Dexorder Service;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(s) Transmit, exchange, or otherwise support the direct or indirect proceeds of criminal or
|
||||
fraudulent activity;
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(t) Violate any Applicable Law or regulation; or
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(u) Encourage or enable any other individual to do any of the foregoing.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
Dexorder is not obligated to monitor access to or use of the Dexorder Service or to review or edit any content.
|
||||
However, we have the right to do so for the purpose of operating the Dexorder Service, to ensure compliance with
|
||||
these Terms and to comply with Applicable Law or other legal requirements. We reserve the right, but are not
|
||||
obligated, to suspend or terminate access to the Dexorder Service at any time if we believe you are violating
|
||||
these
|
||||
Terms. We have the right to investigate violations of these Terms or conduct that affects the Dexorder Service. We
|
||||
may also consult and cooperate with law enforcement authorities to prosecute users who violate the law.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>8. Feedback</v-card-title>
|
||||
<v-card-text>
|
||||
We appreciate feedback, comments, ideas, proposals and suggestions for improvements to the Dexorder Service
|
||||
(“<b>Feedback</b>”). If you choose to submit Feedback, you agree that we are free to use it (and permit others to
|
||||
use it)
|
||||
without any restriction or compensation to you.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>9. Links to Third Party Websites or Resources</v-card-title>
|
||||
<v-card-text>
|
||||
The Dexorder Service may allow you to access third-party websites, integrations, or other resources, including the
|
||||
DEX,
|
||||
services providing Activation Signals, and any bridge between the DEX and any third party protocols (collectively,
|
||||
“<b>Third Party Resources</b>”). We provide access only as a convenience and are not responsible for the content,
|
||||
products
|
||||
or services on or available from those resources or links displayed on such websites. You acknowledge sole
|
||||
responsibility for and assume all risk arising from, your use of any third-party resources. Our provision of
|
||||
access to
|
||||
Third Party Resources does not constitute approval, endorsement, or control of such Third Party Resource.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>10. Termination</v-card-title>
|
||||
<v-card-text>
|
||||
We may suspend or terminate your access to and use of the Dexorder Service, at our sole discretion, at any time
|
||||
and
|
||||
without notice to you. You acknowledge and agree that we shall have no liability or obligation to you in such
|
||||
event and
|
||||
that you will not be entitled to a refund of any amounts that you have already paid to us or any third party, to
|
||||
the
|
||||
fullest extent permitted by Applicable Law. Upon any termination, discontinuation, or cancellation of the Dexorder
|
||||
Service or your account, the following Sections will survive: 6.(d) , 7 , 8 , 10 , 11 , 13 , 14 , 15 , 16 , and 17
|
||||
.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>11. Warranty Disclaimers</v-card-title>
|
||||
<v-card-text>
|
||||
THE DEXORDER SERVICE (INCLUDING WITHOUT LIMITATION THE VAULT) AND ANY CONTENT CONTAINED THEREIN, AS
|
||||
WELL AS THE PROTOCOL, THE DEXORDER ORACLE, AND ANY ASSOCIATED PROTOCOL OR BLOCKCHAIN MESSAGING
|
||||
FUNCTIONALITY SUCH AS ACTIVATION SIGNALS UNDERLYING THE DEXORDER SERVICE (TOGETHER, THE “<b>UTILITIES</b>”),
|
||||
ARE PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND. WITHOUT LIMITING THE FOREGOING, WE EXPLICITLY
|
||||
DISCLAIM ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
|
||||
ENJOYMENT AND NON-INFRINGEMENT, AND ANY WARRANTIES ARISING OUT OF COURSE OF DEALING OR USAGE OF
|
||||
TRADE. WE MAKE NO WARRANTY THAT THE UTILITIES WILL MEET YOUR REQUIREMENTS, BE AVAILABLE ON AN
|
||||
UNINTERRUPTED, SECURE, OR ERROR-FREE BASIS. WE MAKE NO WARRANTY REGARDING THE QUALITY, ACCURACY,
|
||||
TIMELINESS, TRUTHFULNESS, COMPLETENESS OR RELIABILITY OF ANY INFORMATION OR CONTENT ON THE UTILITIES.
|
||||
DEXORDER FURTHER EXPRESSLY DISCLAIMS ALL LIABILITY OR RESPONSIBILITY IN CONNECTION WITH THIRD PARTY
|
||||
SERVICES. NOTHING HEREIN NOR ANY USE OF THE UTILITIES IN CONNECTION WITH THIRD PARTY SERVICES
|
||||
CONSTITUTES OUR ENDORSEMENT, RECOMMENDATION OR ANY OTHER AFFILIATION OF OR WITH ANY THIRD PARTY
|
||||
SERVICES.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
DEXORDER DOES NOT REPRESENT OR WARRANT THAT ANY CONTENT ON THE UTILITIES IS ACCURATE, COMPLETE,
|
||||
RELIABLE, CURRENT OR ERROR-FREE. WE WILL NOT BE LIABLE FOR ANY LOSS OF ANY KIND FROM ANY ACTION TAKEN
|
||||
OR TAKEN IN RELIANCE ON MATERIAL OR INFORMATION CONTAINED ON THE UTILITIES. DEXORDER CANNOT AND
|
||||
DOES NOT REPRESENT OR WARRANT THAT THE UTILITIES, ANY CONTENT THEREIN, OR OUR SERVERS ARE FREE OF
|
||||
VIRUSES OR OTHER HARMFUL COMPONENTS.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
WE CANNOT GUARANTEE THE SECURITY OF ANY DATA THAT YOU DISCLOSE ONLINE. YOU ACCEPT THE INHERENT
|
||||
SECURITY RISKS OF PROVIDING INFORMATION AND DEALING ONLINE OVER THE INTERNET AND WILL NOT HOLD US
|
||||
RESPONSIBLE FOR ANY BREACH OF SECURITY.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
DEXORDER WILL NOT BE RESPONSIBLE OR LIABLE TO YOU FOR ANY LOSS AND TAKES NO RESPONSIBILITY FOR, AND
|
||||
WILL NOT BE LIABLE TO YOU FOR, ANY USE OF THE UTILITIES, INCLUDING BUT NOT LIMITED TO ANY LOSSES,
|
||||
DAMAGES OR CLAIMS ARISING FROM: (I) USER ERROR SUCH AS FORGOTTEN PASSWORDS, INCORRECTLY
|
||||
CONSTRUCTED TRANSACTIONS, EXCEEDING TRANSFER LIMITS OF THIRD PARTY RESOURCES OR THE DEX, OR
|
||||
MISTYPED WALLET ADDRESSES; (II) SERVER FAILURE OR DATA LOSS; (III) BLOCKCHAIN NETWORKS, CRYPTOCURRENCY
|
||||
WALLETS, CORRUPT FILES, SOFTWARE ERRORS, OR BUGS; (IV) UNAUTHORIZED ACCESS TO THE UTILITIES; OR (V) ANY
|
||||
THIRD PARTY UTILITIES, INCLUDING WITHOUT LIMITATION THE USE OF VIRUSES, PHISHING, BRUTEFORCING OR OTHER
|
||||
MEANS OF ATTACK AGAINST ANY BLOCKCHAIN NETWORK UNDERLYING THE UTILITIES.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
THE UTILITIES MAY INCLUDE THE PLACEMENT OR EXECUTION OF AN ACTIVATION SIGNAL TO A USER VAULT WITH
|
||||
RESPECT TO USER DEFINED INTERACTIONS (E.G., VIA TRANSMITTING AN ACTIVATION SIGNAL WHICH TRIGGERS
|
||||
INTERACTION EXECUTION), HOWEVER, ANYONE, INCLUDING YOU OR ANY THIRD PARTY, MAY CAUSE AN INTERACTION
|
||||
TO BE EXECUTED (SUCH AS BY TRANSMITTING THE APPLICABLE ACTIVATION SIGNAL), AND NOTWITHSTANDING
|
||||
ANYTHING TO THE CONTRARY IN THESE TERMS, DEXORDER DOES NOT GUARANTEE THE PLACEMENT OR EXECUTION
|
||||
OR ANY INTERACTION, INCLUDING WITHOUT LIMITATION THAT DEXORDER WILL TRANSMIT ANY PARTICULAR
|
||||
ACTIVATION SIGNAL TO TRIGGER EXECUTION OF ANY INTERACTION, OR THAT AN INTERACTION WILL OTHERWISE BE
|
||||
PROPERLY CARRIED OUT PURSUANT TO A VAULT. YOU ACKNOWLEDGE AND AGREE THAT YOU WILL NOT RELY ON THE
|
||||
UTILITIES TO CARRY OUT PLACEMENTS OR EXECUTIONS OF INTERACTIONS. IF ANY PARTICULAR INTERACTION THAT IS
|
||||
PLACED IS NOT EXECUTED BY DEXORDER IN A TIMELY MANNER, YOU MAY CAUSE SUCH INTERACTION TO BE
|
||||
EXECUTED YOURSELF OR BY ENGAGING A THIRD PARTY SERVICE PROVIDER TO DO SO (E.G., BY TRANSMITTING THE
|
||||
APPLICABLE ACTIVATION SIGNAL YOURSELF OR ENGAGING A THIRD PARTY TO DO SO). YOU ACCEPT THE INHERENT
|
||||
RISK THAT ANY PARTICULAR INTERACTION MAY NOT BE EXECUTED, INCLUDING WITHOUT LIMITATION DUE TO BAD
|
||||
ACTORS OR THE MALFUNCTION OF THE UTILITIES, AND DEXORDER HEREBY DISCLAIMS ANY AND ALL LIABILITY AND
|
||||
RESPONSIBILITY IN CONNECTION WITH THE PLACEMENT OR EXECUTION OF INTERACTIONS AND THE VAULT.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
THE UTILITIES MAY NOT BE AVAILABLE DUE TO ANY NUMBER OF FACTORS INCLUDING, BUT NOT LIMITED TO,
|
||||
PERIODIC SYSTEM MAINTENANCE, SCHEDULED OR UNSCHEDULED, ACTS OF GOD, UNAUTHORIZED ACCESS, VIRUSES,
|
||||
DENIAL OF SERVICE OR OTHER ATTACKS, TECHNICAL FAILURE OF THE UTILITIES AND/OR TELECOMMUNICATIONS
|
||||
INFRASTRUCTURE OR DISRUPTION, AND THEREFORE WE EXPRESSLY DISCLAIM ANY EXPRESS OR IMPLIED WARRANTY
|
||||
REGARDING THE USE AND/OR AVAILABILITY, ACCESSIBILITY, SECURITY OR PERFORMANCE OF THE UTILITIES CAUSED
|
||||
BY SUCH FACTORS. WE DO NOT MAKE ANY REPRESENTATIONS OR WARRANTIES AGAINST THE POSSIBILITY OF
|
||||
DELETION, MISDELIVERY OR FAILURE TO STORE COMMUNICATIONS, PERSONALIZED SETTINGS OR OTHER DATA.
|
||||
SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES. ACCORDINGLY, SOME OF THE
|
||||
ABOVE DISCLAIMERS OF WARRANTIES MAY NOT APPLY TO YOU BUT OTHERS REMAIN IN EFFECT.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
You understand that your use of the Utilities is entirely at your own risk. You assume all risks associated with
|
||||
using the Utilities, and digital assets and decentralized systems generally, including but not limited to, that
|
||||
digital assets are highly volatile; you may not have ready access to assets; and you may lose some or all of your
|
||||
tokens or other assets, including with respect to an Interaction or the Vault. You agree that you will have no
|
||||
recourse against Dexorder for any losses due to your use of the Utilities. For example, these losses may arise
|
||||
from or relate to: (i) lost funds; (ii) server failure or data loss; (iii) corrupted digital wallet files; (iv)
|
||||
unauthorized
|
||||
access; (v) errors, mistakes, or inaccuracies; or (vi) third-party activities.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>12. Assumption of Risk.</v-card-title>
|
||||
<v-card-text>You accept, acknowledge and assume the following risks:</v-card-text>
|
||||
<v-card-text>
|
||||
(a) You are solely responsible for determining what, if any, Taxes apply to your transactions
|
||||
through the Utilities. Neither Dexorder nor any Dexorder affiliates are responsible for determining the Taxes
|
||||
that apply to such transactions.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(b) A lack of use or public interest in the creation and development of distributed
|
||||
ecosystems could negatively impact the development of those ecosystems and related applications, and could
|
||||
therefore also negatively impact the potential utility or value of certain digital assets.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(c) By accessing and using the Utilities, you represent that you understand the inherent
|
||||
risks associated with using cryptographic and blockchain-based systems, and that you have a working knowledge
|
||||
of the usage and intricacies of tokens such as, bitcoin (BTC), ether (ETH), and other digital tokens such as those
|
||||
following the Ethereum Token Standard (ERC-20). You further understand that the markets for tokens can be
|
||||
highly volatile due to factors including (but not limited to) adoption, speculation, technology, security, and
|
||||
regulation. You acknowledge that the cost and speed of transacting with cryptographic and blockchain-based
|
||||
systems are variable and may increase at any time. Accordingly, you understand and agree to assume full
|
||||
responsibility for all of the risks of accessing and using and engaging with the Utilities.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>13. Indemnity</v-card-title>
|
||||
<v-card-text>
|
||||
You will indemnify, defend (at Dexorder’s option) and hold Dexorder and its affiliates and their respective
|
||||
officers,
|
||||
directors, employees and agents, harmless from and against any claims, disputes, demands, liabilities, damages,
|
||||
losses,
|
||||
and costs and expenses, including, without limitation, reasonable legal and accounting fees arising out of or in
|
||||
any way
|
||||
connected with: (a) your access to or use of the Utilities, (b) Interactions and the Vault, (c) your violation of
|
||||
these
|
||||
Terms, or (d) your negligence, willful misconduct, fraud, or violation of Applicable Laws. You may not settle or
|
||||
otherwise compromise any claim subject to this Section without Dexorder’s prior written approval.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>14. Limitation of Liability</v-card-title>
|
||||
<v-card-text>
|
||||
(a) TO THE MAXIMUM EXTENT PERMITTED BY LAW, NEITHER DEXORDER NOR ITS SERVICE
|
||||
PROVIDERS INVOLVED IN CREATING, PRODUCING, OR DELIVERING THE UTILITIES WILL BE LIABLE FOR ANY
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES, OR DAMAGES FOR LOST PROFITS, LOST
|
||||
REVENUES, LOST SAVINGS, LOST BUSINESS OPPORTUNITY, LOSS OF DATA OR GOODWILL, SERVICE
|
||||
INTERRUPTION, COMPUTER DAMAGE OR SYSTEM FAILURE OR THE COST OF SUBSTITUTE SERVICES OF ANY KIND
|
||||
ARISING OUT OF OR IN CONNECTION WITH THESE TERMS OR FROM THE USE OF OR INABILITY TO USE THE
|
||||
UTILITIES, WHETHER BASED ON WARRANTY, CONTRACT, TORT (INCLUDING NEGLIGENCE), PRODUCT LIABILITY
|
||||
OR ANY OTHER LEGAL THEORY, AND WHETHER OR NOT DEXORDER OR ITS SERVICE PROVIDERS HAS BEEN
|
||||
INFORMED OF THE POSSIBILITY OF SUCH DAMAGE, EVEN IF A LIMITED REMEDY SET FORTH HEREIN IS FOUND TO
|
||||
HAVE FAILED OF ITS ESSENTIAL PURPOSE.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(b) TO THE MAXIMUM EXTENT PERMITTED BY THE LAW, IN NO EVENT WILL DEXORDER’S
|
||||
TOTAL LIABILITY ARISING OUT OF OR IN CONNECTION WITH THESE TERMS OR FROM THE USE OF OR INABILITY
|
||||
TO USE THE UTILITIES EXCEED THE TOTAL FEES YOU HAVE PAID OR ARE PAYABLE BY YOU TO DEXORDER FOR USE
|
||||
OF THE UTILITIES (EXCLUDING GAS FEES), OR ONE HUNDRED DOLLARS ($100) IF YOU HAVE NOT HAD ANY
|
||||
PAYMENT OBLIGATIONS TO DEXORDER, AS APPLICABLE.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(c) THE EXCLUSIONS AND LIMITATIONS OF DAMAGES SET FORTH ABOVE ARE
|
||||
FUNDAMENTAL ELEMENTS OF THE BASIS OF THE BARGAIN BETWEEN DEXORDER AND YOU.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>15. Governing Law and Forum Choice</v-card-title>
|
||||
<v-card-text>
|
||||
These Terms will be governed by and construed in accordance with the laws of the British Virgin Islands without
|
||||
regard
|
||||
to its conflict of laws provisions.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>16. Dispute Resolution</v-card-title>
|
||||
<v-card-text>
|
||||
(a) <u>Mandatory Arbitration of Disputes</u>. We each agree that any dispute, claim or
|
||||
controversy arising out of or relating to these Terms or the breach, termination, enforcement, interpretation or
|
||||
validity thereof or the use of the Utilities (collectively, “<b>Disputes</b>”) will be resolved <b>solely by
|
||||
binding, individual
|
||||
arbitration and not in a class, representative or consolidated action or proceeding.</b> You and Dexorder agree
|
||||
that the Cayman Islands Arbitration Law governs the interpretation and enforcement of these Terms, and that
|
||||
you and Dexorder are each waiving the right to a trial by jury or to participate in a class action. This
|
||||
arbitration
|
||||
provision shall survive termination of these Terms.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(b) <u>Exceptions</u>. As limited exceptions to Section 16.(a) above: (i) each party may seek to
|
||||
resolve a Dispute in small claims court if it qualifies; and (ii) each party retains the right to seek injunctive
|
||||
or
|
||||
other equitable relief from a court to prevent (or enjoin) the infringement or misappropriation of our
|
||||
intellectual
|
||||
property rights.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(c) <u>Conducting Arbitration and Arbitration Rules</u>. The arbitration will be conducted by the
|
||||
Cayman International Mediation & Arbitration Centre (CI-MAC) in accordance with its arbitration rules in force
|
||||
at the time of the dispute (“<b>CI-MAC Rules</b>”), except as modified by these Terms. The CI-MAC Rules are
|
||||
available
|
||||
at <a href="https://www.caymanarbitration.com/arbitrationrules2023">https://www.caymanarbitration.com/arbitrationrules2023</a>.
|
||||
A party who wishes to start arbitration must
|
||||
submit a written request for arbitration to CI-MAC and give notice to the other party as specified in the CI-MAC
|
||||
Rules. CI-MAC provides instructions on submitting a request for arbitration under Section 3 (Request for
|
||||
arbitration) of the CI-MAC Rules.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
Any arbitration hearings will take place in the county (or parish) where you live, unless the parties agree to a
|
||||
different location. The parties agree that the arbitrator shall have exclusive authority to decide all issues
|
||||
relating
|
||||
to the interpretation, applicability, enforceability and scope of this arbitration agreement.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(d) <u>Arbitration Costs</u>. Payment of all filing, administration and arbitrator fees will be
|
||||
governed by the JAMS Rules, and we won’t seek to recover the administration and arbitrator fees we are
|
||||
responsible for paying, unless the arbitrator finds your Dispute frivolous. If we prevail in arbitration we’ll pay
|
||||
all
|
||||
of our attorneys’ fees and costs and won’t seek to recover them from you. If you prevail in arbitration you will
|
||||
be entitled to an award of attorneys’ fees and expenses to the extent provided under Applicable Law.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(e) <u>Injunctive and Declaratory Relief</u>. Except as provided in Section 16.(b) above, the
|
||||
arbitrator shall determine all issues of liability on the merits of any claim asserted by either party and may
|
||||
award
|
||||
declaratory or injunctive relief only in favor of the individual party seeking relief and only to the extent
|
||||
necessary to provide relief warranted by that party’s individual claim. To the extent that you or we prevail on a
|
||||
claim and seek public injunctive relief (that is, injunctive relief that has the primary purpose and effect of
|
||||
prohibiting unlawful acts that threaten future injury to the public), the entitlement to and extent of such relief
|
||||
must be litigated in a civil court of competent jurisdiction and not in arbitration. The parties agree that
|
||||
litigation
|
||||
of any issues of public injunctive relief shall be stayed pending the outcome of the merits of any individual
|
||||
claims in arbitration.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(f) <u>Class Action Waiver</u>. <b>YOU AND DEXORDER AGREE THAT EACH MAY BRING CLAIMS
|
||||
AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS
|
||||
MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING.</b> Further, if the parties’ Dispute is
|
||||
resolved through arbitration, the arbitrator may not consolidate another person’s claims with your claims, and
|
||||
may not otherwise preside over any form of a representative or class proceeding. If this specific provision is
|
||||
found to be unenforceable, then the entirety of this Dispute Resolution section shall be null and void.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(g) <u>Severability</u>. With the exception of any of the provisions in Section 16.(f) of these Terms
|
||||
(“<b>Class Action Waiver</b>”), if an arbitrator or court of competent jurisdiction decides that any part of these
|
||||
Terms
|
||||
is invalid or unenforceable, the other parts of these Terms will still apply.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>17. General Terms</v-card-title>
|
||||
<v-card-text>
|
||||
(a) <u>Reservation of Rights</u>. Dexorder and its licensors exclusively own all right, title and
|
||||
interest in and to the Dexorder Service, including all associated intellectual property rights. You acknowledge
|
||||
that the Dexorder Service is protected by copyright, trademark, and other laws of the United States and foreign
|
||||
countries. You agree not to remove, alter or obscure any copyright, trademark, service mark or other
|
||||
proprietary rights notices incorporated in or accompanying the Dexorder Service.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(b) <u>Entire Agreement</u>. These Terms, including any addendum terms, constitute the entire
|
||||
and exclusive understanding and agreement between Dexorder and you regarding the Dexorder Service, and
|
||||
these Terms supersede and replace all prior oral or written understandings or agreements between Dexorder
|
||||
and you regarding the Dexorder Service. If any provision of these Terms is held invalid or unenforceable by an
|
||||
arbitrator or a court of competent jurisdiction, that provision will be enforced to the maximum extent
|
||||
permissible and the other provisions of these Terms will remain in full force and effect. Except where provided
|
||||
by Applicable Law in your jurisdiction, you may not assign or transfer these Terms, by operation of law or
|
||||
otherwise, without Dexorder’s prior written consent. Any attempt by you to assign or transfer these Terms
|
||||
absent our consent or your statutory right, will be null. Dexorder may freely assign or transfer these Terms
|
||||
without restriction. Subject to the foregoing, these Terms will bind and inure to the benefit of the parties,
|
||||
their
|
||||
successors and permitted assigns.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(c) <u>Notices</u>. Any notices or other communications provided by Dexorder under these Terms
|
||||
will be given: (i) via email; or (ii) by posting to the Dexorder Service. For notices made by email, the date of
|
||||
receipt will be deemed the date on which such notice is transmitted.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
(d) <u>Waiver of Rights</u>. Dexorder’s failure to enforce any right or provision of these Terms will
|
||||
not be considered a waiver of such right or provision. The waiver of any such right or provision will be effective
|
||||
only if in writing and signed by a duly authorized representative of Dexorder. Except as expressly set forth in
|
||||
these Terms, the exercise by either party of any of its remedies under these Terms will be without prejudice to
|
||||
its other remedies under these Terms or otherwise.
|
||||
</v-card-text>
|
||||
|
||||
<v-card-title>18. Contact Information</v-card-title>
|
||||
<v-card-text>
|
||||
If you have any questions about these Terms or the Dexorder Service, please contact Dexorder
|
||||
at <a href="mailto:legal@dexorder.com">legal@dexorder.com</a> or <a href="mailto:support@dexorder.com">support@dexorder.com</a>.
|
||||
</v-card-text>
|
||||
|
||||
|
||||
<slot/>
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'tos-card'
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.ii {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<v-text-field label="Tranches" type="number" variant="outlined" aria-valuemin="1" min="1" max="255"
|
||||
v-model="os.tranches" :rules="[validateRequired,validateTranches]" v-auto-select>
|
||||
<template v-slot:append-inner>tranches</template>
|
||||
</v-text-field>
|
||||
<v-text-field label="Tranches" type="number" variant="outlined" aria-valuemin="1" min="1" max="255"
|
||||
v-model="os.tranches" :rules="[validateRequired,validateTranches]" v-auto-select>
|
||||
<template v-slot:append-inner>tranches</template>
|
||||
</v-text-field>
|
||||
<v-text-field type="number" variant="outlined" :min="1" v-model="os.interval" class="interval"
|
||||
:label="os.intervalIsTotal ? 'Completion time' : 'Time between tranches'" v-auto-select>
|
||||
<template v-slot:prepend-inner>
|
||||
|
||||
@@ -36,10 +36,10 @@ const showDialog = ref(false)
|
||||
const upgradeResult = ref(null)
|
||||
|
||||
detectUpgrade().then((version)=>{
|
||||
if (version===0) {
|
||||
console.log('Vault is the latest version')
|
||||
if (version===null) {
|
||||
console.log('detectUpgrade failed')
|
||||
}
|
||||
else {
|
||||
else if (s.version<version) {
|
||||
upgradeVersion.value = version
|
||||
console.log(`Vault upgrade available to version ${version}`)
|
||||
}
|
||||
|
||||
@@ -34,12 +34,10 @@
|
||||
<v-card-item v-if="!empty">
|
||||
<v-table>
|
||||
<tbody>
|
||||
<suspense>
|
||||
<native-row v-if="nativeBalance" :chain-id="s.chainId" :addr="s.vault" :amount="nativeBalance"
|
||||
:on-withdraw="onWithdrawNative" :on-wrap="()=>wrapShow=true"/>
|
||||
</suspense>
|
||||
<native-row v-if="nativeBalance && BigInt(nativeBalance)>0n" :chain-id="s.chainId" :addr="s.vault" :amount="nativeBalance"
|
||||
:on-withdraw="onWithdrawNative" :on-wrap="()=>wrapShow=true"/>
|
||||
<suspense v-for="(amount,addr) of balances">
|
||||
<token-row v-if="BigInt(amount)!==0n" :chain-id="s.chainId" :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/>
|
||||
<token-row v-if="amount && BigInt(amount)>0n" :chain-id="s.chainId" :addr="addr" :amount="amount" :onWithdraw="onWithdraw"/>
|
||||
</suspense>
|
||||
</tbody>
|
||||
</v-table>
|
||||
@@ -62,13 +60,13 @@
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {computed, defineAsyncComponent, onUnmounted, ref, watchEffect} from "vue";
|
||||
import {vaultAddress} from "@/blockchain/contract.js";
|
||||
import {ensureVault, provider} from "@/blockchain/wallet.js";
|
||||
import {ensureVault} from "@/blockchain/wallet.js";
|
||||
import CopyButton from "@/components/CopyButton.vue";
|
||||
import Withdraw from "@/components/Withdraw.vue";
|
||||
import {getToken} from "@/blockchain/token.js";
|
||||
import NativeRow from "@/components/NativeRow.vue";
|
||||
import NativeWrap from "@/components/NativeWrap.vue";
|
||||
import WithdrawNative from "@/components/WithdrawNative.vue";
|
||||
import {provider} from "@/blockchain/provider.js";
|
||||
|
||||
const TokenRow = defineAsyncComponent(()=>import('./TokenRow.vue'))
|
||||
const s = useStore()
|
||||
@@ -91,9 +89,8 @@ const hasVault = computed(()=>s.vault!==null)
|
||||
|
||||
const withdrawToken = ref(null)
|
||||
const withdrawShow = ref(false)
|
||||
async function onWithdraw(addr) {
|
||||
const token = await getToken(s.chainId, addr)
|
||||
console.log('withdraw', addr, token)
|
||||
async function onWithdraw(token) {
|
||||
console.log('withdraw', addr.value, token)
|
||||
withdrawToken.value = token
|
||||
withdrawShow.value = true
|
||||
}
|
||||
|
||||
72
src/components/WelcomeDialog.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<v-dialog v-model="modelValue" max-width="600">
|
||||
<v-card title="Welcome to Dexorder!">
|
||||
<template #prepend><logo variant="icon" size="large"/></template>
|
||||
<v-card-text>
|
||||
<div class="d-flex">
|
||||
Dexorder powers up <div class="d-inline-flex align-self-baseline"><v-img src="/uniswap-logo.svg" width="1.25em" inline/></div>Uniswap with advanced ordering capabilities.
|
||||
</div>
|
||||
<p class="mt-2">
|
||||
Dexorder gives you a personal trading vault smart contract that trades its tokens with Uniswap only when
|
||||
your advanced order conditions are met.
|
||||
</p>
|
||||
<p class="mt-2">Level up your DeFi trading with:</p>
|
||||
<v-list density="compact">
|
||||
<v-list-item prepend-icon="mdi-clock-outline"><b>DCA / TWAP</b> <small>up to 1000 parts</small></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-ray-vertex"><b>Limit Ladders</b> <small>capture ranges</small></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-vector-line"><b>Diagonal Limits</b> <small>trade trends and channels</small></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-chart-line"><b>Breakout Orders</b> <small>buy <i>above</i> a price level</small></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-plus-minus"><b>Stop-loss</b> <small>coming soon</small></v-list-item>
|
||||
<!-- <v-list-item prepend-icon="mdi-cancel">One-click Cancel All</v-list-item>-->
|
||||
<!--
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-avatar image="/arbitrum-logo.svg" size="1.5em" class="mr-4"/>
|
||||
</template>
|
||||
<template #default>
|
||||
<b>Arbitrum One</b> support <small>fast and cheap</small>
|
||||
</template>
|
||||
</v-list-item>
|
||||
-->
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-avatar image="/uniswap-logo.svg" size="1.5em" class="mr-4" style="background-color: white"/>
|
||||
</template>
|
||||
<template #default>
|
||||
<b>Uniswap</b> v3 support <small>high liquidity</small>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center mb-9">
|
||||
<!-- <v-btn variant="text" @click="learnMore" class="justify-end">Learn More</v-btn>-->
|
||||
<v-btn variant="tonal" color="primary" @click="tryIt" class="justify-center">Try It!</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {track} from "@/track.js";
|
||||
import Logo from "@/components/Logo.vue";
|
||||
|
||||
const modelValue = defineModel()
|
||||
|
||||
function tryIt() {
|
||||
track('tutorial_begin')
|
||||
modelValue.value = false
|
||||
}
|
||||
|
||||
function learnMore() {
|
||||
track('learn-more')
|
||||
window.open('https://dexorder.com/introduction.html', 'dexorderwww')
|
||||
modelValue.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
small {
|
||||
margin-left: 1em;
|
||||
}
|
||||
</style>
|
||||
@@ -11,7 +11,7 @@
|
||||
<v-btn variant="text" text="max" @click="floatAmount=balanceFloat"/>
|
||||
</template>
|
||||
<template v-slot:append-inner>
|
||||
<span>{{ token.s }}</span>
|
||||
<span style="max-width: 6em">{{ token.s }}</span>
|
||||
</template>
|
||||
</v-text-field>
|
||||
<v-card-actions>
|
||||
@@ -30,16 +30,23 @@ import {tokenFloat} from "@/misc.js";
|
||||
import {vaultContract} from "@/blockchain/contract.js"
|
||||
import {pendTransaction} from "@/blockchain/wallet.js";
|
||||
import {FixedNumber} from "ethers";
|
||||
import {WithdrawTransaction} from "@/blockchain/transaction.js";
|
||||
|
||||
const s = useStore()
|
||||
const props = defineProps(['modelValue', 'vault', 'token'])
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const balance = computed(() => {
|
||||
console.log('balance', props.vault, props.token, s.vaultBalances)
|
||||
const b = s.vaultBalances[props.vault][props.token.address];
|
||||
const b = s.getBalance(props.token?.a)
|
||||
console.log('balance', b)
|
||||
// const b = s.vaultBalances[props.vault][props.token.address];
|
||||
return b === undefined ? 0n : BigInt(b)
|
||||
})
|
||||
const balanceFloat = computed(() => tokenFloat(props.token, balance.value))
|
||||
const balanceFloat = computed(() => {
|
||||
let balance = tokenFloat(props.token, s.getBalance(props.token?.a))
|
||||
balance /= 10**props.token.d
|
||||
console.log('balanceFloat', balance, s.getBalance(props.token?.a), props.token)
|
||||
return balance
|
||||
})
|
||||
const floatAmount = ref(0)
|
||||
|
||||
function withdraw() {
|
||||
@@ -50,10 +57,7 @@ function withdraw() {
|
||||
if( amount === 0n )
|
||||
return
|
||||
console.log('pending withdrawal', valueStr, amount, props.token.s)
|
||||
pendTransaction(async (signer)=>{
|
||||
const vault = await vaultContract(vaultAddr, signer)
|
||||
return await vault['withdraw(address,uint256)'](props.token.a, amount)
|
||||
})
|
||||
new WithdrawTransaction(s.chainId, vaultAddr, props.token, amount).submit()
|
||||
floatAmount.value = 0
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store";
|
||||
import {computed, ref} from "vue";
|
||||
import {vaultContract} from "@/blockchain/contract.js"
|
||||
import {pendTransaction} from "@/blockchain/wallet.js";
|
||||
import {FixedNumber} from "ethers";
|
||||
import {WithdrawNativeTransaction} from "@/blockchain/transaction.js";
|
||||
|
||||
const s = useStore()
|
||||
const props = defineProps(['modelValue', 'vault', 'balance'])
|
||||
@@ -44,11 +43,7 @@ function withdraw() {
|
||||
console.log('pending native withdrawal', valueStr, amount)
|
||||
if( amount === 0n )
|
||||
return
|
||||
pendTransaction(async (signer)=>{
|
||||
const vault = await vaultContract(vaultAddr, signer)
|
||||
console.log('invoking withdraw', vaultAddr, amount)
|
||||
return await vault['withdraw(uint256)'](amount)
|
||||
})
|
||||
new WithdrawNativeTransaction(s.chainId, vaultAddr, amount).submit()
|
||||
floatAmount.value = 0
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
<script setup>
|
||||
import {computed} from "vue";
|
||||
import DCABuilder from "@/components/chart/DCABuilder.vue";
|
||||
import DateBuilder from "@/components/chart/DateBuilder.vue";
|
||||
import LimitBuilder from "@/components/chart/LimitBuilder.vue";
|
||||
import MarketBuilder from "@/components/chart/MarketBuilder.vue";
|
||||
import DiagonalBuilder from "@/components/chart/DiagonalBuilder.vue";
|
||||
import DCABuilder from "@/components/chart/DCABuilder.vue";
|
||||
|
||||
const props = defineProps(['order', 'builder'])
|
||||
|
||||
@@ -21,6 +22,8 @@ const component = computed(()=>{
|
||||
return LimitBuilder
|
||||
case 'DiagonalBuilder':
|
||||
return DiagonalBuilder
|
||||
case 'DateBuilder':
|
||||
return DateBuilder
|
||||
default:
|
||||
console.error('Unknown builder component '+props.builder.component)
|
||||
return null
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
<template>
|
||||
<row-bar :color="builder.color">
|
||||
<color-band :color="builder.color"/>
|
||||
<slot/>
|
||||
<div class="align-self-center">
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="plain" v-bind="props" icon="mdi-dots-vertical"/>
|
||||
</template>
|
||||
<v-list>
|
||||
<!-- <v-list-subheader :title="'Limit '+ (priceA?priceA.toPrecision(5):'')"/>-->
|
||||
<v-list-item title="Delete" key="withdraw" value="withdraw" prepend-icon="mdi-delete" color="red"
|
||||
@click="deleteMyBuilder"/>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-sheet dense style="overflow-y: hidden" class="pa-1 pb-2">
|
||||
<h3 class="ml-1">{{name}}</h3>
|
||||
<div class="d-flex flex-row align-content-stretch">
|
||||
<div class="ml-2"> </div>
|
||||
<slot/>
|
||||
<div class="align-self-center ml-auto mr-3 trashcan">
|
||||
<v-btn icon="mdi-delete" @click="showDeleteDialog=true"/>
|
||||
</div>
|
||||
</div>
|
||||
</row-bar>
|
||||
<v-dialog v-model="showDeleteDialog" :max-width="300">
|
||||
<v-card :title="`Delete ${name}?`" :text="`Do you want to delete this ${name}?`">
|
||||
<v-card-actions>
|
||||
<v-btn @click="showDeleteDialog=false">Keep</v-btn>
|
||||
<v-btn variant="tonal" color="error" @click="deleteMyBuilder">Delete</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {builderFuncs, deleteBuilder} from "@/orderbuild.js";
|
||||
import ColorBand from "@/components/chart/ColorBand.vue";
|
||||
import RowBar from "@/components/chart/RowBar.vue";
|
||||
import {onBeforeUnmount, onMounted, onUnmounted, onUpdated, watchEffect} from "vue";
|
||||
import {onBeforeUnmount, onMounted, onUnmounted, onUpdated, ref, watchEffect} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
order: Object,
|
||||
builder: Object,
|
||||
buildTranches: {type: Function},
|
||||
@@ -32,6 +35,7 @@ const props = defineProps({
|
||||
})
|
||||
const emit = defineEmits(['update:builder'])
|
||||
|
||||
const showDeleteDialog = ref(false)
|
||||
|
||||
let lastId = props.builder.id
|
||||
builderFuncs[props.builder.id] = props.buildTranches
|
||||
@@ -53,6 +57,7 @@ if (props.deleteShapes)
|
||||
onBeforeUnmount(props.deleteShapes)
|
||||
|
||||
function deleteMyBuilder() {
|
||||
showDeleteDialog.value = false;
|
||||
deleteBuilder(props.order, props.builder);
|
||||
}
|
||||
|
||||
@@ -60,5 +65,11 @@ function deleteMyBuilder() {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/vars";
|
||||
|
||||
.trashcan {
|
||||
:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div ref="element" class="chart"/>
|
||||
<div id="tv-widget" ref="element" class="chart"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import "/public/datafeeds/udf/dist/bundle.js"
|
||||
import {onMounted, ref} from "vue";
|
||||
import {initWidget} from "@/charts/chart.js";
|
||||
|
||||
@@ -12,18 +11,8 @@ const element = ref()
|
||||
onMounted(() => {
|
||||
const el = element.value;
|
||||
initWidget(el)
|
||||
initShapes()
|
||||
})
|
||||
|
||||
function initShapes() {
|
||||
// const c = widget.chart()
|
||||
// for( const s of ss.shapes ) {
|
||||
// const type = s.type.toLowerCase().replace(' ','_')
|
||||
// console.log('create type', type)
|
||||
// c.createMultipointShape(s.points, {shape:type})
|
||||
// }
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,63 +1,55 @@
|
||||
|
||||
<template>
|
||||
<row-bar :color="color">
|
||||
<color-band :color="color"/>
|
||||
<div :style="faintColorStyle" style="width: 100%" class="justify-start align-content-start">
|
||||
<v-text-field type="number" inputmode="numeric" pattern="[0-9]*\.?[0-9]*" v-model="order.amount" variant="outlined"
|
||||
density="compact"
|
||||
:hint="available" :persistent-hint="true"
|
||||
min="0"
|
||||
class="amount py-2" :color="color"
|
||||
:label="order.amountIsTokenA ? 'Amount':('Value in '+co.selectedSymbol.quote.s)"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-btn variant="outlined" :color="color" class="ml-3"
|
||||
:text="(order.buy ? 'Buy ' : 'Sell ') + co.selectedSymbol.base.s"
|
||||
@click="order.buy=!order.buy"/>
|
||||
</template>
|
||||
<template #prepend-inner>
|
||||
<v-btn variant="text" text="max" class="px-0" size="small"
|
||||
:disabled="!maxAmount || order.amountIsTokenA===order.buy && !co.price" @click="setMax"/>
|
||||
</template>
|
||||
<template #append-inner>
|
||||
<v-btn :text="order.amountIsTokenA?co.baseToken.s:co.quoteToken.s+' worth'"
|
||||
:color="color" variant="text" @click="toggleAmountToken"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
<row-bar :color="sideColor">
|
||||
<div style="width: 100%" class="justify-start align-content-start">
|
||||
<order-amount :order="props.order" class="mt-2" :color="sideColor"/>
|
||||
<template v-for="b in builders">
|
||||
<builder-factory :order="order" :builder="b"/>
|
||||
</template>
|
||||
<div class="my-3">
|
||||
<div v-if="order.builders.length===0"> <!--todo remove gralpha limitation of one builder-->
|
||||
<v-tooltip text="Spread order across time" location="top">
|
||||
<v-tooltip text="Up to 1000 equal parts spread across time" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props">
|
||||
<v-btn :color="color" variant="text" prepend-icon="mdi-clock-outline" @click="build(order,'DCABuilder')">DCA</v-btn>
|
||||
<v-btn id="DCA-button" :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-clock-outline" @click="build(order,'DCABuilder')">DCA</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip text="Trade a price level" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props">
|
||||
<v-btn :color="color" variant="text" prepend-icon="mdi-ray-vertex" @click="build(order,'LimitBuilder')">Limit</v-btn>
|
||||
<v-btn id="LimitBuilder-button" :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-ray-vertex" @click="build(order,'LimitBuilder')">Limit</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip text="Trade trends and channels" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props">
|
||||
<v-btn :color="color" variant="text" prepend-icon="mdi-vector-line" @click="build(order,'DiagonalBuilder')">Diagonal</v-btn>
|
||||
<v-btn id="Diagonal-button" :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-vector-line" @click="build(order,'DiagonalBuilder')">Diagonal</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip text="Up to 10 weighted parts spaced out in time" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props">
|
||||
<v-btn id="Dates-button" :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-reorder-vertical" @click="build(order,'DateBuilder')">Dates</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip text="Coming Soon! Stoplosses and Takeprofits" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<span v-bind="props">
|
||||
<v-btn :color="color" variant="text" prepend-icon="mdi-plus-minus" disabled>Stoploss</v-btn>
|
||||
<v-btn :class="order.buy?'green':'red'" variant="text" prepend-icon="mdi-plus-minus" disabled>Stoploss</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<!-- mdi-ray-start-end mdi-vector-polyline -->
|
||||
|
||||
<!-- after="newbie"-->
|
||||
<one-time-hint name="choose-builder" :activator="hintData.activator"
|
||||
:text="hintData.text" location="top"
|
||||
:when="!builtAny"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,30 +60,34 @@
|
||||
|
||||
import BuilderFactory from "@/components/chart/BuilderFactory.vue";
|
||||
import {builderFuncs, newBuilder, orderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {useOrderStore, useStore} from "@/store/store.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {computed, onUnmounted, onUpdated, ref, watchEffect} from "vue";
|
||||
import {lightenColor, lightenColor2} from "@/misc.js";
|
||||
import {toPrecision} from "@/misc.js";
|
||||
import {useTheme} from "vuetify";
|
||||
import RowBar from "@/components/chart/RowBar.vue";
|
||||
import ColorBand from "@/components/chart/ColorBand.vue";
|
||||
import Color from "color";
|
||||
import {Exchange, newOrder} from "@/blockchain/orderlib.js";
|
||||
import {lookupSymbol} from "@/charts/datafeed.js";
|
||||
import {newOrder} from "@/blockchain/orderlib.js";
|
||||
import OrderAmount from "@/components/chart/OrderAmount.vue";
|
||||
import {track} from "@/track.js";
|
||||
import OneTimeHint from "@/components/OneTimeHint.vue";
|
||||
|
||||
const props = defineProps(['order'])
|
||||
const s = useStore()
|
||||
const co = useChartOrderStore()
|
||||
const os = useOrderStore()
|
||||
|
||||
const marketBuilder = newBuilder('MarketBuilder')
|
||||
|
||||
console.log('chart order', props.order)
|
||||
const builders = computed(()=>props.order.builders.length > 0 ? props.order.builders : [marketBuilder])
|
||||
const tokenIn = computed(()=>props.order.buy ? co.quoteToken : co.baseToken)
|
||||
const tokenOut = computed(()=>props.order.buy ? co.baseToken : co.quoteToken)
|
||||
|
||||
|
||||
console.log('order', props.order)
|
||||
const builtAny = ref(false)
|
||||
|
||||
function build(order, component, options={}) {
|
||||
track('build', {builder:component})
|
||||
builtAny.value = true
|
||||
order.builders.push(newBuilder(component, options))
|
||||
}
|
||||
|
||||
@@ -115,7 +111,38 @@ function buildOrder() {
|
||||
const order = props.order
|
||||
console.log('buildOrder', order)
|
||||
|
||||
if (!order.amount) return null
|
||||
if (!order.amount)
|
||||
return {order: null, warnings: ['Amount must be greater than 0']}
|
||||
|
||||
const symbol = co.selectedSymbol
|
||||
const amountDec = order.amountIsTokenA ? symbol.base.d : symbol.quote.d
|
||||
const amount = BigInt(Math.trunc(order.amount * 10 ** amountDec))
|
||||
|
||||
let warnings = []
|
||||
const inAddr = order.buy ? symbol.quote.a : symbol.base.a
|
||||
const inDec = order.buy ? symbol.quote.d : symbol.base.d
|
||||
const available = s.getBalance(inAddr) / 10 ** inDec
|
||||
|
||||
let needed
|
||||
if (order.amountIsTokenA && order.buy)
|
||||
// need quote currency to buy an amount of base
|
||||
needed = order.amount * co.price
|
||||
else if (order.amountIsTokenA && !order.buy)
|
||||
// sell an exact amount of base
|
||||
needed = order.amount
|
||||
else if (!order.amountIsTokenA && order.buy)
|
||||
// need an exact amount of quote
|
||||
needed = order.amount
|
||||
else if (!order.amountIsTokenA && !order.buy)
|
||||
// sell a quote amount worth of base
|
||||
needed = order.amount / co.price
|
||||
else
|
||||
throw new Error('Invalid order')
|
||||
const deficit = needed - available
|
||||
if (deficit > 0) {
|
||||
const inSymbol = order.buy ? symbol.quote.s : symbol.base.s
|
||||
warnings.push(`Insufficient funds. Add ${toPrecision(deficit, 5)} ${inSymbol} to your vault to complete this order.`)
|
||||
}
|
||||
|
||||
// struct SwapOrder {
|
||||
// address tokenIn;
|
||||
@@ -128,20 +155,24 @@ function buildOrder() {
|
||||
// uint64 conditionalOrder; // use NO_CONDITIONAL_ORDER for no chaining. conditionalOrder index must be < than this order's index for safety (written first) and conditionalOrder state must be Template
|
||||
// Tranche[] tranches;
|
||||
// }
|
||||
const symbol = co.selectedSymbol
|
||||
const amountDec = order.amountIsTokenA ? symbol.base.d : symbol.quote.d
|
||||
const amount = BigInt(Math.trunc(order.amount * 10 ** amountDec))
|
||||
const amountIsInput = !!(order.amountIsTokenA ^ order.buy)
|
||||
|
||||
let tranches = []
|
||||
for (const builder of builders.value) {
|
||||
console.log('builder', builder)
|
||||
const ts = builderFuncs[builder.id]()
|
||||
const built = builderFuncs[builder.id]()
|
||||
const ts = built.tranches
|
||||
const ws = built.warnings
|
||||
console.log('tranches', ts)
|
||||
tranches = [...tranches, ...ts]
|
||||
warnings = [...warnings, ...ws]
|
||||
}
|
||||
|
||||
return newOrder(tokenIn.value.a, tokenOut.value.a, symbol.exchangeId, symbol.fee, amount, amountIsInput, symbol.inverted, tranches)
|
||||
return {
|
||||
warnings,
|
||||
order: newOrder(tokenIn.value.a, tokenOut.value.a, symbol.exchangeId, symbol.fee,
|
||||
amount, amountIsInput, symbol.inverted, tranches),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,63 +188,32 @@ onUnmounted(() => delete orderFuncs[lastId])
|
||||
|
||||
|
||||
const theme = useTheme().current
|
||||
const color = computed(()=>new Color(props.order.buy?theme.value.colors.success:theme.value.colors.error).darken(0.2).string())
|
||||
const lightColor = computed(() => lightenColor(color.value))
|
||||
const faintColor = computed(() => lightenColor2(color.value))
|
||||
const colorStyle = computed(() => { return {'color': color.value} })
|
||||
const bgColorStyle = computed(() => { return {'background-color': color.value} })
|
||||
const lightColorStyle = computed(() => { return {'background-color': lightColor.value} })
|
||||
const faintColorStyle = computed(() => { return {'background-color': faintColor.value} })
|
||||
const sideColor = computed(()=>new Color(props.order.buy?theme.value.colors.success:theme.value.colors.error).darken(0.2).string())
|
||||
const color = computed(()=>theme.value.colors["on-background"])
|
||||
// const lightColor = computed(() => lightenColor(color.value))
|
||||
// const faintColor = computed(() => lightenColor2(color.value))
|
||||
// const colorStyle = computed(() => { return {'color': color.value} })
|
||||
// const bgColorStyle = computed(() => { return {'background-color': color.value} })
|
||||
// const lightColorStyle = computed(() => { return {'background-color': lightColor.value} })
|
||||
// const faintColorStyle = computed(() => { return {'background-color': faintColor.value} })
|
||||
|
||||
const inToken = computed( ()=>props.order.buy ? co.quoteToken : co.baseToken )
|
||||
const maxAmount = computed(()=>{
|
||||
const token = inToken.value;
|
||||
if (!token)
|
||||
return null
|
||||
const balance = s.balances[token.a]
|
||||
if( !balance )
|
||||
return null
|
||||
const divisor = os.amountIsTotal ? 1 : os.tranches
|
||||
return balance / 10**token.d / divisor
|
||||
})
|
||||
const available = computed(()=>{
|
||||
const max = maxAmount.value
|
||||
return max === null ? '' : `Available: ${maxAmount.value} ${tokenIn.value.s}`
|
||||
})
|
||||
// Tutorial Hint
|
||||
|
||||
const lastMaxValue = ref(-1)
|
||||
let tutorial = 'limit'
|
||||
|
||||
function setMax() {
|
||||
let amount = maxAmount.value
|
||||
if (amount) {
|
||||
const order = props.order
|
||||
const price = co.price
|
||||
if (order.amountIsTokenA===order.buy) {
|
||||
if (order.buy)
|
||||
amount /= price
|
||||
else
|
||||
amount *= price
|
||||
}
|
||||
amount = Number(amount.toPrecision(7))
|
||||
lastMaxValue.value = amount
|
||||
order.amount = amount
|
||||
}
|
||||
const _hintData = {
|
||||
'limit': {
|
||||
activator: '#LimitBuilder-button',
|
||||
text: '↓ Try a Limit Ladder ↓'
|
||||
},
|
||||
}
|
||||
|
||||
function toggleAmountToken() {
|
||||
const order = props.order
|
||||
order.amountIsTokenA=!order.amountIsTokenA
|
||||
if (order.amount === lastMaxValue.value)
|
||||
setMax()
|
||||
}
|
||||
const hintData = computed(()=>_hintData[tutorial] || _hintData['limit'])
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.amount {
|
||||
max-width: 30em;
|
||||
div.v-field {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@use "src/styles/vars" as *;
|
||||
.v-btn.green:hover {color: $green !important;}
|
||||
.v-btn.red:hover {color: $red !important;}
|
||||
</style>
|
||||
@@ -7,48 +7,83 @@
|
||||
Place Dexorder
|
||||
</v-btn>
|
||||
<v-btn variant="text" prepend-icon="mdi-delete" v-if="co.orders.length>0"
|
||||
:disabled="!orderChanged" @click="cancelOrder">Reset</v-btn>
|
||||
:disabled="!orderChanged" @click="resetOrder">Reset</v-btn>
|
||||
<v-btn id="share-btn" variant="text" prepend-icon="mdi-share" v-if="co.orders.length>0"
|
||||
:disabled="sharing"
|
||||
@click="shareOrder">{{sharing?'Preparing...':'Share Order'}}</v-btn>
|
||||
<v-tooltip activator="#share-btn" :text="shareError?'Error copying share link :(':'Copied share link!'" v-model="showSharedTooltip"
|
||||
:open-on-hover="false" :open-on-click="false"
|
||||
/>
|
||||
</template>
|
||||
<needs-chart>
|
||||
<template v-for="o in co.orders">
|
||||
<chart-order :order="o"/>
|
||||
</template>
|
||||
<v-dialog v-model="showResetDialog" max-width="300">
|
||||
<v-card title="Cancel Order?" text="Do you want to cancel this order and start again?">
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn @click="()=>showResetDialog=false">Keep Existing</v-btn>
|
||||
<v-btn @click="()=>{co.resetOrders(); showResetDialog=false}" color="red" text="Reset Order"/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</needs-chart>
|
||||
<div class="overflow-y-auto">
|
||||
<needs-chart>
|
||||
<template v-for="o in co.orders">
|
||||
<chart-order :order="o"/>
|
||||
</template>
|
||||
<v-dialog v-model="showResetDialog" max-width="300">
|
||||
<v-card title="Cancel Order?" text="Do you want to cancel this order and start again?">
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn @click="()=>showResetDialog=false">Keep Existing</v-btn>
|
||||
<v-btn @click="doResetOrder" color="red" text="Reset Order"/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="showWarnings" max-width="300">
|
||||
<v-card prepend-icon="mdi-warning" title="Order Warnings">
|
||||
<v-card-item>
|
||||
<ul class="ml-5">
|
||||
<li v-for="w of orderWarnings">{{w}}</li>
|
||||
</ul>
|
||||
</v-card-item>
|
||||
<v-card-text>Continue placing this order?</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn @click="()=>showWarnings=false">Back</v-btn>
|
||||
<v-btn @click="doPlaceOrder">Place Order</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="placementError" max-width="300">
|
||||
<v-card prepend-icon="mdi-alert" title="Order Error" text="There was an error placing your order. Please try again or contact support.">
|
||||
<v-card-actions>
|
||||
<v-spacer/>
|
||||
<v-btn @click="()=>{placementError=false;ws.transaction=null}">OK</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</needs-chart>
|
||||
</div>
|
||||
</toolbar-pane>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {orderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {addSymbolChangedCallback, removeSymbolChangedCallback} from "@/charts/chart.js";
|
||||
import {computed, onBeforeUnmount, ref} from "vue";
|
||||
import {computed, onBeforeUnmount, ref, toRaw, watchEffect} from "vue";
|
||||
import {useOrderStore, useStore} from "@/store/store.js";
|
||||
|
||||
import {routeFinder} from "@/blockchain/route.js";
|
||||
import ChartOrder from "@/components/chart/ChartOrder.vue";
|
||||
import {useTheme} from "vuetify";
|
||||
import {pendOrder} from "@/blockchain/wallet.js";
|
||||
import {useWalletStore} from "@/blockchain/wallet.js";
|
||||
import ToolbarPane from "@/components/chart/ToolbarPane.vue";
|
||||
import NeedsChart from "@/components/NeedsChart.vue";
|
||||
import {nav} from "@/misc.js";
|
||||
import {PlaceOrderTransaction} from "@/blockchain/transaction.js";
|
||||
import {errorSuggestsMissingVault} from "@/misc.js";
|
||||
import {track} from "@/track.js";
|
||||
import {getShareUrl} from "@/share.js";
|
||||
|
||||
const s = useStore()
|
||||
const co = useChartOrderStore()
|
||||
const os = useOrderStore()
|
||||
const ws = useWalletStore()
|
||||
|
||||
function changeSymbol(symbol) {
|
||||
console.log('changeSymbol', symbol)
|
||||
// console.log('changeSymbol', symbol)
|
||||
os.tokenA = symbol.base
|
||||
os.tokenB = symbol.quote
|
||||
routeFinder.invoke()
|
||||
// routeFinder.invoke()
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +97,7 @@ const theme = useTheme().current
|
||||
const orderColor = computed(()=>co.orders.length===0?null : co.orders[0].buy ? theme.value.colors.success:theme.value.colors.error)
|
||||
|
||||
const valid = computed(()=>{
|
||||
if (!s.allowed)
|
||||
if (!s.approved)
|
||||
return false
|
||||
if (co.drawing)
|
||||
return false
|
||||
@@ -75,34 +110,138 @@ const valid = computed(()=>{
|
||||
|
||||
const orderChanged = computed(()=>!(co.orders.length===1 && co.orders[0].builders.length===0 && !co.orders[0].amount))
|
||||
|
||||
function cancelOrder() {
|
||||
const showWarnings = ref(false)
|
||||
const orderWarnings = ref([])
|
||||
|
||||
const placementError = ref(false)
|
||||
|
||||
function resetOrder() {
|
||||
showResetDialog.value = true
|
||||
}
|
||||
|
||||
function doResetOrder() {
|
||||
co.resetOrders();
|
||||
orderWarnings.value = []
|
||||
showWarnings.value = false
|
||||
showResetDialog.value = false
|
||||
placementError.value = false
|
||||
}
|
||||
|
||||
watchEffect(()=>{
|
||||
const removable = []
|
||||
for (const order of ws.pendingOrders) {
|
||||
console.log('pend state', order.state)
|
||||
switch (order.state) {
|
||||
case PendingOrderState.Sent:
|
||||
break
|
||||
case PendingOrderState.Rejected:
|
||||
removable.push(order)
|
||||
break
|
||||
case PendingOrderState.Signing:
|
||||
break
|
||||
case PendingOrderState.Submitted:
|
||||
removable.push(order)
|
||||
break
|
||||
default:
|
||||
console.error('unknown pend state', order.state)
|
||||
}
|
||||
}
|
||||
if (removable.length>0) {
|
||||
if (ws.pendingOrders.length!==removable.length)
|
||||
console.error('Not all orders were rejected') // todo multiple orders / order group
|
||||
ws.pendingOrders = []
|
||||
}
|
||||
})
|
||||
|
||||
let built = []
|
||||
|
||||
async function placeOrder() {
|
||||
track('place-order')
|
||||
const chartOrders = co.orders;
|
||||
const built = []
|
||||
const allWarns = []
|
||||
built = []
|
||||
for (const chartOrder of chartOrders) {
|
||||
console.log('chartOrder', chartOrder)
|
||||
const buildOrder = orderFuncs[chartOrder.id]
|
||||
const order = buildOrder()
|
||||
const {order, warnings} = buildOrder()
|
||||
built.push(order)
|
||||
allWarns.push(...warnings)
|
||||
}
|
||||
console.log('place orders', built)
|
||||
if (built.length !== 1) {
|
||||
console.error('Multiple orders not supported')
|
||||
|
||||
if (allWarns.length > 0) {
|
||||
orderWarnings.value = allWarns
|
||||
showWarnings.value = true
|
||||
return
|
||||
}
|
||||
|
||||
await doPlaceOrder()
|
||||
}
|
||||
|
||||
async function doPlaceOrder() {
|
||||
console.log('place orders')
|
||||
placementError.value = false
|
||||
showWarnings.value = false
|
||||
if (ws.transaction!==null) {
|
||||
console.error('Transaction already in progress')
|
||||
}
|
||||
else {
|
||||
await pendOrder(built[0])
|
||||
co.resetOrders()
|
||||
nav('Status')
|
||||
const tx = new PlaceOrderTransaction(s.chainId, toRaw(built[0]));
|
||||
tx.retries = 60
|
||||
const oldFailed = tx.failed
|
||||
tx.failed = function(e) {
|
||||
console.error('place order failed', errorSuggestsMissingVault(e), e.code, e)
|
||||
if (errorSuggestsMissingVault(e) && e.action === 'feeManager' && this.retries-- >= 0) {
|
||||
s.creatingVault = true
|
||||
}
|
||||
else {
|
||||
s.creatingVault = false
|
||||
oldFailed.bind(this)(e)
|
||||
placementError.value = e.info?.error?.code !== 4001
|
||||
}
|
||||
}
|
||||
tx.submit() // this assigns the tx to walletStore.transaction
|
||||
}
|
||||
}
|
||||
|
||||
const sharing = ref(false)
|
||||
const showSharedTooltip = ref(false)
|
||||
const shareError = ref(false)
|
||||
const showShareDialog = ref(false)
|
||||
const shareUrl = ref(null)
|
||||
|
||||
function shareOrder() {
|
||||
sharing.value = true
|
||||
track('share')
|
||||
getShareUrl().then(url => {
|
||||
shareError.value = url === null
|
||||
if (url === null) {
|
||||
sharing.value = false
|
||||
return
|
||||
}
|
||||
console.log('share url', url)
|
||||
shareUrl.value = url
|
||||
sharing.value = false
|
||||
navigator.permissions.query({name: "clipboard-write"}).then((permission) => {
|
||||
const permitted = permission.state === "granted" || permission.state === "prompt"
|
||||
if (!permitted) {
|
||||
showShareDialog.value = true
|
||||
} else {
|
||||
navigator.clipboard.writeText(url)
|
||||
.then(() => {
|
||||
showSharedTooltip.value = true
|
||||
setTimeout(() => showSharedTooltip.value = false, 3000)
|
||||
})
|
||||
.catch(() => {
|
||||
showShareDialog.value = true
|
||||
})
|
||||
}
|
||||
}).catch(() => null)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss"> // NOT scoped
|
||||
body {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<toolbar-pane title="Status" icon="mdi-format-list-bulleted-square">
|
||||
<template #toolbar>
|
||||
<v-btn variant="text" v-if="s.vault" prepend-icon="mdi-delete-alert" color="red"
|
||||
@click="cancelAll(s.vault)" :disabled="!anyOrdersOpen"
|
||||
@click="(async function (vault){
|
||||
new CancelAllTransaction(useStore().chainId, vault).submit()
|
||||
})(s.vault)" :disabled="!anyOrdersOpen"
|
||||
text="Cancel All"/>
|
||||
</template>
|
||||
<needs-signer>
|
||||
@@ -16,10 +18,10 @@
|
||||
import ToolbarPane from "@/components/chart/ToolbarPane.vue";
|
||||
import Orders from "@/components/Status.vue";
|
||||
import NeedsSigner from "@/components/NeedsSigner.vue";
|
||||
import {cancelAll} from "@/blockchain/wallet.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {computed} from "vue";
|
||||
import {isOpen} from "@/blockchain/orderlib.js";
|
||||
import {CancelAllTransaction} from "@/blockchain/transaction.js";
|
||||
|
||||
const s = useStore()
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {computed} from "vue";
|
||||
const props = defineProps(['color'])
|
||||
const computedStyle = computed(() => {
|
||||
return {
|
||||
'background-color': props.color,
|
||||
// 'background-color': props.color,
|
||||
'border-color': props.color,
|
||||
'width': '.67em',
|
||||
'border-top-width': '1px',
|
||||
|
||||
@@ -1,246 +1,301 @@
|
||||
<template>
|
||||
<rung-builder name='DCA' :order="order" :builder="builder" v-model="timeEndpoints"
|
||||
:shape="VLine"
|
||||
:mode="1" :flip="flipped" :orientation="0"
|
||||
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
||||
:get-points-value="getPointsValue"
|
||||
:set-values="setValues" :set-weights="setWeights"
|
||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||
<template class="d-flex align-content-center flex-column" style="height: 100%; width: 100%;">
|
||||
<builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
|
||||
:adjust-shapes="adjustShapes" :delete-shapes="deleteShapes" name="DCA">
|
||||
<div class="d-flex flex-column" style="width: 100%;">
|
||||
<div class="d-flex flex-row mb-5">
|
||||
<div class="align-self-center mr-3">Start:</div>
|
||||
<absolute-time-entry v-model="startTime"/>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<v-list style="background-color: inherit">
|
||||
<v-list-item v-for="t in absoluteTimes">{{t}}</v-list-item>
|
||||
</v-list>
|
||||
-->
|
||||
<div class="d-flex flex-row">
|
||||
<div>
|
||||
<v-text-field label="Split into" type="number" variant="outlined"
|
||||
aria-valuemin="1" aria-valuemax="100" min="1" max="1000" step="1"
|
||||
:hint="partsGasHint" :persistent-hint="true"
|
||||
:color="color"
|
||||
v-model="parts" v-auto-select class="parts mr-3">
|
||||
<template v-slot:append-inner>
|
||||
parts
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<absolute-time-entry v-model="absTimeA"/>
|
||||
</td>
|
||||
<td class="weight">{{ weights.length ? allocationTexts[0] : '' }}</td>
|
||||
</tr>
|
||||
<tr v-if="weights.length>2" v-for="i in weights.length-2" class="ml-5"> <!-- vue uses 1-based loops -->
|
||||
<td class="d-flex justify-end pr-3">{{ dateStrings[i] }}</td>
|
||||
<td class="weight">{{ allocationTexts[i] }}</td>
|
||||
</tr>
|
||||
<tr v-if="weights.length>1">
|
||||
<td>
|
||||
<absolute-time-entry v-model="absTimeB"/>
|
||||
</td>
|
||||
<td class="weight">{{ allocationTexts[weights.length-1] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="mr-3">
|
||||
<order-amount :order="props.order" :multiplier="props.builder.tranches" :color="color" style="width: 20em"/>
|
||||
</div>
|
||||
|
||||
|
||||
</rung-builder>
|
||||
<div>
|
||||
<v-text-field type="number" variant="outlined" :min="1" v-model="displayedInterval" class="interval"
|
||||
:color="color"
|
||||
:label="intervalIsTotal ? 'Total completion time' : 'Time between parts'" v-auto-select>
|
||||
<template v-slot:prepend-inner>
|
||||
<v-btn variant="outlined"
|
||||
:text="intervalIsTotal ? 'All within' : 'Spaced apart by'" class="within mr-2"
|
||||
style="width: 10em"
|
||||
@click="intervalIsTotal=!intervalIsTotal"/>
|
||||
</template>
|
||||
<template v-slot:append-inner>
|
||||
<v-btn variant="outlined" :text="timeUnitsStr" style="width: 5em"
|
||||
@click="toggleTimeUnits" class="time-units"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</builder-panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {builderDefaults, DEFAULT_SLIPPAGE, MIN_EXECUTION_TIME, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {allocationText, VLine} from "@/charts/shape.js";
|
||||
import {sideColor} from "@/misc.js";
|
||||
import {useTheme} from "vuetify";
|
||||
import {useOrderStore, useStore} from "@/store/store.js";
|
||||
import {DISTANT_FUTURE, MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
||||
import {builderDefaults, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {allocationText, ShapeType} from "@/charts/shape.js";
|
||||
import {sideColor, SingletonCoroutine, toPrecision, vAutoSelect} from "@/misc.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {computed, ref, watchEffect} from "vue";
|
||||
import {chart, dragging} from "@/charts/chart.js";
|
||||
import {createShape, deleteShapeId, dragging, widget} from "@/charts/chart.js";
|
||||
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
||||
import {DateTime} from "luxon";
|
||||
import BuilderPanel from "@/components/chart/BuilderPanel.vue";
|
||||
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||
import Color from "color";
|
||||
import OrderAmount from "@/components/chart/OrderAmount.vue";
|
||||
import {DEFAULT_SLIPPAGE, MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||
import {getFeeSchedule} from "@/fees.js";
|
||||
import {NATIVE_TOKEN} from "@/common.js";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
const co = useChartOrderStore()
|
||||
const theme = useTheme().current
|
||||
const props = defineProps(['order', 'builder'])
|
||||
const emit = defineEmits(['update:builder'])
|
||||
|
||||
const minWidth = computed(()=>co.intervalSecs)
|
||||
const stdWidth = computed(()=>10 * minWidth.value)
|
||||
|
||||
function computeDefaultColor() {
|
||||
const index = props.order.builders.indexOf(props.builder)
|
||||
return sideColor(props.order.buy, index)
|
||||
}
|
||||
|
||||
const defaultColor = computeDefaultColor()
|
||||
const color = computed(()=>computeDefaultColor())
|
||||
|
||||
const defaultTranches = 10
|
||||
|
||||
builderDefaults(props.builder, {
|
||||
timeA: s.clock, // todo relative 0
|
||||
timeB: null,
|
||||
startTime: s.clock, // todo relative 0
|
||||
endTime: s.clock + defaultTranches * co.intervalSecs,
|
||||
interval: co.intervalSecs,
|
||||
tranches: defaultTranches,
|
||||
percentage: 100/defaultTranches,
|
||||
// relative: true, // todo
|
||||
relative: false,
|
||||
slippage: DEFAULT_SLIPPAGE,
|
||||
rungs: 1,
|
||||
skew: 0,
|
||||
color: defaultColor,
|
||||
color: color.value,
|
||||
valid: true,
|
||||
})
|
||||
|
||||
const times = ref([])
|
||||
const weights = ref([])
|
||||
function adjustShapes() {}
|
||||
|
||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(w, w*props.order.amount, amountSymbol.value)))
|
||||
function buildTranches() {
|
||||
const warnings = []
|
||||
let interval = props.builder.interval
|
||||
let parts = props.builder.tranches
|
||||
if (interval < 60) {
|
||||
interval = 60
|
||||
parts = (props.builder.endTime - props.builder.startTime) / interval
|
||||
warnings.push(`DCA parts cannot be closer than one minute apart. Using ${parts} parts.`)
|
||||
}
|
||||
const rate = Math.ceil(MAX_FRACTION / parts)
|
||||
const endTime = Math.ceil(props.builder.endTime + 10*props.builder.tranches)
|
||||
const startTime = Math.floor(props.builder.startTime);
|
||||
const tranches = [newTranche({marketOrder:true,
|
||||
startTime, endTime, rateLimitFraction: rate, rateLimitPeriod: Math.floor(interval),
|
||||
slippage: props.builder.slippage})]
|
||||
return {tranches, warnings}
|
||||
}
|
||||
|
||||
const endTimes = computed(()=>{
|
||||
if (props.builder.rungs === 1)
|
||||
return DISTANT_FUTURE
|
||||
const ts = times.value
|
||||
const window = Math.max(MIN_EXECUTION_TIME, Math.floor((ts[ts.length-1]-ts[0])/props.builder.rungs))
|
||||
return ts.map((t)=>t+window)
|
||||
})
|
||||
const absoluteTimes = computed(()=>{
|
||||
// console.log('absoluteTimes', props.builder.relative, times.value)
|
||||
// if (!props.builder.relative)
|
||||
return times.value
|
||||
// const now = s.clock
|
||||
// return times.value.map((t)=>now+t)
|
||||
const parts = computed({
|
||||
get() { return props.builder.tranches },
|
||||
set(v) {
|
||||
v = Number(v)
|
||||
v = Math.max(1, Math.min(1000,v))
|
||||
props.builder.tranches = v
|
||||
props.builder.endTime = props.builder.startTime + props.builder.interval * v
|
||||
setPoints(null, true)
|
||||
}
|
||||
})
|
||||
|
||||
const dateStrings = computed(()=>absoluteTimes.value.map((t)=>{
|
||||
const n = DateTime.fromSeconds(t).setZone(s.timeZone)
|
||||
const y = n.toLocaleString({year:'numeric'})
|
||||
const m = n.toLocaleString({month:'long'})
|
||||
const d = n.toLocaleString({day:'numeric'})
|
||||
const h = n.toLocaleString({hour:'numeric', minute:'numeric'})
|
||||
return `${y} ${m} ${d} ${h}`
|
||||
}))
|
||||
const sched = ref(null)
|
||||
const schedFetcher = new SingletonCoroutine(async (vault)=>sched.value = vault === null ? null : await getFeeSchedule(vault))
|
||||
|
||||
watchEffect(()=>{
|
||||
// auto scroll
|
||||
if (!dragging && absoluteTimes.value.length) {
|
||||
const endTime = absoluteTimes.value[absoluteTimes.value.length-1]
|
||||
const range = chart.getVisibleRange()
|
||||
const width = range.to - range.from
|
||||
const now = s.clock
|
||||
const extra = (Math.max(0,endTime - now) + stdWidth.value/2) / width
|
||||
// console.log('visrange', range, width, absV)
|
||||
if (range.to < endTime) {
|
||||
// console.log('scrolling')
|
||||
chart.setVisibleRange({from: now - width, to: now}, {
|
||||
percentRightMargin: Math.round(100 * extra),
|
||||
applyDefaultRightMargin: false
|
||||
})
|
||||
const partsGasHint = computed(()=>{
|
||||
if (sched.value === null) {
|
||||
schedFetcher.invoke(s.vault)
|
||||
return null
|
||||
}
|
||||
const ethFee = Number(sched.value.gasFee) * parts.value / 1e18;
|
||||
const mark = s.markPrice(NATIVE_TOKEN)
|
||||
if (mark)
|
||||
return '$' + Number(ethFee*mark).toFixed(2) + ' gas fee'
|
||||
else
|
||||
return toPrecision(ethFee) + ' ETH gas fee'
|
||||
})
|
||||
|
||||
const intervalIsTotal = ref(false)
|
||||
const displayedInterval = computed({
|
||||
get() {
|
||||
let result = props.builder.interval / timeUnits[timeUnitIndex.value][1]
|
||||
if (intervalIsTotal.value)
|
||||
// express as per-part intervals
|
||||
result *= props.builder.tranches
|
||||
return result
|
||||
},
|
||||
set(v) {
|
||||
v = Number(v)
|
||||
let newValue = v * timeUnits[timeUnitIndex.value][1];
|
||||
if (intervalIsTotal.value)
|
||||
newValue /= props.builder.tranches
|
||||
if (Math.abs(newValue - props.builder.interval) >= Number.EPSILON) {
|
||||
props.builder.interval = newValue
|
||||
props.builder.endTime = props.builder.startTime + newValue * props.builder.tranches
|
||||
setPoints(null, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const timeUnits = [['minutes', 60], ['hours', 3600], ['days', 86400]]
|
||||
function defaultTimeUnit() {
|
||||
let i=1
|
||||
while( i < timeUnits.length && props.builder.interval >= timeUnits[i][1] )
|
||||
i++
|
||||
console.log('defaultTimeUnit', props.builder.interval, i-1)
|
||||
return i-1
|
||||
}
|
||||
const _timeUnitIndex = ref(defaultTimeUnit());
|
||||
const timeUnitIndex = computed({
|
||||
get() {return _timeUnitIndex.value},
|
||||
set(v) {_timeUnitIndex.value = v % timeUnits.length}
|
||||
})
|
||||
const timeUnitsStr = computed(()=>timeUnits[timeUnitIndex.value][0])
|
||||
|
||||
const absTimeA = computed({
|
||||
get() { return _timeEndpoints.value[0] },
|
||||
function toggleTimeUnits() {
|
||||
timeUnitIndex.value += 1
|
||||
}
|
||||
|
||||
const startTime = computed({
|
||||
get() { return props.builder.relative ? s.clock + props.builder.startTime : props.builder.startTime },
|
||||
set(v) {emitUpdate({startTime: v})}
|
||||
})
|
||||
|
||||
const endTime = computed({
|
||||
get() { return startTime.value + props.builder.interval * props.builder.tranches },
|
||||
set(v) {
|
||||
if (v!==null)
|
||||
v = Number(v)
|
||||
updateA(v)
|
||||
}
|
||||
})
|
||||
const absTimeB = computed({
|
||||
get() { return _timeEndpoints.value[1] },
|
||||
set(v) {
|
||||
if (v!==null)
|
||||
v = Number(v)
|
||||
updateB(v)
|
||||
emitUpdate({endTime: v, interval: Math.abs(v - startTime.value)})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const _timeEndpoints = ref([props.builder.timeA, props.builder.timeB])
|
||||
const timeEndpoints = computed({
|
||||
get() { return _timeEndpoints.value},
|
||||
set(v) {
|
||||
const [a, b] = v
|
||||
update(a,b)
|
||||
const barStart = computed(()=>ohlcStart(startTime.value, props.builder.interval))
|
||||
const barEnd = computed(()=>ohlcStart(endTime.value, props.builder.interval))
|
||||
|
||||
function emitUpdatedPoints(a, b) {
|
||||
const updates = {}
|
||||
if (a.time !== barStart.value)
|
||||
updates.startTime = a.time
|
||||
// only set the end if it was moved relative to the start
|
||||
if (b.time - a.time === barEnd.value - barStart.value)
|
||||
// the end has moved exactly the same amount as the start. add a relative amount.
|
||||
updates.endTime = a.time + (endTime.value - startTime.value)
|
||||
else
|
||||
updates.endTime = b.time
|
||||
updates.interval = (b.time - a.time) / props.builder.tranches
|
||||
emitUpdate(updates)
|
||||
}
|
||||
|
||||
function setPoints(points=null, shapeCorrectionNeeded=false) {
|
||||
if (points === null)
|
||||
points = [{time:props.builder.startTime}, {time:props.builder.endTime}]
|
||||
let [a, b] = points
|
||||
const period = co.intervalSecs;
|
||||
|
||||
if (b.time < a.time) {
|
||||
[a, b] = [b, a]
|
||||
shapeCorrectionNeeded = true
|
||||
} else if (b.time === a.time) {
|
||||
b.time = a.time + period
|
||||
shapeCorrectionNeeded = true
|
||||
}
|
||||
|
||||
const curBarStart = ohlcStart(s.clock, period);
|
||||
if (a.time < curBarStart) {
|
||||
const width = b.time - a.time;
|
||||
a.time = s.clock
|
||||
b.time = a.time + width
|
||||
shapeCorrectionNeeded = true
|
||||
}
|
||||
emitUpdatedPoints(a, b);
|
||||
if (shapeCorrectionNeeded) {
|
||||
const [sa, sb] = shape.getPoints()
|
||||
const aTime = ohlcStart(a.time, period);
|
||||
const bTime = ohlcStart(b.time, period);
|
||||
if (sa.time !== aTime || sb.time !== bTime) {
|
||||
const aa = {time: aTime}
|
||||
const bb = {time: bTime}
|
||||
shape.setPoints([aa, bb])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const callbacks = {
|
||||
onDrag(shapeId, shape, points) {
|
||||
let [a, b] = points
|
||||
if (b.time < a.time)
|
||||
[a, b] = [b, a]
|
||||
emitUpdatedPoints(a, b)
|
||||
},
|
||||
onPoints(shapeId, shape, points) {
|
||||
setPoints(points, shape);
|
||||
},
|
||||
}
|
||||
|
||||
function emitUpdate(changes) {
|
||||
for (const [k, v] of Object.entries(changes))
|
||||
props.builder[k] = v
|
||||
}
|
||||
|
||||
const text = computed(()=>{
|
||||
const o = props.order
|
||||
return allocationText(o.buy, 1, o.amount, co.selectedSymbol.base.s,
|
||||
o.amountIsTokenA ? null : co.selectedSymbol.quote.s, parts.value, '\n')
|
||||
})
|
||||
|
||||
let shapeId = createShape(ShapeType.DateRange, [{time:barStart.value}, {time:barEnd.value}], {}, callbacks)
|
||||
let shape = widget.activeChart().getShapeById(shapeId)
|
||||
function setProperties() {
|
||||
const color = computeDefaultColor()
|
||||
shape.setProperties({
|
||||
extendTop: true,
|
||||
extendBottom: true,
|
||||
linecolor: color,
|
||||
backgroundColor: new Color(color).alpha(0.2).toString(),
|
||||
backgroundTransparency: 60,
|
||||
customText: {
|
||||
text: text.value,
|
||||
visible: true,
|
||||
color: color,
|
||||
},
|
||||
})
|
||||
}
|
||||
setProperties()
|
||||
|
||||
watchEffect(setProperties)
|
||||
|
||||
watchEffect(()=>{
|
||||
const curBarStart = ohlcStart(s.clock, co.intervalSecs);
|
||||
if (curBarStart > barStart.value && !dragging) { // check dragging late to ensure reactivity on bar start
|
||||
const delta = curBarStart - props.builder.startTime
|
||||
setPoints([{time: props.builder.startTime + delta}, {time: props.builder.endTime + delta}])
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function updateA(a) {
|
||||
update(a, _timeEndpoints.value[1], true)
|
||||
function deleteShapes() {
|
||||
deleteShapeId(shapeId)
|
||||
}
|
||||
|
||||
|
||||
function updateB(b) {
|
||||
update(_timeEndpoints.value[0], b, false)
|
||||
}
|
||||
|
||||
|
||||
function update(a, b, updatingA) {
|
||||
if (updatingA) {
|
||||
const minB = a + minWidth.value
|
||||
if (b < minB)
|
||||
b = minB
|
||||
}
|
||||
else {
|
||||
const maxA = b - minWidth.value
|
||||
if (a > maxA)
|
||||
a = maxA
|
||||
}
|
||||
_timeEndpoints.value = [a, b]
|
||||
const newBuilder = {...props.builder}
|
||||
newBuilder.timeA = a
|
||||
newBuilder.timeB = b
|
||||
emit('update:builder', newBuilder)
|
||||
}
|
||||
|
||||
const flipped = computed(()=>{
|
||||
const a = props.builder.timeA
|
||||
const b = props.builder.timeB
|
||||
return a !== null && b !== null && a > b
|
||||
})
|
||||
|
||||
function buildTranches() {
|
||||
const order = props.order
|
||||
const builder = props.builder
|
||||
const tranches = []
|
||||
|
||||
console.log('buildTranches', builder, order, tranches)
|
||||
const ts = times.value
|
||||
const ets = endTimes.value
|
||||
const ws = weights.value
|
||||
for(let i=0; i<ts.length; i++) {
|
||||
const t = newTranche({
|
||||
fraction: ws[i] * MAX_FRACTION,
|
||||
startTime: ts[i],
|
||||
endTime: ets[i],
|
||||
slippage: builder.slippage,
|
||||
})
|
||||
tranches.push(t)
|
||||
}
|
||||
return tranches
|
||||
}
|
||||
|
||||
|
||||
function getModelValue(model) {
|
||||
if(!model) {
|
||||
console.log('getModelValue', model)
|
||||
return null
|
||||
}
|
||||
return model.time
|
||||
}
|
||||
|
||||
function getPointsValue(points) {
|
||||
return points[0].price
|
||||
}
|
||||
|
||||
function setModelValue(model, value) {
|
||||
// console.log('DCA set model value', model, value)
|
||||
// const v = value === null ? null : props.builder.relative ? s.clock + Math.round(value) : Math.round(value)
|
||||
const v = value === null ? null : Math.round(value)
|
||||
if (model.time !== v) {
|
||||
// console.log('DCA do set time', v)
|
||||
model.time = v
|
||||
}
|
||||
}
|
||||
|
||||
function setValues(values) {
|
||||
times.value = values.map((t)=>Math.round(t))
|
||||
}
|
||||
|
||||
function setWeights(ws) { weights.value = ws }
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -248,5 +303,11 @@ td.weight {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.parts {
|
||||
width: 10em;
|
||||
}
|
||||
.interval {
|
||||
width: 22em;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
255
src/components/chart/DateBuilder.vue
Normal file
@@ -0,0 +1,255 @@
|
||||
<template class="d-flex align-content-center flex-column" style="height: 100%; width: 100%;">
|
||||
<rung-builder name='Dates' :order="order" :builder="builder" v-model="timeEndpoints"
|
||||
:shape="VLine"
|
||||
:mode="1" :flip="flipped" :orientation="0"
|
||||
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
||||
:get-points-value="getPointsValue"
|
||||
:set-values="setValues" :set-weights="setWeights"
|
||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||
|
||||
<!--
|
||||
<v-list style="background-color: inherit">
|
||||
<v-list-item v-for="t in absoluteTimes">{{t}}</v-list-item>
|
||||
</v-list>
|
||||
-->
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<absolute-time-entry v-model="absTimeA"/>
|
||||
</td>
|
||||
<td class="weight">{{ weights.length ? allocationTexts[0] : '' }}</td>
|
||||
</tr>
|
||||
<tr v-if="weights.length>2" v-for="i in weights.length-2" class="ml-5"> <!-- vue uses 1-based loops -->
|
||||
<td class="d-flex justify-end pr-3">{{ dateStrings[i] }}</td>
|
||||
<td class="weight">{{ allocationTexts[i] }}</td>
|
||||
</tr>
|
||||
<tr v-if="weights.length>1">
|
||||
<td>
|
||||
<absolute-time-entry v-model="absTimeB"/>
|
||||
</td>
|
||||
<td class="weight">{{ allocationTexts[weights.length-1] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</rung-builder>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {builderDefaults, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {allocationText, VLine} from "@/charts/shape.js";
|
||||
import {sideColor} from "@/misc.js";
|
||||
import {useOrderStore, usePrefStore, useStore} from "@/store/store.js";
|
||||
import {DEFAULT_SLIPPAGE, DISTANT_FUTURE, MAX_FRACTION, MIN_EXECUTION_TIME, newTranche} from "@/blockchain/orderlib.js";
|
||||
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
||||
import {computed, ref, watchEffect} from "vue";
|
||||
import {chart, dragging} from "@/charts/chart.js";
|
||||
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
||||
import {DateTime} from "luxon";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
const co = useChartOrderStore()
|
||||
const prefs = usePrefStore()
|
||||
const props = defineProps(['order', 'builder'])
|
||||
const emit = defineEmits(['update:builder'])
|
||||
|
||||
const minWidth = computed(()=>co.intervalSecs)
|
||||
const stdWidth = computed(()=>10 * minWidth.value)
|
||||
|
||||
function computeDefaultColor() {
|
||||
const index = props.order.builders.indexOf(props.builder)
|
||||
return sideColor(props.order.buy, index)
|
||||
}
|
||||
|
||||
const defaultColor = computeDefaultColor()
|
||||
|
||||
builderDefaults(props.builder, {
|
||||
timeA: s.clock, // todo relative 0
|
||||
timeB: null,
|
||||
// relative: true, // todo
|
||||
relative: false,
|
||||
slippage: DEFAULT_SLIPPAGE,
|
||||
rungs: 1,
|
||||
balance: 0,
|
||||
color: defaultColor,
|
||||
buy: true,
|
||||
})
|
||||
|
||||
const times = ref([])
|
||||
const weights = ref([])
|
||||
|
||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
|
||||
|
||||
const endTimes = computed(()=>{
|
||||
const ts = times.value
|
||||
const window = Math.max(MIN_EXECUTION_TIME, Math.floor((ts[ts.length-1]-ts[0])/props.builder.rungs))
|
||||
return ts.map((t)=>t+window)
|
||||
})
|
||||
const absoluteTimes = computed(()=>{
|
||||
// console.log('absoluteTimes', props.builder.relative, times.value)
|
||||
// if (!props.builder.relative)
|
||||
return times.value
|
||||
// const now = s.clock
|
||||
// return times.value.map((t)=>now+t)
|
||||
})
|
||||
|
||||
const dateStrings = computed(()=>absoluteTimes.value.map((t)=>{
|
||||
const n = DateTime.fromSeconds(t).setZone(prefs.timezone)
|
||||
const y = n.toLocaleString({year:'numeric'})
|
||||
const m = n.toLocaleString({month:'long'})
|
||||
const d = n.toLocaleString({day:'numeric'})
|
||||
const h = n.toLocaleString({hour:'numeric', minute:'numeric'})
|
||||
return `${y} ${m} ${d} ${h}`
|
||||
}))
|
||||
|
||||
watchEffect(()=>{
|
||||
// auto scroll
|
||||
if (!dragging && absoluteTimes.value.length) {
|
||||
const endTime = absoluteTimes.value[absoluteTimes.value.length-1]
|
||||
const range = chart.getVisibleRange()
|
||||
const width = range.to - range.from
|
||||
const now = s.clock
|
||||
const extra = (Math.max(0,endTime - now) + stdWidth.value/2) / width
|
||||
// console.log('visrange', range, width, absV)
|
||||
if (range.to < endTime) {
|
||||
// console.log('scrolling')
|
||||
chart.setVisibleRange({from: now - width, to: now}, {
|
||||
percentRightMargin: Math.round(100 * extra),
|
||||
applyDefaultRightMargin: false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const absTimeA = computed({
|
||||
get() { return _timeEndpoints.value[0] },
|
||||
set(v) {
|
||||
if (v!==null)
|
||||
v = Number(v)
|
||||
updateA(v)
|
||||
}
|
||||
})
|
||||
const absTimeB = computed({
|
||||
get() { return _timeEndpoints.value[1] },
|
||||
set(v) {
|
||||
if (v!==null)
|
||||
v = Number(v)
|
||||
updateB(v)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const _timeEndpoints = ref([props.builder.timeA, props.builder.timeB])
|
||||
const timeEndpoints = computed({
|
||||
get() { return _timeEndpoints.value},
|
||||
set(v) {
|
||||
const [a, b] = v
|
||||
update(a,b, true, true)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function updateA(a) {
|
||||
update(a, _timeEndpoints.value[1], true, false)
|
||||
}
|
||||
|
||||
|
||||
function updateB(b) {
|
||||
update(_timeEndpoints.value[0], b, false, true)
|
||||
}
|
||||
|
||||
|
||||
function update(a, b, updateA, updateB) {
|
||||
if (updateA) {
|
||||
const minB = a + minWidth.value
|
||||
if (b < minB)
|
||||
b = minB
|
||||
}
|
||||
if (updateB) {
|
||||
const maxA = b - minWidth.value
|
||||
if (a > maxA)
|
||||
a = maxA
|
||||
}
|
||||
_timeEndpoints.value = [a, b]
|
||||
props.builder.timeA = a
|
||||
props.builder.timeB = b
|
||||
}
|
||||
|
||||
const flipped = computed(()=>{
|
||||
const a = props.builder.timeA
|
||||
const b = props.builder.timeB
|
||||
return a !== null && b !== null && a > b
|
||||
})
|
||||
|
||||
function buildTranches() {
|
||||
const order = props.order
|
||||
const builder = props.builder
|
||||
const tranches = []
|
||||
const warnings = []
|
||||
|
||||
console.log('buildTranches', builder, order, tranches)
|
||||
const ts = times.value
|
||||
const ets = endTimes.value
|
||||
const ws = weights.value
|
||||
console.log('buildTranches times ends weights', ts, ets, ws)
|
||||
for(let i=0; i<ts.length; i++) {
|
||||
const endTime = Math.max(ets[i],ts[i]+60);
|
||||
console.log('time check', endTime, s.clock)
|
||||
if (endTime <= s.clock)
|
||||
warnings.push(`Tranche already expired at ${new Date(endTime*1000)}`)
|
||||
const t = newTranche({
|
||||
marketOrder: true,
|
||||
slippage: builder.slippage,
|
||||
fraction: ws[i] * MAX_FRACTION,
|
||||
startTime: ts[i],
|
||||
endTime, // always give at least 60 seconds of window to execute
|
||||
})
|
||||
tranches.push(t)
|
||||
}
|
||||
return {tranches, warnings}
|
||||
}
|
||||
|
||||
|
||||
function getModelValue(model) {
|
||||
if(!model) {
|
||||
console.log('getModelValue', model)
|
||||
return null
|
||||
}
|
||||
return model.time
|
||||
}
|
||||
|
||||
function getPointsValue(points) {
|
||||
return points[0].price
|
||||
}
|
||||
|
||||
function setModelValue(model, value) {
|
||||
// console.log('DCA set model value', model, value)
|
||||
// const v = value === null ? null : props.builder.relative ? s.clock + Math.round(value) : Math.round(value)
|
||||
const v = value === null ? null : Math.round(value)
|
||||
if (model.time !== v) {
|
||||
// console.log('DCA do set time', v)
|
||||
model.time = v
|
||||
}
|
||||
}
|
||||
|
||||
function setValues(values) {
|
||||
times.value = values.map((t)=>Math.round(t))
|
||||
}
|
||||
|
||||
function setWeights(ws) { weights.value = ws }
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
td.weight {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:set-values="setLines" :set-weights="setWeights"
|
||||
:set-shapes="setShapes"
|
||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||
<table>
|
||||
<table v-if="!co.drawing">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> </td>
|
||||
@@ -32,7 +32,7 @@
|
||||
<v-text-field type="number" v-model="price1A" min="0"
|
||||
density="compact" hide-details variant="outlined"
|
||||
class="mx-1 my-2 price"
|
||||
:color="color" :base-color="color"
|
||||
:color="color"
|
||||
label="Price"
|
||||
/>
|
||||
</td>
|
||||
@@ -49,7 +49,7 @@
|
||||
<v-text-field type="number" v-model="price1B" min="0"
|
||||
density="compact" hide-details variant="outlined"
|
||||
class="mx-1 my-2 price"
|
||||
:color="color" :base-color="color"
|
||||
:color="color"
|
||||
label="Price"
|
||||
/>
|
||||
</td>
|
||||
@@ -68,7 +68,7 @@
|
||||
<v-text-field type="number" v-model="price2A" min="0"
|
||||
density="compact" hide-details variant="outlined"
|
||||
class="mx-1 my-2 price"
|
||||
:color="color" :base-color="color"
|
||||
:color="color"
|
||||
label="Price"
|
||||
/>
|
||||
</td>
|
||||
@@ -82,26 +82,34 @@
|
||||
<v-text-field type="number" v-model="price2B" min="0"
|
||||
density="compact" hide-details variant="outlined"
|
||||
class="mx-1 my-2 price"
|
||||
:color="color" :base-color="color"
|
||||
:color="color"
|
||||
label="Price"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<one-time-hint name="click-chart" activator="#tv-widget" location="center"
|
||||
:when="builder.lineA===null && !co.drew" text="Click the chart!"
|
||||
:on-complete="()=>track('click-chart')"
|
||||
/>
|
||||
</rung-builder>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {applyLinePoints, builderDefaults, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {sideColor} from "@/misc.js";
|
||||
import {sideColor, toPrecisionOrNull} from "@/misc.js";
|
||||
import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import {allocationText, DLine} from "@/charts/shape.js";
|
||||
import {vectorEquals, vectorInterpolate} from "@/vector.js";
|
||||
import AbsoluteTimeEntry from "@/components/AbsoluteTimeEntry.vue";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import OneTimeHint from "@/components/OneTimeHint.vue";
|
||||
import {track} from "@/track.js";
|
||||
|
||||
const s = useStore()
|
||||
const co = useChartOrderStore()
|
||||
const props = defineProps(['order', 'builder'])
|
||||
const emit = defineEmits(['update:builder'])
|
||||
@@ -112,7 +120,6 @@ function computeDefaultColor() {
|
||||
}
|
||||
|
||||
const defaultColor = computeDefaultColor()
|
||||
|
||||
const color = computed(()=>props.builder.color ? props.builder.color : defaultColor)
|
||||
|
||||
// Fields must be defined in order to be reactive
|
||||
@@ -122,9 +129,10 @@ builderDefaults(props.builder, {
|
||||
extendLeft: false,
|
||||
extendRight: true,
|
||||
rungs: 1,
|
||||
skew: 0,
|
||||
balance: 0,
|
||||
breakout: false,
|
||||
color: defaultColor,
|
||||
buy: true,
|
||||
})
|
||||
|
||||
|
||||
@@ -132,6 +140,7 @@ function buildTranches() {
|
||||
const order = props.order
|
||||
const builder = props.builder
|
||||
const tranches = []
|
||||
const warnings = []
|
||||
|
||||
console.log('buildTranches', builder, order, _endpoints.value)
|
||||
const la = _endpoints.value[0] // use the flatline format which is a vector of length 4, useful for vectorInterpolate()
|
||||
@@ -149,6 +158,9 @@ function buildTranches() {
|
||||
t.startTime = reversed ? line[2] : line[0]
|
||||
if (reversed ? !el : !er)
|
||||
t.endTime = reversed ? line[0] : line[2]
|
||||
if (t.endTime <= s.clock)
|
||||
warnings.push(`Tranche already expired at ${new Date(t.endTime*1000)}`)
|
||||
|
||||
// console.log('tranche start/end',
|
||||
// t.startTime === DISTANT_PAST ? 'PAST' : t.startTime,
|
||||
// t.endTime === DISTANT_FUTURE ? 'FUTURE' : t.endTime)
|
||||
@@ -157,7 +169,7 @@ function buildTranches() {
|
||||
}
|
||||
// if( flipped.value )
|
||||
// tranches.reverse()
|
||||
return tranches
|
||||
return {tranches, warnings}
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +210,7 @@ const time1A = computed({
|
||||
})
|
||||
|
||||
const price1A = computed({
|
||||
get() { return _endpoints.value[0] === null ? null : _endpoints.value[0][1] },
|
||||
get() { return toPrecisionOrNull(_endpoints.value[0] === null ? null : _endpoints.value[0][1], 6) },
|
||||
set(v) {
|
||||
const flatline0 = _endpoints.value[0];
|
||||
update(
|
||||
@@ -220,7 +232,7 @@ const time1B = computed({
|
||||
})
|
||||
|
||||
const price1B = computed({
|
||||
get() { return _endpoints.value[0] === null ? null : _endpoints.value[0][3] },
|
||||
get() { return toPrecisionOrNull(_endpoints.value[0] === null ? null : _endpoints.value[0][3], 6) },
|
||||
set(v) {
|
||||
const flatline0 = _endpoints.value[0];
|
||||
update(
|
||||
@@ -242,7 +254,7 @@ const time2A = computed({
|
||||
})
|
||||
|
||||
const price2A = computed({
|
||||
get() { return _endpoints.value[1] === null ? null : _endpoints.value[1][1] },
|
||||
get() { return toPrecisionOrNull(_endpoints.value[1] === null ? null : _endpoints.value[1][1], 6) },
|
||||
set(v) {
|
||||
const flatline = _endpoints.value[1];
|
||||
update(
|
||||
@@ -264,7 +276,7 @@ const time2B = computed({
|
||||
})
|
||||
|
||||
const price2B = computed({
|
||||
get() { return _endpoints.value[1] === null ? null : _endpoints.value[1][3] },
|
||||
get() { return toPrecisionOrNull(_endpoints.value[1] === null ? null : _endpoints.value[1][3], 6) },
|
||||
set(v) {
|
||||
const flatline = _endpoints.value[1];
|
||||
update(
|
||||
@@ -277,10 +289,8 @@ const price2B = computed({
|
||||
function update(a, b) { // a and b are lines of two points
|
||||
if (!vectorEquals(props.builder.lineA, a) || !vectorEquals(props.builder.lineB, b)) {
|
||||
_endpoints.value = [flattenLine(a), flattenLine(b)]
|
||||
const newBuilder = {...props.builder}
|
||||
newBuilder.lineA = a
|
||||
newBuilder.lineB = b
|
||||
emit('update:builder', newBuilder)
|
||||
props.builder.lineA = a
|
||||
props.builder.lineB = b
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +359,7 @@ function setWeights(ws) {
|
||||
|
||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||
|
||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(w, w*props.order.amount, amountSymbol.value)))
|
||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
|
||||
|
||||
const stdWidth = computed(()=>[0, co.meanRange, 0, co.meanRange])
|
||||
|
||||
@@ -399,12 +409,13 @@ function dirtyLine(a, b) {
|
||||
return result
|
||||
}
|
||||
|
||||
const name = computed(()=>(props.builder.breakout?'Breakout':'Limit')+' Diagonal'+(weights.value.length>1?'s':''))
|
||||
const name = computed(()=>props.builder.breakout?(props.order.buy?'Breakout':'Breakdown'):'Limit')
|
||||
|
||||
const description = computed(()=>{
|
||||
const buy = props.order.buy
|
||||
const above = buy === props.builder.breakout
|
||||
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'
|
||||
const plural = props.builder.rungs > 1 ? 's' : ''
|
||||
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'+(plural?'s':'')
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<rung-builder :name="(builder.breakout?'Breakout':'Limit')+(prices.length>1?' Ladder':'')"
|
||||
<rung-builder :name="(builder.breakout?(order.buy?'Breakout':'Breakdown'):'Limit')+(builder.rungs>1?' Ladder':'')"
|
||||
:description="description"
|
||||
:order="order" :builder="builder"
|
||||
v-model="priceEndpoints" :mode="0" :flip="flipped"
|
||||
@@ -7,7 +7,7 @@
|
||||
:get-model-value="getModelValue" :set-model-value="setModelValue"
|
||||
:set-values="setPrices" :set-weights="setWeights"
|
||||
:std-width="stdWidth" :build-tranches="buildTranches">
|
||||
<table>
|
||||
<table class="rb">
|
||||
<tbody>
|
||||
<template v-if="prices.length>1">
|
||||
<tr>
|
||||
@@ -15,14 +15,13 @@
|
||||
<v-text-field type="number" v-model="higherPrice" min="0"
|
||||
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
||||
label="Price"
|
||||
:color="color" :base-color="color"
|
||||
style="flex: 6em"
|
||||
/>
|
||||
</td>
|
||||
<td class="weight">{{ allocationTexts[higherIndex] }}</td>
|
||||
<td class="weight" style="vertical-align: bottom">{{ allocationTexts[higherIndex] }}</td>
|
||||
</tr>
|
||||
<tr v-for="i in innerIndexes" class="ml-5">
|
||||
<td class="pl-5">{{ prices[i] }}</td>
|
||||
<td class="pl-5">{{ toPrecision(prices[i],6) }}</td>
|
||||
<td class="weight">{{ allocationTexts[i] }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
@@ -31,14 +30,17 @@
|
||||
<v-text-field type="number" v-model="lowerPrice" min="0"
|
||||
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
||||
label="Price"
|
||||
:color="color" :base-color="color"
|
||||
style="flex: 6em"
|
||||
/>
|
||||
</td>
|
||||
<td class="weight">{{ weights.length ? allocationTexts[lowerIndex] : '' }}</td>
|
||||
<td class="weight">{{ weights.length > 1 ? allocationTexts[lowerIndex] : '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<one-time-hint name="click-chart" activator="#tv-widget" location="center"
|
||||
:when="priceA===null" text="Click the chart!"
|
||||
:on-complete="()=>track('click-chart')"
|
||||
/>
|
||||
</rung-builder>
|
||||
</template>
|
||||
|
||||
@@ -50,6 +52,9 @@ import {MAX_FRACTION, newTranche} from "@/blockchain/orderlib.js";
|
||||
import RungBuilder from "@/components/chart/RungBuilder.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import {allocationText, HLine} from "@/charts/shape.js";
|
||||
import OneTimeHint from "@/components/OneTimeHint.vue";
|
||||
import {track} from "@/track.js";
|
||||
import {toPrecision, toPrecisionOrNull} from "@/misc.js";
|
||||
|
||||
const s = useStore()
|
||||
const os = useOrderStore()
|
||||
@@ -71,15 +76,17 @@ builderDefaults(props.builder, {
|
||||
priceA: null,
|
||||
priceB: null,
|
||||
rungs: 1,
|
||||
skew: 0,
|
||||
balance: 0,
|
||||
breakout: false,
|
||||
color: defaultColor,
|
||||
buy: true,
|
||||
})
|
||||
|
||||
function buildTranches() {
|
||||
const order = props.order
|
||||
const builder = props.builder
|
||||
const tranches = []
|
||||
const warnings = []
|
||||
|
||||
console.log('buildTranches', builder, order, tranches)
|
||||
const ps = prices.value
|
||||
@@ -88,17 +95,16 @@ function buildTranches() {
|
||||
let p = ps[i]
|
||||
const w = ws[i]
|
||||
const t = newTranche({
|
||||
// todo start/end
|
||||
fraction: w * MAX_FRACTION,
|
||||
})
|
||||
const symbol = co.selectedSymbol
|
||||
console.log('symbol', symbol, p)
|
||||
// console.log('symbol', symbol, p)
|
||||
applyLinePoint(t, symbol, order.buy, p, builder.breakout)
|
||||
tranches.push(t)
|
||||
}
|
||||
if (!flipped.value)
|
||||
tranches.reverse()
|
||||
return tranches
|
||||
return {tranches, warnings}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,10 +139,8 @@ const priceEndpoints = computed({
|
||||
|
||||
function update(a, b) {
|
||||
_priceEndpoints.value = [a, b]
|
||||
const newBuilder = {...props.builder}
|
||||
newBuilder.priceA = a
|
||||
newBuilder.priceB = b
|
||||
emit('update:builder', newBuilder)
|
||||
props.builder.priceA = a
|
||||
props.builder.priceB = b
|
||||
}
|
||||
|
||||
const flipped = computed(()=>{
|
||||
@@ -146,7 +150,7 @@ const flipped = computed(()=>{
|
||||
})
|
||||
|
||||
const higherPrice = computed({
|
||||
get() { return flipped.value ? priceA.value : priceB.value },
|
||||
get() { return toPrecisionOrNull(flipped.value ? priceA.value : priceB.value, 6) },
|
||||
set(v) {
|
||||
if (flipped.value)
|
||||
priceA.value = v
|
||||
@@ -167,9 +171,7 @@ const innerIndexes = computed(()=>{
|
||||
})
|
||||
|
||||
const lowerPrice = computed({
|
||||
get() {
|
||||
return !flipped.value ? priceA.value : priceB.value
|
||||
},
|
||||
get() {return toPrecisionOrNull(!flipped.value ? priceA.value : priceB.value, 6)},
|
||||
set(v) {
|
||||
if (!flipped.value)
|
||||
priceA.value = v
|
||||
@@ -195,13 +197,14 @@ function setPrices(ps) {prices.value = ps}
|
||||
function setWeights(ws) { weights.value = ws }
|
||||
|
||||
const amountSymbol = computed(()=>props.order.amountIsTokenA ? co.selectedSymbol.base.s : co.selectedSymbol.quote.s )
|
||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(w, w*props.order.amount, amountSymbol.value)))
|
||||
const allocationTexts = computed(()=>weights.value.map((w)=>allocationText(props.order.buy, w, w * props.order.amount, co.selectedSymbol.base.s, amountSymbol.value)))
|
||||
const color = computed(()=>props.builder.color ? props.builder.color : defaultColor)
|
||||
const stdWidth = computed(()=>co.meanRange)
|
||||
const description = computed(()=>{
|
||||
const buy = props.order.buy
|
||||
const above = buy === props.builder.breakout
|
||||
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'
|
||||
const plural = props.builder.rungs > 1 ? 's' : ''
|
||||
return (buy?'Buy ':'Sell ')+(above?'above':'below')+' the line'+plural
|
||||
})
|
||||
|
||||
function getModelValue(model) {
|
||||
@@ -227,5 +230,16 @@ td.weight {
|
||||
padding-right: 0.5em;
|
||||
text-align: right;
|
||||
}
|
||||
table.rb {
|
||||
padding: 0;
|
||||
border-spacing: 0;
|
||||
tbody {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {builderDefaults, builderFuncs, DEFAULT_SLIPPAGE, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {builderDefaults, builderFuncs, useChartOrderStore} from "@/orderbuild.js";
|
||||
import {computed, onMounted, onUnmounted} from "vue";
|
||||
import {newTranche} from "@/blockchain/orderlib.js";
|
||||
import {DEFAULT_SLIPPAGE, newTranche} from "@/blockchain/orderlib.js";
|
||||
|
||||
const co = useChartOrderStore()
|
||||
const props = defineProps(['order', 'builder'])
|
||||
@@ -36,8 +36,17 @@ const slippage = computed({
|
||||
set(v) {props.builder.slippage=v/100; emit('update:builder', props.builder)}
|
||||
})
|
||||
|
||||
const MIN_SLIPPAGE = 0.01;
|
||||
|
||||
function buildTranches() {
|
||||
return [newTranche({slippage: slippage.value/100})]
|
||||
let warnings = []
|
||||
if (slippage.value < MIN_SLIPPAGE)
|
||||
warnings.push('Slippage will be set to the minimum of 0.01%')
|
||||
const slip = Math.max(slippage.value, MIN_SLIPPAGE)
|
||||
return {
|
||||
tranches: [newTranche({marketOrder: true, slippage: slip / 100})],
|
||||
warnings,
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => builderFuncs[props.builder.id] = buildTranches)
|
||||
|
||||
135
src/components/chart/OrderAmount.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="d-flex flex-row align-start">
|
||||
<!--
|
||||
<v-btn v-if="!multiplier"
|
||||
variant="outlined" :color="color"
|
||||
:text="(props.order.buy ? 'Buy ' : 'Sell ') + co.selectedSymbol.base.s"
|
||||
@click="props.order.buy=!props.order.buy"
|
||||
class="ml-3 mt-2 mr-2"
|
||||
size="2.5em"
|
||||
style="width: 9em"
|
||||
/>
|
||||
-->
|
||||
<div class="d-flex flex-column align-center">
|
||||
<v-switch v-if="!multiplier" v-model="switchModel" :color="sideColor" :base-color="sideColor" density="compact"
|
||||
class="my-0 mx-3 clickable">
|
||||
<template #prepend>
|
||||
<span :style="order.buy?{color:theme.colors.success}:{}" @click="order.buy=true" class="bs-button buy">Buy<br/>{{co.selectedSymbol.base.s}}</span>
|
||||
</template>
|
||||
<template #append>
|
||||
<span :style="!order.buy?{color:theme.colors.error}:{}" @click="order.buy=false" class="bs-button sell">Sell<br/>{{co.selectedSymbol.base.s}}</span>
|
||||
</template>
|
||||
</v-switch>
|
||||
</div>
|
||||
<v-text-field type="number" inputmode="numeric" pattern="[0-9]*\.?[0-9]*" v-model="amount" variant="outlined"
|
||||
:hint="available" :persistent-hint="true"
|
||||
min="0"
|
||||
class="amount mx-3"
|
||||
style="max-width: 20em"
|
||||
:color="color"
|
||||
:label="order.amountIsTokenA ?
|
||||
(multiplier ? 'Amount each' : 'Amount') :
|
||||
((multiplier ? 'Value each in ' : 'Value in ')+co.selectedSymbol.quote.s)">
|
||||
<template #prepend-inner>
|
||||
<v-btn variant="text" text="max" class="px-0" size="small"
|
||||
:disabled="!maxAmount || order.amountIsTokenA===order.buy && !co.price" @click="setMax"/>
|
||||
</template>
|
||||
<template #append-inner>
|
||||
<v-btn :text="order.amountIsTokenA?co.baseToken.s:co.quoteToken.s+' worth'"
|
||||
variant="text" @click="toggleAmountToken" style="width: 7em"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {computed, ref} from "vue";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import Color from "color";
|
||||
import {useTheme} from "vuetify";
|
||||
|
||||
const props = defineProps(['order', 'multiplier', 'color'])
|
||||
|
||||
const s = useStore()
|
||||
const co = useChartOrderStore()
|
||||
|
||||
const theme = useTheme().current
|
||||
// console.log('theme', theme.value)
|
||||
const sideColor = computed(()=>new Color(props.order.buy?theme.value.colors.success:theme.value.colors.error).darken(0.2).string())
|
||||
const switchModel = computed({
|
||||
get() {return !props.order.buy},
|
||||
set(v) {props.order.buy=!v}
|
||||
})
|
||||
|
||||
const amount = computed({
|
||||
get() {
|
||||
let result = props.order.amount
|
||||
if (result !== null && props.multiplier)
|
||||
result /= props.multiplier
|
||||
return result
|
||||
},
|
||||
set(v) {
|
||||
if (v !== null && props.multiplier)
|
||||
v *= props.multiplier
|
||||
props.order.amount = v
|
||||
}
|
||||
})
|
||||
|
||||
const available = computed(()=>{
|
||||
const max = maxAmount.value
|
||||
return max === null ? '' : `Available: ${maxAmount.value} ${inToken.value.s}`
|
||||
})
|
||||
|
||||
const inToken = computed( ()=>props.order.buy ? co.quoteToken : co.baseToken )
|
||||
|
||||
const maxAmount = computed(()=>{
|
||||
const token = inToken.value;
|
||||
if (!token)
|
||||
return null
|
||||
const balance = s.balances[token.a]
|
||||
if( !balance )
|
||||
return null
|
||||
return balance / 10**token.d
|
||||
})
|
||||
|
||||
const lastMaxValue = ref(-1)
|
||||
|
||||
function setMax() {
|
||||
let amount = maxAmount.value
|
||||
if (amount) {
|
||||
const order = props.order
|
||||
const price = co.price
|
||||
if (order.amountIsTokenA===order.buy) {
|
||||
if (order.buy)
|
||||
amount /= price
|
||||
else
|
||||
amount *= price
|
||||
}
|
||||
amount = Number(amount.toPrecision(7))
|
||||
lastMaxValue.value = amount
|
||||
order.amount = amount
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAmountToken() {
|
||||
const order = props.order
|
||||
order.amountIsTokenA=!order.amountIsTokenA
|
||||
if (order.amount === lastMaxValue.value)
|
||||
setMax()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "src/styles/vars" as *;
|
||||
.amount {
|
||||
max-width: 30em;
|
||||
}
|
||||
.bs-button {
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
&.buy:hover {color: $green;}
|
||||
&.sell:hover {color: $red;}
|
||||
}
|
||||
</style>
|
||||
@@ -1,19 +1,50 @@
|
||||
<template>
|
||||
<builder-panel :order="order" :builder="builder" :build-tranches="buildTranches"
|
||||
:adjust-shapes="adjustShapes" :delete-shapes="deleteShapes">
|
||||
:adjust-shapes="adjustShapes" :delete-shapes="deleteShapes" :name="name">
|
||||
<div style="min-width: 4em; font-size: larger" :style="colorStyle"
|
||||
class="d-flex flex-column align-self-start ml-2">
|
||||
class="d-flex flex-column">
|
||||
<!--
|
||||
<div class="flex-row align-items-center">
|
||||
<v-btn variant="outlined" @click="props.builder.breakout=!props.builder.breakout">{{ name }}</v-btn>
|
||||
<div class="description">{{description}}</div>
|
||||
<v-btn variant="outlined" style="width: 8em"
|
||||
@click="()=>{if (props.builder.breakout!==undefined) props.builder.breakout=!props.builder.breakout}">{{ name }}</v-btn>
|
||||
<div class="description w-100 text-center">{{description}}</div>
|
||||
</div>
|
||||
-->
|
||||
<!--
|
||||
<floating-div id="rungs" v-if="endpoints[0]">
|
||||
-->
|
||||
<v-text-field type="number" v-model="rungs"
|
||||
density="compact" hide-details class="mx-1 my-2" variant="outlined"
|
||||
label="Rungs"
|
||||
:color="color" :base-color="color" min="1" :max="MAX_RUNGS"
|
||||
:color="color"
|
||||
min="1" :max="MAX_RUNGS"
|
||||
:disabled="rungsDisabled"
|
||||
style="width: 4.5em;"
|
||||
id="rungs"
|
||||
/>
|
||||
<!--
|
||||
</floating-div>
|
||||
-->
|
||||
<one-time-hint name="rungs" activator="#rungs" after="choose-builder"
|
||||
text="↓ Try increasing rungs!" location="top"
|
||||
:when="rungs===1&&endpoints[0]!==null"
|
||||
:on-complete="()=>track('rungs')"
|
||||
/>
|
||||
<v-tooltip v-if="builder.breakout!==undefined"
|
||||
:text="order.buy?'Breakout orders buy above the breakout line':'Breakdown orders sell below the breakdown line'">
|
||||
<template #activator="{ props }">
|
||||
<div v-bind="props">
|
||||
<v-switch v-model="breakout" :label="order.buy?'Breakout':'Breakdown'"
|
||||
persistent-hint :color="switchColor" :base-color="switchColor" hide-details direction="vertical"
|
||||
density="compact"/>
|
||||
<div class="mx-auto">
|
||||
<span style="font-size: .7em; vertical-align: top"
|
||||
:style="builder.breakout?{color:new Color(color).lighten(0.5).string()}:null">
|
||||
{{ description }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
|
||||
<slot/>
|
||||
@@ -22,16 +53,31 @@
|
||||
<v-icon icon="mdi-chat-alert-outline" color="grey" class="mr-1"/>
|
||||
Click the chart!
|
||||
</div>
|
||||
<div v-if="rungs>1" class="mx-2 d-flex align-center">
|
||||
<v-slider v-if="rungs>1" :direction="orientation?'vertical':'horizontal'" min="-100" max="100" v-model="skew100"
|
||||
class="no-slider-bg ml-2 mr-4" hide-details/>
|
||||
<v-text-field type="number" v-model="skew100" min="-100" max="100"
|
||||
density="compact" hide-details variant="outlined" label="Skew" step="5"
|
||||
:color="color" :base-color="color" class="skew">
|
||||
<template v-slot:prepend>
|
||||
<v-btn icon="mdi-scale-balance" variant="plain" @click="builder.skew=0" :color="color"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
<div v-if="rungs>1" class="mx-2 d-flex justify-start">
|
||||
<div class="d-flex align-center mt-2">
|
||||
<!--
|
||||
<floating-div id="slider" v-if="endpoints[0]">
|
||||
-->
|
||||
<div id="balance-slider">
|
||||
<v-slider v-if="rungs>1" :direction="orientation?'vertical':'horizontal'" min="-100" max="100" v-model="balance100"
|
||||
class="no-slider-bg ml-2 mr-4" hide-details/>
|
||||
</div>
|
||||
<!--
|
||||
</floating-div>
|
||||
-->
|
||||
<one-time-hint name="balance-slider" activator="#balance-slider" after="rungs"
|
||||
text="↓ Slide the amount balance ↓" location="top"
|
||||
:when="balance100===0"
|
||||
:on-complete="()=>track('balance-slider')"
|
||||
/>
|
||||
<v-text-field type="number" v-model="balance100" min="-100" max="100"
|
||||
density="compact" hide-details variant="outlined" label="Balance" step="5"
|
||||
class="balance">
|
||||
<template v-slot:prepend>
|
||||
<v-btn icon="mdi-scale-balance" variant="plain" @click="builder.balance=0"/>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
</builder-panel>
|
||||
</template>
|
||||
@@ -46,13 +92,19 @@ import {cancelDrawing} from "@/charts/chart.js";
|
||||
import {
|
||||
devectorize,
|
||||
vectorAdd,
|
||||
vectorDiv, vectorEquals,
|
||||
vectorDiv,
|
||||
vectorEquals,
|
||||
vectorIsNull,
|
||||
vectorIsZero,
|
||||
vectorize,
|
||||
vectorMul, vectorNeg,
|
||||
vectorMul,
|
||||
vectorNeg,
|
||||
vectorSub
|
||||
} from "@/vector.js";
|
||||
import {logicalXOR} from "@/common.js";
|
||||
import OneTimeHint from "@/components/OneTimeHint.vue";
|
||||
import {track} from "@/track.js";
|
||||
import FloatingDiv from "@/components/FloatingDiv.vue";
|
||||
|
||||
const co = useChartOrderStore()
|
||||
const endpoints = defineModel('modelValue') // 2-item list of points/values
|
||||
@@ -65,7 +117,7 @@ const props = defineProps({
|
||||
stdWidth: [Number, Array],
|
||||
shape: Function, // shape() -> Shape
|
||||
mode: { type: Number, default: 0 }, // rung addition mode: 0 = split, 1 = extend
|
||||
flip: { type: Boolean, default: false }, // if true, the skew slider is flipped upside-down
|
||||
flip: { type: Boolean, default: false }, // if true, the balance slider is flipped upside-down
|
||||
orientation: { type: Number, default: 1 }, // 0 = horizontal slider, 1 = vertical
|
||||
// values may be scalars or vector arrays
|
||||
getModelValue: Function, // getModelValue(model) -> value
|
||||
@@ -77,22 +129,35 @@ const props = defineProps({
|
||||
|
||||
const flippedSign = computed(()=>props.flip?-1:1)
|
||||
|
||||
const skew100 = computed( {
|
||||
get() {return flippedSign.value*props.builder.skew*100},
|
||||
set(v) {props.builder.skew = flippedSign.value*v/100; }
|
||||
const balance100 = computed( {
|
||||
get() {return flippedSign.value*props.builder.balance*100},
|
||||
set(v) {
|
||||
// if (v<-60) v = -60;
|
||||
props.builder.balance = flippedSign.value*v/100; }
|
||||
} )
|
||||
|
||||
// validity checks
|
||||
watchEffect(()=>{
|
||||
const rungs = props.builder.rungs
|
||||
// const prev = props.builder.valid
|
||||
props.builder.valid = rungs >= 1 && endpoints.value[0] && (rungs < 2 || endpoints.value[1])
|
||||
props.builder.valid = rungs >= 1 && endpoints.value[0] > 0 && (rungs < 2 || endpoints.value[1])
|
||||
// console.log('valid?', prev, props.builder.valid, rungs, valueA.value, valueB.value)
|
||||
})
|
||||
|
||||
let lastBuy = null
|
||||
watchEffect(()=>{
|
||||
if (props.order.buy!==lastBuy) {
|
||||
lastBuy = props.order.buy
|
||||
props.builder.color=computeDefaultColor()
|
||||
}
|
||||
})
|
||||
|
||||
const breakout = computed({
|
||||
get() {return !logicalXOR(props.builder.breakout, props.order.buy)},
|
||||
set(v) {props.builder.breakout = !logicalXOR(v, props.order.buy)},
|
||||
})
|
||||
|
||||
function setEndpoints(a, b) {
|
||||
// console.log('rb setting endpoints', devectorize(a), devectorize(b))
|
||||
endpoints.value = [devectorize(a), devectorize(b)]
|
||||
}
|
||||
|
||||
@@ -115,8 +180,7 @@ const rungs = computed({
|
||||
r = Number(r)
|
||||
const prevR = Number(props.builder.rungs)
|
||||
props.builder.rungs = r
|
||||
// console.log('set rungs', prevR, r, a, b)
|
||||
if ( r > 0 && vectorIsNull(b) ) {
|
||||
if ( prevR === 1 && r > 1 ) {
|
||||
// convert single shape to a range
|
||||
if (props.mode===0) {
|
||||
const width = vectorize(props.stdWidth)
|
||||
@@ -133,7 +197,7 @@ const rungs = computed({
|
||||
else
|
||||
throw Error(`Unknown rung mode ${props.mode}`)
|
||||
}
|
||||
else if ( r === 1 && !vectorIsNull(b) ) {
|
||||
else if ( prevR > 1 && r === 1 ) {
|
||||
// convert from a range to a single shape
|
||||
if (props.mode===0)
|
||||
a = vectorDiv(vectorAdd(a,b), 2)
|
||||
@@ -184,14 +248,10 @@ const values = computed(()=>{
|
||||
|
||||
|
||||
const weights = computed(() => {
|
||||
// const skew = props.flip ? -props.builder.skew : props.builder.skew
|
||||
// const balance = props.flip ? -props.builder.balance : props.builder.balance
|
||||
const most = 0.998
|
||||
let skew = -props.builder.skew
|
||||
if (skew <= -1)
|
||||
skew = -most
|
||||
else if (skew >= 1)
|
||||
skew = most
|
||||
const ws = linearWeights(props.builder.rungs, skew)
|
||||
let balance = Math.min(most, Math.max(-most, -props.builder.balance))
|
||||
const ws = linearWeights(props.builder.rungs, balance)
|
||||
if (props.setWeights)
|
||||
props.setWeights(ws)
|
||||
return ws
|
||||
@@ -217,8 +277,10 @@ const color = computed({
|
||||
props.builder.color = c.saturation <= maxLightness ? v : c.lightness(maxLightness).rgb().string()
|
||||
}
|
||||
})
|
||||
const switchColor = computed(()=>props.builder.breakout ? color.value : null)
|
||||
const colorStyle = computed(() => {
|
||||
return {'color': color.value}
|
||||
// return {'color': color.value}
|
||||
return {}
|
||||
})
|
||||
|
||||
|
||||
@@ -251,9 +313,9 @@ function translateOnModel(shape) {
|
||||
if (!this.beingDragged())
|
||||
return
|
||||
const prev = getModelValue(oldModel)
|
||||
const cur = vectorize(getModelValue(this.model))
|
||||
const cur = vectorize(getModelValue(model))
|
||||
const delta = vectorSub(cur, prev)
|
||||
// console.log('delta', shape.id, prev, cur, delta)
|
||||
// console.log('translateOnModel delta', shape.id, prev, cur, delta)
|
||||
let [a, b] = endpoints.value
|
||||
a = vectorize(a)
|
||||
if (!vectorIsZero(delta)) {
|
||||
@@ -320,11 +382,12 @@ function makeModel(index) {
|
||||
allocation: alloc,
|
||||
maxAllocation: Math.max(...weights.value),
|
||||
amount: props.order.amount * alloc,
|
||||
baseSymbol: co.selectedSymbol.base.s,
|
||||
amountSymbol: amountSymbol.value,
|
||||
textLocation: above ? 'above' : 'below',
|
||||
extraText: !props.builder.breakout ? ' ' :
|
||||
// (above ? '↑ Breakout ↑' : '↓ Breakout ↓')
|
||||
(above ? '▲ Breakout ▲' : '▼ Breakout ▼')
|
||||
breakout: props.builder.breakout,
|
||||
extraText: null,
|
||||
buy,
|
||||
}
|
||||
setModelValue(result, values.value[index])
|
||||
return result
|
||||
@@ -404,6 +467,8 @@ function deleteShapes() {
|
||||
|
||||
if (!endpoints.value[0])
|
||||
shapeA.createOrDraw(); // initiate drawing mode
|
||||
else
|
||||
adjustShapes()
|
||||
|
||||
</script>
|
||||
|
||||
@@ -417,7 +482,7 @@ if (!endpoints.value[0])
|
||||
:deep(.v-slider.no-slider-bg .v-slider-track__fill) {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
.skew {
|
||||
.balance {
|
||||
min-width: 9em;
|
||||
max-width: 12em;
|
||||
}
|
||||
|
||||
28
src/components/chart/Shared.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<h2>Shared Order</h2>
|
||||
<p>Loading the shared order into the app...</p>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {loadShareUrl} from "@/share.js";
|
||||
import {router} from "@/router/router.js";
|
||||
import {useRoute} from "vue-router";
|
||||
|
||||
const route = useRoute()
|
||||
const code = route.params.code
|
||||
loadShareUrl(code).then((ok)=> {
|
||||
if (ok) {
|
||||
console.log('loaded share data',code)
|
||||
router.replace('/order');
|
||||
}
|
||||
else {
|
||||
console.log('failed to load share data',code)
|
||||
}
|
||||
}).catch((e)=> {
|
||||
console.error(e)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="d-flex mb-1 align-center w-100">
|
||||
<logo class="d-flex align-end clickable logo-large" @click="nav('Order')" :show-tag="true"/>
|
||||
<logo class="d-flex align-end clickable logo-large ml-1" @click="// noinspection JSIgnoredPromiseFromCall
|
||||
router.push({name: 'Order'})" :show-tag="true" max-height="32"/>
|
||||
<slot/>
|
||||
<div class="ml-auto d-flex align-center">
|
||||
<span class="title mr-4">{{title}}</span>
|
||||
@@ -9,31 +10,18 @@
|
||||
<toolbar-button tooltip="Assets" icon="mdi-currency-btc" route="Assets"/>
|
||||
<!-- mdi-format-list-checks mdi-format-list-bulleted-square -->
|
||||
<toolbar-button tooltip="Status" icon="mdi-format-list-checks" route="Status"/>
|
||||
<v-btn variant="text" icon="mdi-help-circle-outline" text="Info" @click="showCorp"></v-btn>
|
||||
<toolbar-button tooltip="About" icon="mdi-information-outline" href="https://dexorder.com/" target="dexorderwww"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {useTheme} from "vuetify";
|
||||
import ToolbarButton from "@/components/chart/ToolbarButton.vue";
|
||||
import beta from "@/components/Beta.vue";
|
||||
import Logo from "@/components/Logo.vue";
|
||||
import {nav} from "@/misc.js";
|
||||
import {router} from "@/router/router.js";
|
||||
|
||||
const props = defineProps(['title', 'icon'])
|
||||
|
||||
const s = useStore()
|
||||
const co = useChartOrderStore()
|
||||
|
||||
const theme = useTheme().current
|
||||
|
||||
function showCorp() {
|
||||
window.open('https://dexorder.trade/', 'dexorder')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -2,20 +2,34 @@
|
||||
<v-tooltip :text="tooltip" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" :color="isCurrent?'primary':undefined" variant="text"
|
||||
:icon="icon" @click="()=>nav(route)"/>
|
||||
:icon="icon" @click="click"/>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed} from "vue";
|
||||
import {useRoute} from "vue-router";
|
||||
import {nav} from "/src/misc.js"
|
||||
import {router} from "@/router/router.js";
|
||||
|
||||
const props = defineProps(['icon', 'route', 'tooltip'])
|
||||
console.log('toolbar button props', props.route)
|
||||
const router = useRoute();
|
||||
const props = defineProps(['icon', 'route', 'tooltip', 'href', 'target'])
|
||||
const isCurrent = computed(() => router.name === props.route)
|
||||
|
||||
function click() {
|
||||
if (!props.href && !props.route)
|
||||
console.warn('must set href or route in toolbar-button')
|
||||
else if (props.href && props.route)
|
||||
console.warn('cannot set both rout and href in toolbar-button')
|
||||
else if (props.href) {
|
||||
if (props.target)
|
||||
window.open(props.href, props.target)
|
||||
else
|
||||
window.location.href = props.href
|
||||
|
||||
}
|
||||
else
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
router.push({name: props.route})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script setup>
|
||||
function openApp() {
|
||||
window.open('https://beta.dexorder.trade/', 'dexorderapp')
|
||||
window.open('https://app.dexorder.com/', 'dexorderapp')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
82
src/corp/Fees.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<v-card title="Dexorder Fee Schedule">
|
||||
<h2>Fill Fee</h2>
|
||||
<v-card-text>
|
||||
Dexorder charges the same fee as the Uniswap pool you trade, capped at a maximum of 0.15%.
|
||||
</v-card-text>
|
||||
<v-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Uniswap Pool Fee</th>
|
||||
<th>Dexorder Fee</th>
|
||||
<th>Total Fee</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>0.01%</td><td>0.01%</td><td>0.02%</td></tr>
|
||||
<tr><td>0.05%</td><td>0.05%</td><td>0.10%</td></tr>
|
||||
<tr><td>0.30%</td><td>0.15%</td><td>0.45%</td></tr>
|
||||
<tr><td>1.00%</td><td>0.15%</td><td>1.15%</td></tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
<h2>Gas Fees</h2>
|
||||
<v-card-text>
|
||||
In order to execute multi-tranche orders at various future times, Dexorder charges an up-front gas fee to cover
|
||||
the cost of all future smart contract calls required to fill your order. These gas fees depend on how many
|
||||
tranches are in your order and whether the order triggers a conditional order (stop-loss or take-profit). These
|
||||
gas fees are collected in native currency (ETH) and are included in your initial order placement transaction.
|
||||
This means your wallet will display the total gas cost for your entire order when you first place it.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
Once you place your order, you will never be charged additional gas fees. Dexorder's up-front gas charge is
|
||||
treated like an insurance fund, guaranteeing future execution of your order, even if the price of gas increases
|
||||
significantly. If your order does not execute all of its tranches or is cancelled, the gas fee is not refunded.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
Dexorder does not seek to profit from gas fees. They are designed to cover our cost of triggering your order
|
||||
multiple times in the future.
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
The formula for computing your gas cost is:
|
||||
```javascript
|
||||
// Standard order without any conditional order
|
||||
totalGasCost = trancheFee * numberOfTranches;
|
||||
|
||||
// Order which triggers a conditional order (stop-loss or take-profit)
|
||||
totalGasCost = (trancheFee + orderFee) * estimatedExecutions + trancheFee * estimatedConditionalExecutions;
|
||||
|
||||
estimatedExecutions = numberOfTranches / rateLimit;
|
||||
```
|
||||
|
||||
`trancheFee` and `orderFee` are specified by the `FeeManager` contract and may be adjusted on an hourly basis.
|
||||
These fee adjustments have important limits and cannot be set to arbitrarily high values. See the
|
||||
<a href="https://github.com/dexorder-trade/contract/blob/master/src/core/FeeManager.sol">
|
||||
FeeManager source code
|
||||
</a>, which describes the security measures.
|
||||
|
||||
The `estimatedExecutions` is computed as the number of tranches in the order, divided by any rate limit. For
|
||||
example, if the rate limit is 20% per hour, then there will be at least 5 executions required to fill the total
|
||||
amount, so the number of tranches is multiplied by 5 (same as dividing by 20%).
|
||||
|
||||
View the [source code for fees](https://github.com/dexorder-trade/contract/blob/master/src/core/OrderLib.sol#L20).
|
||||
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
Tranche
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
|
||||
</v-card-text>
|
||||
|
||||
</v-card>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||