mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 16:28:06 +00:00
Compare commits
1
Commits
main
...
feat/extended
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
958de8f2b8 |
@@ -114,3 +114,16 @@ PARADEX_WALLET_ADDRESS=
|
|||||||
|
|
||||||
# Enable verbose adapter logging: set to "1" or "true"
|
# Enable verbose adapter logging: set to "1" or "true"
|
||||||
# PARADEX_DEBUG=false
|
# PARADEX_DEBUG=false
|
||||||
|
|
||||||
|
# Extended exchange configuration (Starknet)
|
||||||
|
# Set EXCHANGE=extended to activate. Requires on-chain Stark key + vault id.
|
||||||
|
EXTENDED_API_KEY=
|
||||||
|
EXTENDED_STARK_PRIVATE_KEY= # 0x-prefixed Stark private key for signing orders
|
||||||
|
EXTENDED_VAULT_ID= # Vault/sub-account id from Extended UI
|
||||||
|
EXTENDED_MARKET=BTC-USD # Defaults to TRADE_SYMBOL when omitted
|
||||||
|
|
||||||
|
# Optional overrides (defaults target mainnet host)
|
||||||
|
# EXTENDED_API_HOST=api.starknet.extended.exchange
|
||||||
|
# EXTENDED_STREAM_HOST=api.starknet.extended.exchange
|
||||||
|
# EXTENDED_PRIVATE_STREAM_HOST=api.starknet.extended.exchange
|
||||||
|
# EXTENDED_USER_AGENT=ritmex-bot
|
||||||
|
|||||||
@@ -6,12 +6,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grvt/client": "^1.6.4",
|
"@grvt/client": "^1.6.4",
|
||||||
"@noble/ed25519": "^3.0.0",
|
"@noble/ed25519": "^3.0.0",
|
||||||
|
"@x10xchange/stark-crypto-wrapper-wasm": "^0.2.0",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
|
"bignumber.js": "^9.3.1",
|
||||||
"ccxt": "^4.5.12",
|
"ccxt": "^4.5.12",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"ethereum-cryptography": "^2.1.3",
|
"ethereum-cryptography": "^2.1.3",
|
||||||
"ink": "^6.3.1",
|
"ink": "^6.3.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
"starknet": "^8.5.4",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -132,12 +136,18 @@
|
|||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ=="],
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ=="],
|
||||||
|
|
||||||
"@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="],
|
"@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
|
||||||
|
|
||||||
"@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="],
|
"@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="],
|
||||||
|
|
||||||
"@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="],
|
"@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="],
|
||||||
|
|
||||||
|
"@scure/starknet": ["@scure/starknet@1.1.0", "", { "dependencies": { "@noble/curves": "~1.7.0", "@noble/hashes": "~1.6.0" } }, "sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ=="],
|
||||||
|
|
||||||
|
"@starknet-io/starknet-types-08": ["@starknet-io/types-js@0.8.4", "", {}, "sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ=="],
|
||||||
|
|
||||||
|
"@starknet-io/starknet-types-09": ["@starknet-io/types-js@0.9.2", "", {}, "sha512-vWOc0FVSn+RmabozIEWcEny1I73nDGTvOrLYJsR1x7LGA3AZmqt4i/aW69o/3i2NN5CVP8Ok6G1ayRQJKye3Wg=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
|
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
|
||||||
|
|
||||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||||
@@ -168,12 +178,18 @@
|
|||||||
|
|
||||||
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||||
|
|
||||||
|
"@x10xchange/stark-crypto-wrapper-wasm": ["@x10xchange/stark-crypto-wrapper-wasm@0.2.0", "", {}, "sha512-pi7cHvDgPf9Ri9q0cvrUnCnucafB+F3ch7ia0oEJhKMY+bWvxWRYA2vve9s6UMMNV6PnmTyFZPOYptVGGdLTWA=="],
|
||||||
|
|
||||||
|
"abi-wan-kanabi": ["abi-wan-kanabi@2.2.4", "", { "dependencies": { "ansicolors": "^0.3.2", "cardinal": "^2.1.1", "fs-extra": "^10.0.0", "yargs": "^17.7.2" }, "bin": { "generate": "dist/generate.js" } }, "sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg=="],
|
||||||
|
|
||||||
"ansi-escapes": ["ansi-escapes@7.1.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g=="],
|
"ansi-escapes": ["ansi-escapes@7.1.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g=="],
|
||||||
|
|
||||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||||
|
|
||||||
|
"ansicolors": ["ansicolors@0.3.2", "", {}, "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg=="],
|
||||||
|
|
||||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||||
|
|
||||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||||
@@ -182,12 +198,16 @@
|
|||||||
|
|
||||||
"axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="],
|
"axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="],
|
||||||
|
|
||||||
|
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
|
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
|
||||||
|
|
||||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||||
|
|
||||||
|
"cardinal": ["cardinal@2.1.1", "", { "dependencies": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" }, "bin": { "cdl": "./bin/cdl.js" } }, "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw=="],
|
||||||
|
|
||||||
"ccxt": ["ccxt@4.5.12", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-2lfL2TKjq4vBkQUQWJfDqFywhvYCZmk9r0SWC8GqA4AHZ6qozKVUJowxQTvdRsLX9jBwYSE0nc7JVurBrQ6SHg=="],
|
"ccxt": ["ccxt@4.5.12", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-2lfL2TKjq4vBkQUQWJfDqFywhvYCZmk9r0SWC8GqA4AHZ6qozKVUJowxQTvdRsLX9jBwYSE0nc7JVurBrQ6SHg=="],
|
||||||
|
|
||||||
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
||||||
@@ -202,14 +222,22 @@
|
|||||||
|
|
||||||
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
|
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
|
||||||
|
|
||||||
|
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||||
|
|
||||||
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
|
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
|
||||||
|
|
||||||
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||||
|
|
||||||
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
|
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
|
||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
|
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
|
||||||
|
|
||||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||||
|
|
||||||
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||||
@@ -238,8 +266,12 @@
|
|||||||
|
|
||||||
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
|
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
|
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
||||||
|
|
||||||
|
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||||
|
|
||||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||||
|
|
||||||
"ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="],
|
"ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="],
|
||||||
@@ -252,10 +284,14 @@
|
|||||||
|
|
||||||
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
||||||
|
|
||||||
|
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||||
|
|
||||||
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
||||||
|
|
||||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||||
@@ -264,6 +300,8 @@
|
|||||||
|
|
||||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
|
|
||||||
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||||
|
|
||||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||||
@@ -280,6 +318,10 @@
|
|||||||
|
|
||||||
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||||
|
|
||||||
|
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||||
|
|
||||||
|
"lossless-json": ["lossless-json@4.3.0", "", {}, "sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g=="],
|
||||||
|
|
||||||
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
|
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
|
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
|
||||||
@@ -298,6 +340,8 @@
|
|||||||
|
|
||||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||||
|
|
||||||
|
"pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="],
|
||||||
|
|
||||||
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
|
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
|
||||||
|
|
||||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
@@ -316,6 +360,10 @@
|
|||||||
|
|
||||||
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
|
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
|
||||||
|
|
||||||
|
"redeyed": ["redeyed@2.1.1", "", { "dependencies": { "esprima": "~4.0.0" } }, "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ=="],
|
||||||
|
|
||||||
|
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||||
|
|
||||||
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="],
|
"rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="],
|
||||||
@@ -334,6 +382,8 @@
|
|||||||
|
|
||||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||||
|
|
||||||
|
"starknet": ["starknet@8.9.0", "", { "dependencies": { "@noble/curves": "~1.7.0", "@noble/hashes": "~1.6.0", "@scure/base": "~1.2.1", "@scure/starknet": "1.1.0", "@starknet-io/starknet-types-08": "npm:@starknet-io/types-js@~0.8.4", "@starknet-io/starknet-types-09": "npm:@starknet-io/types-js@~0.9.1", "abi-wan-kanabi": "2.2.4", "lossless-json": "^4.2.0", "pako": "^2.0.4", "ts-mixer": "^6.0.3" } }, "sha512-uhXSCKW0SMBGckd4yVlscVz4DMkQqZ+/RTpfxZ5J46e6AuCUHOqGKv61tqgUP9GhdvgHV/XyN+kdmlyZn3rXjA=="],
|
||||||
|
|
||||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||||
|
|
||||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
@@ -354,12 +404,16 @@
|
|||||||
|
|
||||||
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
|
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
|
||||||
|
|
||||||
|
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
|
||||||
|
|
||||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
||||||
|
|
||||||
|
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||||
|
|
||||||
"vite": ["vite@7.1.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA=="],
|
"vite": ["vite@7.1.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA=="],
|
||||||
|
|
||||||
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||||
@@ -374,10 +428,56 @@
|
|||||||
|
|
||||||
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
||||||
|
|
||||||
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||||
|
|
||||||
|
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||||
|
|
||||||
|
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||||
|
|
||||||
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
|
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
|
||||||
|
|
||||||
|
"@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="],
|
||||||
|
|
||||||
|
"@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="],
|
||||||
|
|
||||||
|
"@scure/starknet/@noble/curves": ["@noble/curves@1.7.0", "", { "dependencies": { "@noble/hashes": "1.6.0" } }, "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw=="],
|
||||||
|
|
||||||
|
"@scure/starknet/@noble/hashes": ["@noble/hashes@1.6.1", "", {}, "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w=="],
|
||||||
|
|
||||||
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
|
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
|
||||||
|
|
||||||
|
"cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
|
"starknet/@noble/curves": ["@noble/curves@1.7.0", "", { "dependencies": { "@noble/hashes": "1.6.0" } }, "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw=="],
|
||||||
|
|
||||||
|
"starknet/@noble/hashes": ["@noble/hashes@1.6.1", "", {}, "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w=="],
|
||||||
|
|
||||||
|
"yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"@scure/starknet/@noble/curves/@noble/hashes": ["@noble/hashes@1.6.0", "", {}, "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ=="],
|
||||||
|
|
||||||
"cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
"cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||||
|
|
||||||
|
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
|
"cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
|
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
|
"starknet/@noble/curves/@noble/hashes": ["@noble/hashes@1.6.0", "", {}, "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ=="],
|
||||||
|
|
||||||
|
"yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
|
"yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
|
"yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -25,12 +25,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grvt/client": "^1.6.4",
|
"@grvt/client": "^1.6.4",
|
||||||
"@noble/ed25519": "^3.0.0",
|
"@noble/ed25519": "^3.0.0",
|
||||||
|
"@x10xchange/stark-crypto-wrapper-wasm": "^0.2.0",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
|
"bignumber.js": "^9.3.1",
|
||||||
"ccxt": "^4.5.12",
|
"ccxt": "^4.5.12",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"ethereum-cryptography": "^2.1.3",
|
"ethereum-cryptography": "^2.1.3",
|
||||||
"ink": "^6.3.1",
|
"ink": "^6.3.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
|
"starknet": "^8.5.4",
|
||||||
"ws": "^8.18.3"
|
"ws": "^8.18.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const SYMBOL_PRIORITY_BY_EXCHANGE: Record<SupportedExchangeId, { envKeys: string
|
|||||||
lighter: { envKeys: ["LIGHTER_SYMBOL", "TRADE_SYMBOL"], fallback: "BTCUSDT" },
|
lighter: { envKeys: ["LIGHTER_SYMBOL", "TRADE_SYMBOL"], fallback: "BTCUSDT" },
|
||||||
backpack: { envKeys: ["BACKPACK_SYMBOL", "TRADE_SYMBOL"], fallback: "BTCUSDC" },
|
backpack: { envKeys: ["BACKPACK_SYMBOL", "TRADE_SYMBOL"], fallback: "BTCUSDC" },
|
||||||
paradex: { envKeys: ["PARADEX_SYMBOL", "TRADE_SYMBOL"], fallback: "BTC/USDC" },
|
paradex: { envKeys: ["PARADEX_SYMBOL", "TRADE_SYMBOL"], fallback: "BTC/USDC" },
|
||||||
|
extended: { envKeys: ["EXTENDED_MARKET", "EXTENDED_SYMBOL", "TRADE_SYMBOL"], fallback: "BTC-USD" },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resolveSymbolFromEnv(explicitExchangeId?: SupportedExchangeId | string | null): string {
|
export function resolveSymbolFromEnv(explicitExchangeId?: SupportedExchangeId | string | null): string {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { GrvtExchangeAdapter, type GrvtCredentials } from "./grvt/adapter";
|
|||||||
import { LighterExchangeAdapter, type LighterCredentials } from "./lighter/adapter";
|
import { LighterExchangeAdapter, type LighterCredentials } from "./lighter/adapter";
|
||||||
import { BackpackExchangeAdapter, type BackpackCredentials } from "./backpack/adapter";
|
import { BackpackExchangeAdapter, type BackpackCredentials } from "./backpack/adapter";
|
||||||
import { ParadexExchangeAdapter, type ParadexCredentials } from "./paradex/adapter";
|
import { ParadexExchangeAdapter, type ParadexCredentials } from "./paradex/adapter";
|
||||||
|
import { ExtendedExchangeAdapter, type ExtendedCredentials } from "./extended/adapter";
|
||||||
|
|
||||||
export interface ExchangeFactoryOptions {
|
export interface ExchangeFactoryOptions {
|
||||||
symbol: string;
|
symbol: string;
|
||||||
@@ -13,9 +14,10 @@ export interface ExchangeFactoryOptions {
|
|||||||
lighter?: LighterCredentials;
|
lighter?: LighterCredentials;
|
||||||
backpack?: BackpackCredentials;
|
backpack?: BackpackCredentials;
|
||||||
paradex?: ParadexCredentials;
|
paradex?: ParadexCredentials;
|
||||||
|
extended?: ExtendedCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SupportedExchangeId = "aster" | "grvt" | "lighter" | "backpack" | "paradex";
|
export type SupportedExchangeId = "aster" | "grvt" | "lighter" | "backpack" | "paradex" | "extended";
|
||||||
|
|
||||||
export function resolveExchangeId(value?: string | null): SupportedExchangeId {
|
export function resolveExchangeId(value?: string | null): SupportedExchangeId {
|
||||||
const fallback = (value ?? process.env.EXCHANGE ?? process.env.TRADE_EXCHANGE ?? "aster")
|
const fallback = (value ?? process.env.EXCHANGE ?? process.env.TRADE_EXCHANGE ?? "aster")
|
||||||
@@ -26,6 +28,7 @@ export function resolveExchangeId(value?: string | null): SupportedExchangeId {
|
|||||||
if (fallback === "lighter") return "lighter";
|
if (fallback === "lighter") return "lighter";
|
||||||
if (fallback === "backpack") return "backpack";
|
if (fallback === "backpack") return "backpack";
|
||||||
if (fallback === "paradex") return "paradex";
|
if (fallback === "paradex") return "paradex";
|
||||||
|
if (fallback === "extended") return "extended";
|
||||||
return "aster";
|
return "aster";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +37,7 @@ export function getExchangeDisplayName(id: SupportedExchangeId): string {
|
|||||||
if (id === "lighter") return "Lighter";
|
if (id === "lighter") return "Lighter";
|
||||||
if (id === "backpack") return "Backpack";
|
if (id === "backpack") return "Backpack";
|
||||||
if (id === "paradex") return "Paradex";
|
if (id === "paradex") return "Paradex";
|
||||||
|
if (id === "extended") return "Extended";
|
||||||
return "AsterDex";
|
return "AsterDex";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,5 +55,8 @@ export function createExchangeAdapter(options: ExchangeFactoryOptions): Exchange
|
|||||||
if (id === "paradex") {
|
if (id === "paradex") {
|
||||||
return new ParadexExchangeAdapter({ ...options.paradex, symbol: options.symbol });
|
return new ParadexExchangeAdapter({ ...options.paradex, symbol: options.symbol });
|
||||||
}
|
}
|
||||||
|
if (id === "extended") {
|
||||||
|
return new ExtendedExchangeAdapter({ ...options.extended, market: options.symbol });
|
||||||
|
}
|
||||||
return new AsterExchangeAdapter({ ...options.aster, symbol: options.symbol });
|
return new AsterExchangeAdapter({ ...options.aster, symbol: options.symbol });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
import { setTimeout, clearTimeout } from "timers";
|
||||||
|
import type {
|
||||||
|
AccountListener,
|
||||||
|
DepthListener,
|
||||||
|
ExchangeAdapter,
|
||||||
|
ExchangePrecision,
|
||||||
|
KlineListener,
|
||||||
|
OrderListener,
|
||||||
|
TickerListener,
|
||||||
|
} from "../adapter";
|
||||||
|
import type { AsterOrder, CreateOrderParams } from "../types";
|
||||||
|
import { ExtendedGateway, type ExtendedGatewayOptions } from "./gateway";
|
||||||
|
|
||||||
|
export interface ExtendedCredentials {
|
||||||
|
apiKey?: string;
|
||||||
|
starkPrivateKey?: string;
|
||||||
|
vaultId?: string;
|
||||||
|
market?: string;
|
||||||
|
apiHost?: string;
|
||||||
|
streamHost?: string;
|
||||||
|
privateStreamHost?: string;
|
||||||
|
userAgent?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtendedExchangeAdapter implements ExchangeAdapter {
|
||||||
|
readonly id = "extended";
|
||||||
|
private readonly gateway: ExtendedGateway;
|
||||||
|
private initPromise: Promise<void> | null = null;
|
||||||
|
private readonly initContexts = new Set<string>();
|
||||||
|
private retryTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
private retryDelayMs = 3000;
|
||||||
|
private lastInitErrorAt = 0;
|
||||||
|
|
||||||
|
constructor(credentials: ExtendedCredentials = {}) {
|
||||||
|
const apiKey = credentials.apiKey ?? process.env.EXTENDED_API_KEY;
|
||||||
|
const starkPrivateKey = credentials.starkPrivateKey ?? process.env.EXTENDED_STARK_PRIVATE_KEY;
|
||||||
|
const vaultId = credentials.vaultId ?? process.env.EXTENDED_VAULT_ID;
|
||||||
|
const market = credentials.market ?? process.env.EXTENDED_MARKET ?? process.env.TRADE_SYMBOL ?? "BTC-USD";
|
||||||
|
if (!apiKey) throw new Error("Missing EXTENDED_API_KEY");
|
||||||
|
if (!starkPrivateKey) throw new Error("Missing EXTENDED_STARK_PRIVATE_KEY");
|
||||||
|
if (!vaultId) throw new Error("Missing EXTENDED_VAULT_ID");
|
||||||
|
const options: ExtendedGatewayOptions = {
|
||||||
|
apiKey,
|
||||||
|
starkPrivateKey,
|
||||||
|
vaultId,
|
||||||
|
market,
|
||||||
|
apiHost: credentials.apiHost ?? process.env.EXTENDED_API_HOST,
|
||||||
|
streamHost: credentials.streamHost ?? process.env.EXTENDED_STREAM_HOST,
|
||||||
|
privateStreamHost: credentials.privateStreamHost ?? process.env.EXTENDED_PRIVATE_STREAM_HOST,
|
||||||
|
userAgent: credentials.userAgent ?? process.env.EXTENDED_USER_AGENT,
|
||||||
|
logger: (context, error) => this.log(context, error),
|
||||||
|
};
|
||||||
|
this.gateway = new ExtendedGateway(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsTrailingStops(): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
watchAccount(cb: AccountListener): void {
|
||||||
|
void this.ensureInitialized("watchAccount");
|
||||||
|
this.gateway.onAccount(this.safeInvoke("watchAccount", cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
watchOrders(cb: OrderListener): void {
|
||||||
|
void this.ensureInitialized("watchOrders");
|
||||||
|
this.gateway.onOrders(this.safeInvoke("watchOrders", cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
watchDepth(_symbol: string, cb: DepthListener): void {
|
||||||
|
void this.ensureInitialized("watchDepth");
|
||||||
|
this.gateway.onDepth(this.safeInvoke("watchDepth", cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
watchTicker(_symbol: string, cb: TickerListener): void {
|
||||||
|
void this.ensureInitialized("watchTicker");
|
||||||
|
this.gateway.onTicker(this.safeInvoke("watchTicker", cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
watchKlines(_symbol: string, interval: string, cb: KlineListener): void {
|
||||||
|
void this.ensureInitialized(`watchKlines:${interval}`);
|
||||||
|
this.gateway.watchKlines(interval, this.safeInvoke("watchKlines", cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrder(params: CreateOrderParams): Promise<AsterOrder> {
|
||||||
|
await this.ensureInitialized("createOrder");
|
||||||
|
return this.gateway.createOrder(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelOrder(params: { symbol: string; orderId: number | string }): Promise<void> {
|
||||||
|
await this.ensureInitialized("cancelOrder");
|
||||||
|
await this.gateway.cancelOrder({ orderId: params.orderId });
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelOrders(params: { symbol: string; orderIdList: Array<number | string> }): Promise<void> {
|
||||||
|
await this.ensureInitialized("cancelOrders");
|
||||||
|
await this.gateway.cancelOrders({ orderIdList: params.orderIdList });
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelAllOrders(_params: { symbol: string }): Promise<void> {
|
||||||
|
await this.ensureInitialized("cancelAllOrders");
|
||||||
|
await this.gateway.cancelAllOrders();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPrecision(): Promise<ExchangePrecision | null> {
|
||||||
|
try {
|
||||||
|
const precision = await this.gateway.getPrecision();
|
||||||
|
if (!precision) return null;
|
||||||
|
return {
|
||||||
|
priceTick: precision.priceTick,
|
||||||
|
qtyStep: precision.qtyStep,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.log("getPrecision", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private safeInvoke<T extends (...args: any[]) => void>(context: string, cb: T): T {
|
||||||
|
const wrapped = ((...args: any[]) => {
|
||||||
|
try {
|
||||||
|
cb(...args);
|
||||||
|
} catch (error) {
|
||||||
|
this.log(`${context} handler`, error);
|
||||||
|
}
|
||||||
|
}) as T;
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureInitialized(context?: string): Promise<void> {
|
||||||
|
if (!this.initPromise) {
|
||||||
|
this.initContexts.clear();
|
||||||
|
this.initPromise = this.gateway.ensureInitialized().catch((error) => {
|
||||||
|
this.handleInitError("initialize", error);
|
||||||
|
this.initPromise = null;
|
||||||
|
this.scheduleRetry();
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (context && !this.initContexts.has(context)) {
|
||||||
|
this.initContexts.add(context);
|
||||||
|
this.initPromise.catch((error) => {
|
||||||
|
this.handleInitError(context, error);
|
||||||
|
this.scheduleRetry();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.initPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleRetry(): void {
|
||||||
|
if (this.retryTimer) return;
|
||||||
|
this.retryTimer = setTimeout(() => {
|
||||||
|
this.retryTimer = null;
|
||||||
|
if (this.initPromise) return;
|
||||||
|
this.retryDelayMs = Math.min(this.retryDelayMs * 2, 60_000);
|
||||||
|
void this.ensureInitialized("retry");
|
||||||
|
}, this.retryDelayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInitError(context: string, error: unknown): void {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - this.lastInitErrorAt < 5000) return;
|
||||||
|
this.lastInitErrorAt = now;
|
||||||
|
this.log(context, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private log(context: string, error: unknown): void {
|
||||||
|
if (process.env.EXTENDED_DEBUG === "1" || process.env.EXTENDED_DEBUG === "true") {
|
||||||
|
console.error(`[ExtendedExchangeAdapter] ${context}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,713 @@
|
|||||||
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import NodeWebSocket from "ws";
|
||||||
|
import { setTimeout, clearTimeout, setInterval, clearInterval } from "timers";
|
||||||
|
import type {
|
||||||
|
AccountListener,
|
||||||
|
DepthListener,
|
||||||
|
KlineListener,
|
||||||
|
OrderListener,
|
||||||
|
TickerListener,
|
||||||
|
} from "../adapter";
|
||||||
|
import type {
|
||||||
|
AsterAccountAsset,
|
||||||
|
AsterAccountPosition,
|
||||||
|
AsterAccountSnapshot,
|
||||||
|
AsterDepth,
|
||||||
|
AsterKline,
|
||||||
|
AsterOrder,
|
||||||
|
AsterTicker,
|
||||||
|
CreateOrderParams,
|
||||||
|
} from "../types";
|
||||||
|
import { toDecimal } from "./math";
|
||||||
|
import { ExtendedOrderBuilder, type ExtendedOrderContext } from "./order-builder";
|
||||||
|
import { tryInitExtendedWasm } from "./signing";
|
||||||
|
import type {
|
||||||
|
ExtendedBalance,
|
||||||
|
ExtendedCandle,
|
||||||
|
ExtendedDepthMessage,
|
||||||
|
ExtendedFees,
|
||||||
|
ExtendedMarket,
|
||||||
|
ExtendedOrder,
|
||||||
|
ExtendedPosition,
|
||||||
|
ExtendedStarknetDomain,
|
||||||
|
ExtendedTrade,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
const WebSocketCtor: typeof globalThis.WebSocket =
|
||||||
|
typeof globalThis.WebSocket !== "undefined" ? globalThis.WebSocket : ((NodeWebSocket as unknown) as typeof globalThis.WebSocket);
|
||||||
|
type WebSocket = typeof WebSocketCtor extends { new (...args: any[]): infer R } ? R : NodeWebSocket;
|
||||||
|
|
||||||
|
const RECONNECT_DELAY_MS = 3000;
|
||||||
|
|
||||||
|
export interface ExtendedGatewayOptions {
|
||||||
|
apiKey: string;
|
||||||
|
starkPrivateKey: string;
|
||||||
|
vaultId: string;
|
||||||
|
market: string;
|
||||||
|
apiHost?: string;
|
||||||
|
streamHost?: string;
|
||||||
|
privateStreamHost?: string;
|
||||||
|
userAgent?: string;
|
||||||
|
logger?: (context: string, error: unknown) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AccountState {
|
||||||
|
balance: ExtendedBalance | null;
|
||||||
|
positions: ExtendedPosition[];
|
||||||
|
orders: ExtendedOrder[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DepthState {
|
||||||
|
bids: [string, string][];
|
||||||
|
asks: [string, string][];
|
||||||
|
seq: number;
|
||||||
|
ts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtendedGateway {
|
||||||
|
private readonly options: ExtendedGatewayOptions;
|
||||||
|
private readonly logger: (context: string, error: unknown) => void;
|
||||||
|
private readonly axios: AxiosInstance;
|
||||||
|
private marketInfo: ExtendedMarket | null = null;
|
||||||
|
private fees: ExtendedFees | null = null;
|
||||||
|
private domain: ExtendedStarknetDomain | null = null;
|
||||||
|
private orderBuilder: ExtendedOrderBuilder | null = null;
|
||||||
|
private initialized = false;
|
||||||
|
|
||||||
|
private accountState: AccountState = { balance: null, positions: [], orders: [] };
|
||||||
|
private readonly accountListeners = new Set<AccountListener>();
|
||||||
|
private readonly orderListeners = new Set<OrderListener>();
|
||||||
|
private readonly depthListeners = new Set<DepthListener>();
|
||||||
|
private readonly tickerListeners = new Set<TickerListener>();
|
||||||
|
private readonly klineListeners = new Map<string, Set<KlineListener>>();
|
||||||
|
|
||||||
|
private accountWs: WebSocket | null = null;
|
||||||
|
private depthWs: WebSocket | null = null;
|
||||||
|
private tradesWs: WebSocket | null = null;
|
||||||
|
private candleWs = new Map<string, WebSocket>();
|
||||||
|
private reconnectTimers: Array<ReturnType<typeof setTimeout>> = [];
|
||||||
|
private lastDepth: DepthState | null = null;
|
||||||
|
private lastTrade: ExtendedTrade | null = null;
|
||||||
|
|
||||||
|
constructor(options: ExtendedGatewayOptions) {
|
||||||
|
this.options = options;
|
||||||
|
this.logger =
|
||||||
|
options.logger ??
|
||||||
|
((context, error) => {
|
||||||
|
console.error(`[ExtendedGateway] ${context}`, error);
|
||||||
|
});
|
||||||
|
const baseURL = `https://${options.apiHost ?? "api.starknet.extended.exchange"}`;
|
||||||
|
this.axios = axios.create({
|
||||||
|
baseURL,
|
||||||
|
headers: {
|
||||||
|
"X-Api-Key": options.apiKey,
|
||||||
|
"User-Agent": options.userAgent ?? "ritmex-bot",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureInitialized(): Promise<void> {
|
||||||
|
if (this.initialized) return;
|
||||||
|
this.logInfo("init", `Initializing Extended for ${this.options.market}`);
|
||||||
|
await tryInitExtendedWasm();
|
||||||
|
await Promise.all([this.loadMarket(), this.loadFees(), this.loadDomain()]);
|
||||||
|
this.orderBuilder = new ExtendedOrderBuilder(this.buildOrderContext());
|
||||||
|
await this.loadInitialAccount();
|
||||||
|
this.startAccountStream();
|
||||||
|
this.startDepthStream();
|
||||||
|
this.startTradesStream();
|
||||||
|
this.initialized = true;
|
||||||
|
this.logInfo("init", "Extended gateway ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
this.closeSocket(this.accountWs);
|
||||||
|
this.closeSocket(this.depthWs);
|
||||||
|
this.closeSocket(this.tradesWs);
|
||||||
|
for (const ws of this.candleWs.values()) {
|
||||||
|
this.closeSocket(ws);
|
||||||
|
}
|
||||||
|
this.candleWs.clear();
|
||||||
|
this.reconnectTimers.forEach((timer) => clearTimeout(timer));
|
||||||
|
this.reconnectTimers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccount(listener: AccountListener): void {
|
||||||
|
this.accountListeners.add(listener);
|
||||||
|
const snapshot = this.buildAccountSnapshot();
|
||||||
|
if (snapshot) {
|
||||||
|
try {
|
||||||
|
listener(snapshot);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger("accountReplay", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOrders(listener: OrderListener): void {
|
||||||
|
this.orderListeners.add(listener);
|
||||||
|
if (this.accountState.orders.length) {
|
||||||
|
try {
|
||||||
|
listener(this.accountState.orders.map((order) => this.mapOrder(order)));
|
||||||
|
} catch (error) {
|
||||||
|
this.logger("ordersReplay", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDepth(listener: DepthListener): void {
|
||||||
|
this.depthListeners.add(listener);
|
||||||
|
if (this.lastDepth) {
|
||||||
|
listener(this.mapDepth(this.lastDepth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTicker(listener: TickerListener): void {
|
||||||
|
this.tickerListeners.add(listener);
|
||||||
|
const ticker = this.buildTicker();
|
||||||
|
if (ticker) {
|
||||||
|
listener(ticker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchKlines(interval: string, listener: KlineListener): void {
|
||||||
|
const set = this.klineListeners.get(interval) ?? new Set<KlineListener>();
|
||||||
|
set.add(listener);
|
||||||
|
this.klineListeners.set(interval, set);
|
||||||
|
this.ensureCandleStream(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrder(params: CreateOrderParams): Promise<AsterOrder> {
|
||||||
|
if (!this.orderBuilder) {
|
||||||
|
await this.ensureInitialized();
|
||||||
|
}
|
||||||
|
const builder = this.orderBuilder!;
|
||||||
|
const marketPrice = this.estimateMarketPrice(params.side);
|
||||||
|
const built = builder.build({ ...params, marketPrice });
|
||||||
|
const { data } = await this.axios.post<{ data?: ExtendedOrder; status?: string }>("/api/v1/user/order", built.payload);
|
||||||
|
// Some responses only return status; fallback to local mapping using request params.
|
||||||
|
const order = data?.data ?? null;
|
||||||
|
if (order) {
|
||||||
|
return this.mapOrder(order);
|
||||||
|
}
|
||||||
|
// Fallback: return lightweight order reflecting submission.
|
||||||
|
return this.mapOrder({
|
||||||
|
id: built.orderId,
|
||||||
|
market: this.options.market,
|
||||||
|
type: this.normalizeOrderType(params.type),
|
||||||
|
side: params.side,
|
||||||
|
status: "NEW",
|
||||||
|
price: built.payload.price as string,
|
||||||
|
qty: built.payload.qty as string,
|
||||||
|
filledQty: "0",
|
||||||
|
reduceOnly: built.payload.reduceOnly as boolean,
|
||||||
|
postOnly: built.payload.postOnly as boolean,
|
||||||
|
createdTime: Date.now(),
|
||||||
|
updatedTime: Date.now(),
|
||||||
|
trigger: built.payload.trigger as any,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelOrder(params: { orderId: number | string }): Promise<void> {
|
||||||
|
await this.axios.delete(`/api/v1/user/order/${params.orderId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelOrders(params: { orderIdList: Array<number | string> }): Promise<void> {
|
||||||
|
await this.axios.post("/api/v1/user/order/massCancel", { orderIds: params.orderIdList });
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelAllOrders(): Promise<void> {
|
||||||
|
await this.axios.post("/api/v1/user/order/massCancel", { markets: [this.options.market] });
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPrecision(): Promise<{ priceTick: number; qtyStep: number } | null> {
|
||||||
|
if (!this.marketInfo) return null;
|
||||||
|
const { tradingConfig } = this.marketInfo;
|
||||||
|
return {
|
||||||
|
priceTick: Number(tradingConfig.minPriceChange),
|
||||||
|
qtyStep: Number(tradingConfig.minOrderSizeChange),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Internal fetchers --------------------------------------------------
|
||||||
|
|
||||||
|
private async loadMarket(): Promise<void> {
|
||||||
|
const { data } = await this.axios.get<{ data: ExtendedMarket[] }>("/api/v1/info/markets", {
|
||||||
|
params: { market: [this.options.market] },
|
||||||
|
});
|
||||||
|
const market = (data?.data ?? [])[0];
|
||||||
|
if (!market) {
|
||||||
|
throw new Error(`Extended market not found: ${this.options.market}`);
|
||||||
|
}
|
||||||
|
this.marketInfo = market;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadFees(): Promise<void> {
|
||||||
|
const { data } = await this.axios.get<{ data: ExtendedFees[] }>("/api/v1/user/fees", {
|
||||||
|
params: { market: [this.options.market] },
|
||||||
|
});
|
||||||
|
const fees = (data?.data ?? [])[0];
|
||||||
|
if (!fees) {
|
||||||
|
throw new Error("Unable to fetch Extended fee configuration");
|
||||||
|
}
|
||||||
|
this.fees = fees;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadDomain(): Promise<void> {
|
||||||
|
const { data } = await this.axios.get<{ data: ExtendedStarknetDomain }>("/api/v1/info/starknet");
|
||||||
|
if (!data?.data) {
|
||||||
|
throw new Error("Failed to load Extended Starknet domain");
|
||||||
|
}
|
||||||
|
this.domain = data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadInitialAccount(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const [balanceRes, positionsRes, ordersRes] = await Promise.all([
|
||||||
|
this.axios.get<{ data: ExtendedBalance }>("/api/v1/user/balance"),
|
||||||
|
this.axios.get<{ data: ExtendedPosition[] }>("/api/v1/user/positions", {
|
||||||
|
params: { market: [this.options.market] },
|
||||||
|
}),
|
||||||
|
this.axios.get<{ data: ExtendedOrder[] }>("/api/v1/user/orders", {
|
||||||
|
params: { market: [this.options.market] },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
this.accountState.balance = balanceRes.data?.data ?? null;
|
||||||
|
this.accountState.positions = positionsRes.data?.data ?? [];
|
||||||
|
this.accountState.orders = ordersRes.data?.data ?? [];
|
||||||
|
this.emitAccount();
|
||||||
|
this.emitOrders();
|
||||||
|
} catch (error) {
|
||||||
|
this.logger("loadInitialAccount", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildOrderContext(): ExtendedOrderContext {
|
||||||
|
if (!this.marketInfo || !this.fees || !this.domain) {
|
||||||
|
throw new Error("Extended gateway not ready");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
market: this.marketInfo,
|
||||||
|
fees: this.fees,
|
||||||
|
domain: this.domain,
|
||||||
|
vaultId: this.options.vaultId,
|
||||||
|
starkPrivateKey: this.options.starkPrivateKey,
|
||||||
|
builderFeeRate: this.fees.builderFeeRate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- WebSocket handling -------------------------------------------------
|
||||||
|
|
||||||
|
private startAccountStream(): void {
|
||||||
|
const host = this.options.privateStreamHost ?? this.options.streamHost ?? "api.starknet.extended.exchange";
|
||||||
|
const url = `wss://${host}/stream.extended.exchange/v1/account`;
|
||||||
|
this.accountWs = this.createSocket(
|
||||||
|
url,
|
||||||
|
{ headers: { "X-Api-Key": this.options.apiKey, "User-Agent": this.options.userAgent ?? "ritmex-bot" } },
|
||||||
|
"account",
|
||||||
|
(payload) => {
|
||||||
|
this.handleAccountMessage(payload);
|
||||||
|
},
|
||||||
|
(socket) => {
|
||||||
|
this.accountWs = socket;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startDepthStream(): void {
|
||||||
|
const host = this.options.streamHost ?? "api.starknet.extended.exchange";
|
||||||
|
const url = `wss://${host}/stream.extended.exchange/v1/orderbooks/${encodeURIComponent(this.options.market)}?depth=1`;
|
||||||
|
this.depthWs = this.createSocket(
|
||||||
|
url,
|
||||||
|
undefined,
|
||||||
|
"depth",
|
||||||
|
(payload) => {
|
||||||
|
this.handleDepthMessage(payload as ExtendedDepthMessage);
|
||||||
|
},
|
||||||
|
(socket) => {
|
||||||
|
this.depthWs = socket;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startTradesStream(): void {
|
||||||
|
const host = this.options.streamHost ?? "api.starknet.extended.exchange";
|
||||||
|
const url = `wss://${host}/stream.extended.exchange/v1/publicTrades/${encodeURIComponent(this.options.market)}`;
|
||||||
|
this.tradesWs = this.createSocket(
|
||||||
|
url,
|
||||||
|
undefined,
|
||||||
|
"trades",
|
||||||
|
(payload) => {
|
||||||
|
this.handleTradesMessage(payload as { data?: ExtendedTrade[]; ts?: number; seq?: number });
|
||||||
|
},
|
||||||
|
(socket) => {
|
||||||
|
this.tradesWs = socket;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureCandleStream(interval: string): void {
|
||||||
|
if (this.candleWs.has(interval)) return;
|
||||||
|
const host = this.options.streamHost ?? "api.starknet.extended.exchange";
|
||||||
|
const url = `wss://${host}/stream.extended.exchange/v1/candles/${encodeURIComponent(this.options.market)}/trades?interval=${encodeURIComponent(interval)}`;
|
||||||
|
const ws = this.createSocket(
|
||||||
|
url,
|
||||||
|
undefined,
|
||||||
|
`candle:${interval}`,
|
||||||
|
(payload) => {
|
||||||
|
this.handleCandleMessage(interval, payload as { data?: ExtendedCandle[] });
|
||||||
|
},
|
||||||
|
(socket) => {
|
||||||
|
this.candleWs.set(interval, socket);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.candleWs.set(interval, ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSocket(
|
||||||
|
url: string,
|
||||||
|
options: NodeWebSocket.ClientOptions | undefined,
|
||||||
|
context: string,
|
||||||
|
handler: (data: any) => void,
|
||||||
|
onCreate?: (socket: WebSocket) => void
|
||||||
|
): WebSocket {
|
||||||
|
const ws = new WebSocketCtor(url, options as any);
|
||||||
|
if (onCreate) {
|
||||||
|
onCreate(ws as any);
|
||||||
|
}
|
||||||
|
let pingTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
|
ws.onopen = () => {
|
||||||
|
if (pingTimer) clearInterval(pingTimer);
|
||||||
|
pingTimer = setInterval(() => {
|
||||||
|
try {
|
||||||
|
ws.send(JSON.stringify({ type: "ping" }));
|
||||||
|
} catch (_error) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}, 15_000);
|
||||||
|
this.logInfo(`${context}:open`, url);
|
||||||
|
};
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data = typeof event.data === "string" ? event.data : event.data.toString();
|
||||||
|
if (data === "ping") {
|
||||||
|
ws.send("pong");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler(JSON.parse(data));
|
||||||
|
} catch (error) {
|
||||||
|
this.logger(`${context}:message`, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ws.onerror = (error) => {
|
||||||
|
this.logError(`${context}:error`, error);
|
||||||
|
};
|
||||||
|
ws.onclose = () => {
|
||||||
|
if (pingTimer) clearInterval(pingTimer);
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
this.logInfo(`${context}:reconnect`, `reconnecting to ${url}`);
|
||||||
|
this.reconnectTimers = this.reconnectTimers.filter((t) => t !== timer);
|
||||||
|
this.createSocket(url, options, context, handler, onCreate);
|
||||||
|
}, RECONNECT_DELAY_MS);
|
||||||
|
this.reconnectTimers.push(timer);
|
||||||
|
this.logInfo(`${context}:close`, `closed ${url}`);
|
||||||
|
};
|
||||||
|
return ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeSocket(socket: WebSocket | null): void {
|
||||||
|
if (socket && socket.readyState === socket.OPEN) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- WS handlers -------------------------------------------------------
|
||||||
|
|
||||||
|
private handleAccountMessage(message: any): void {
|
||||||
|
if (!message || typeof message !== "object") return;
|
||||||
|
const type = message.type;
|
||||||
|
const data = message.data ?? {};
|
||||||
|
if (type === "BALANCE" && data.balance) {
|
||||||
|
this.accountState.balance = data.balance as ExtendedBalance;
|
||||||
|
this.emitAccount();
|
||||||
|
this.logInfo("account:balance", "updated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === "POSITION" && Array.isArray(data.positions)) {
|
||||||
|
this.accountState.positions = data.positions as ExtendedPosition[];
|
||||||
|
this.emitAccount();
|
||||||
|
this.logInfo("account:positions", `positions=${data.positions.length}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === "ORDER" && Array.isArray(data.orders)) {
|
||||||
|
this.accountState.orders = data.orders as ExtendedOrder[];
|
||||||
|
this.emitOrders();
|
||||||
|
this.logInfo("account:orders", `orders=${data.orders.length}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === "TRADE" && Array.isArray(data.trades)) {
|
||||||
|
// Trades can update fills; refresh orders if present.
|
||||||
|
if (Array.isArray(data.orders)) {
|
||||||
|
this.accountState.orders = data.orders as ExtendedOrder[];
|
||||||
|
this.emitOrders();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Fallback: handle messages where data is nested without type
|
||||||
|
const payload = message.data ?? message;
|
||||||
|
if (payload?.balance) {
|
||||||
|
this.accountState.balance = payload.balance as ExtendedBalance;
|
||||||
|
this.emitAccount();
|
||||||
|
this.logInfo("account:balance", "snapshot");
|
||||||
|
}
|
||||||
|
if (payload?.positions) {
|
||||||
|
this.accountState.positions = payload.positions as ExtendedPosition[];
|
||||||
|
this.emitAccount();
|
||||||
|
this.logInfo("account:positions", `positions=${(payload.positions as ExtendedPosition[]).length}`);
|
||||||
|
}
|
||||||
|
if (payload?.orders) {
|
||||||
|
this.accountState.orders = payload.orders as ExtendedOrder[];
|
||||||
|
this.emitOrders();
|
||||||
|
this.logInfo("account:orders", `orders=${(payload.orders as ExtendedOrder[]).length}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDepthMessage(message: ExtendedDepthMessage): void {
|
||||||
|
if (!message?.data) return;
|
||||||
|
const bids = (message.data.b ?? []).map((level) => [String(level.p), String(level.q)] as [string, string]);
|
||||||
|
const asks = (message.data.a ?? []).map((level) => [String(level.p), String(level.q)] as [string, string]);
|
||||||
|
this.lastDepth = { bids, asks, seq: message.seq ?? Date.now(), ts: message.ts ?? Date.now() };
|
||||||
|
this.emitDepth();
|
||||||
|
this.emitTicker();
|
||||||
|
if (bids.length || asks.length) {
|
||||||
|
this.logDebug("depth", `bids=${bids[0]?.[0] ?? "-"} asks=${asks[0]?.[0] ?? "-"}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleTradesMessage(message: { data?: ExtendedTrade[]; ts?: number }): void {
|
||||||
|
const trades = message?.data ?? [];
|
||||||
|
if (!trades.length) return;
|
||||||
|
this.lastTrade = trades[trades.length - 1];
|
||||||
|
this.emitTicker();
|
||||||
|
this.logDebug("trades", `last=${this.lastTrade.p} side=${this.lastTrade.S}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCandleMessage(interval: string, payload: { data?: ExtendedCandle[] }): void {
|
||||||
|
const candles = payload?.data ?? [];
|
||||||
|
if (!candles.length) return;
|
||||||
|
const mapped = candles.map((candle) => this.mapKline(candle));
|
||||||
|
const listeners = this.klineListeners.get(interval);
|
||||||
|
if (!listeners) return;
|
||||||
|
listeners.forEach((listener) => {
|
||||||
|
try {
|
||||||
|
listener(mapped);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger(`kline:${interval}`, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Emitters ----------------------------------------------------------
|
||||||
|
|
||||||
|
private emitAccount(): void {
|
||||||
|
const snapshot = this.buildAccountSnapshot();
|
||||||
|
if (!snapshot) return;
|
||||||
|
for (const listener of this.accountListeners) {
|
||||||
|
try {
|
||||||
|
listener(snapshot);
|
||||||
|
} catch (error) {
|
||||||
|
this.logError("accountListener", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitOrders(): void {
|
||||||
|
const mapped = this.accountState.orders.map((order) => this.mapOrder(order));
|
||||||
|
for (const listener of this.orderListeners) {
|
||||||
|
try {
|
||||||
|
listener(mapped);
|
||||||
|
} catch (error) {
|
||||||
|
this.logError("orderListener", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitDepth(): void {
|
||||||
|
if (!this.lastDepth) return;
|
||||||
|
const depth = this.mapDepth(this.lastDepth);
|
||||||
|
for (const listener of this.depthListeners) {
|
||||||
|
try {
|
||||||
|
listener(depth);
|
||||||
|
} catch (error) {
|
||||||
|
this.logError("depthListener", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitTicker(): void {
|
||||||
|
const ticker = this.buildTicker();
|
||||||
|
if (!ticker) return;
|
||||||
|
for (const listener of this.tickerListeners) {
|
||||||
|
try {
|
||||||
|
listener(ticker);
|
||||||
|
} catch (error) {
|
||||||
|
this.logError("tickerListener", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Mapping helpers ---------------------------------------------------
|
||||||
|
|
||||||
|
private buildAccountSnapshot(): AsterAccountSnapshot | null {
|
||||||
|
const balance = this.accountState.balance;
|
||||||
|
if (!balance) return null;
|
||||||
|
const positions = (this.accountState.positions ?? []).map((position) => this.mapPosition(position));
|
||||||
|
const totalUnrealized = positions.reduce((acc, pos) => acc + (Number(pos.unrealizedProfit) || 0), 0);
|
||||||
|
const assets: AsterAccountAsset[] = [
|
||||||
|
{
|
||||||
|
asset: balance.collateralName ?? "USDC",
|
||||||
|
walletBalance: balance.balance ?? "0",
|
||||||
|
availableBalance: balance.availableForTrade ?? balance.balance ?? "0",
|
||||||
|
updateTime: balance.updatedTime ?? Date.now(),
|
||||||
|
unrealizedProfit: balance.unrealisedPnl,
|
||||||
|
marginBalance: balance.equity,
|
||||||
|
maxWithdrawAmount: balance.availableForWithdrawal,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
canTrade: true,
|
||||||
|
canDeposit: true,
|
||||||
|
canWithdraw: true,
|
||||||
|
updateTime: balance.updatedTime ?? Date.now(),
|
||||||
|
totalWalletBalance: balance.balance ?? "0",
|
||||||
|
totalUnrealizedProfit: Number.isFinite(totalUnrealized) ? String(totalUnrealized) : "0",
|
||||||
|
availableBalance: balance.availableForTrade ?? balance.balance ?? "0",
|
||||||
|
maxWithdrawAmount: balance.availableForWithdrawal,
|
||||||
|
positions,
|
||||||
|
assets,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapPosition(position: ExtendedPosition): AsterAccountPosition {
|
||||||
|
const size = toDecimal(position.size);
|
||||||
|
const signed = position.side === "SHORT" ? size.negated() : size;
|
||||||
|
return {
|
||||||
|
symbol: position.market,
|
||||||
|
positionAmt: signed.toString(10),
|
||||||
|
entryPrice: position.openPrice ?? "0",
|
||||||
|
unrealizedProfit: position.unrealisedPnl ?? "0",
|
||||||
|
positionSide: position.side === "SHORT" ? "SHORT" : "LONG",
|
||||||
|
updateTime: position.updatedAt ?? Date.now(),
|
||||||
|
markPrice: position.markPrice,
|
||||||
|
leverage: position.leverage,
|
||||||
|
liquidationPrice: position.liquidationPrice,
|
||||||
|
marginType: "cross",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapOrder(order: ExtendedOrder): AsterOrder {
|
||||||
|
const stopPrice =
|
||||||
|
order.trigger?.triggerPrice ??
|
||||||
|
order.takeProfit?.triggerPrice ??
|
||||||
|
order.stopLoss?.triggerPrice ??
|
||||||
|
null;
|
||||||
|
return {
|
||||||
|
orderId: order.id,
|
||||||
|
clientOrderId: order.externalId ?? String(order.id),
|
||||||
|
symbol: order.market,
|
||||||
|
side: order.side,
|
||||||
|
type: this.normalizeOrderType(order.type),
|
||||||
|
status: order.status,
|
||||||
|
price: order.price ?? "0",
|
||||||
|
origQty: order.qty ?? "0",
|
||||||
|
executedQty: order.filledQty ?? "0",
|
||||||
|
stopPrice: stopPrice ? String(stopPrice) : "0",
|
||||||
|
time: order.createdTime ?? Date.now(),
|
||||||
|
updateTime: order.updatedTime ?? order.createdTime ?? Date.now(),
|
||||||
|
reduceOnly: Boolean(order.reduceOnly),
|
||||||
|
closePosition: Boolean(order.reduceOnly),
|
||||||
|
workingType: order.trigger?.triggerPriceType,
|
||||||
|
activationPrice: stopPrice ? String(stopPrice) : undefined,
|
||||||
|
avgPrice: order.averagePrice ?? undefined,
|
||||||
|
cumQuote: order.payedFee,
|
||||||
|
timeInForce: order.type === "MARKET" ? "IOC" : "GTC",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapDepth(state: DepthState): AsterDepth {
|
||||||
|
return {
|
||||||
|
lastUpdateId: state.seq,
|
||||||
|
bids: state.bids,
|
||||||
|
asks: state.asks,
|
||||||
|
eventTime: state.ts,
|
||||||
|
tradeTime: state.ts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapKline(candle: ExtendedCandle): AsterKline {
|
||||||
|
return {
|
||||||
|
eventType: "kline",
|
||||||
|
eventTime: candle.T,
|
||||||
|
openTime: candle.T,
|
||||||
|
closeTime: candle.T,
|
||||||
|
interval: "stream",
|
||||||
|
open: candle.o,
|
||||||
|
high: candle.h,
|
||||||
|
low: candle.l,
|
||||||
|
close: candle.c,
|
||||||
|
volume: candle.v ?? "0",
|
||||||
|
numberOfTrades: 0,
|
||||||
|
isClosed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildTicker(): AsterTicker | null {
|
||||||
|
const lastPrice = this.lastTrade?.p ?? this.marketInfo?.marketStats?.lastPrice;
|
||||||
|
if (!lastPrice && !this.lastDepth) return null;
|
||||||
|
return {
|
||||||
|
symbol: this.options.market,
|
||||||
|
lastPrice: lastPrice ?? "0",
|
||||||
|
openPrice: this.marketInfo?.marketStats?.indexPrice ?? lastPrice ?? "0",
|
||||||
|
highPrice: this.marketInfo?.marketStats?.markPrice ?? lastPrice ?? "0",
|
||||||
|
lowPrice: this.marketInfo?.marketStats?.markPrice ?? lastPrice ?? "0",
|
||||||
|
volume: "0",
|
||||||
|
quoteVolume: "0",
|
||||||
|
bidPrice: this.lastDepth?.bids?.[0]?.[0],
|
||||||
|
askPrice: this.lastDepth?.asks?.[0]?.[0],
|
||||||
|
eventTime: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private estimateMarketPrice(side: "BUY" | "SELL", fallback?: number): number | undefined {
|
||||||
|
const bestBid = Number(this.lastDepth?.bids?.[0]?.[0]);
|
||||||
|
const bestAsk = Number(this.lastDepth?.asks?.[0]?.[0]);
|
||||||
|
if (side === "BUY" && Number.isFinite(bestAsk)) return bestAsk;
|
||||||
|
if (side === "SELL" && Number.isFinite(bestBid)) return bestBid;
|
||||||
|
if (Number.isFinite(fallback)) return fallback;
|
||||||
|
const last = Number(this.lastTrade?.p ?? this.marketInfo?.marketStats?.lastPrice);
|
||||||
|
return Number.isFinite(last) ? last : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeOrderType(type: string): AsterOrder["type"] {
|
||||||
|
const normalized = type.toUpperCase();
|
||||||
|
if (normalized === "LIMIT" || normalized === "MARKET") return normalized as any;
|
||||||
|
return "STOP_MARKET";
|
||||||
|
}
|
||||||
|
|
||||||
|
private logInfo(context: string, message: unknown): void {
|
||||||
|
console.info(`[ExtendedGateway] ${context}: ${message as string}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private logDebug(context: string, message: unknown): void {
|
||||||
|
if (process.env.EXTENDED_DEBUG === "1" || process.env.EXTENDED_DEBUG === "true") {
|
||||||
|
console.debug(`[ExtendedGateway] ${context}: ${message as string}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private logError(context: string, error: unknown): void {
|
||||||
|
this.logger(context, error);
|
||||||
|
if (process.env.EXTENDED_DEBUG === "1" || process.env.EXTENDED_DEBUG === "true") {
|
||||||
|
console.error(`[ExtendedGateway] ${context}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import BigNumber from "bignumber.js";
|
||||||
|
|
||||||
|
export const Decimal = BigNumber;
|
||||||
|
export type Decimal = BigNumber;
|
||||||
|
export type Long = BigNumber;
|
||||||
|
export type RoundingMode = BigNumber.RoundingMode;
|
||||||
|
|
||||||
|
export function toDecimal(value: string | number | BigNumber): Decimal {
|
||||||
|
return new Decimal(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toLong(value: string | number | BigNumber): Long {
|
||||||
|
return new Decimal(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function roundToStep(value: Decimal, step: Decimal, mode: RoundingMode = Decimal.ROUND_DOWN): Decimal {
|
||||||
|
if (step.lte(0)) return value;
|
||||||
|
return value
|
||||||
|
.div(step)
|
||||||
|
.decimalPlaces(0, mode)
|
||||||
|
.times(step)
|
||||||
|
.decimalPlaces(step.decimalPlaces());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidNumber(value: unknown): value is number {
|
||||||
|
return typeof value === "number" && Number.isFinite(value);
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
import type { CreateOrderParams, OrderType } from "../types";
|
||||||
|
import { Decimal, roundToStep, toDecimal, toLong } from "./math";
|
||||||
|
import { calcStarknetExpiration, generateNonce, getStarkPublicKey, getStarknetOrderMsgHash, signMessageHash, toHexString } from "./signing";
|
||||||
|
import type { ExtendedFees, ExtendedMarket, ExtendedStarknetDomain } from "./types";
|
||||||
|
|
||||||
|
export interface ExtendedOrderContext {
|
||||||
|
market: ExtendedMarket;
|
||||||
|
fees: ExtendedFees;
|
||||||
|
domain: ExtendedStarknetDomain;
|
||||||
|
vaultId: string;
|
||||||
|
starkPrivateKey: string;
|
||||||
|
builderId?: string;
|
||||||
|
builderFeeRate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BuiltOrderPayload {
|
||||||
|
payload: Record<string, unknown>;
|
||||||
|
orderId: string;
|
||||||
|
params: CreateOrderParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTimeInForce(input?: CreateOrderParams["timeInForce"]): "GTT" | "IOC" {
|
||||||
|
if (!input) return "GTT";
|
||||||
|
const normalized = input.toString().toUpperCase();
|
||||||
|
if (normalized === "IOC" || normalized === "FOK") return "IOC";
|
||||||
|
return "GTT";
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveTotalFeeRate(ctx: ExtendedOrderContext, postOnly?: boolean): Decimal {
|
||||||
|
const maker = toDecimal(ctx.fees.makerFeeRate);
|
||||||
|
const taker = toDecimal(ctx.fees.takerFeeRate);
|
||||||
|
const builderRate = ctx.builderFeeRate ? toDecimal(ctx.builderFeeRate) : null;
|
||||||
|
const base = postOnly ? maker : Decimal.max(maker, taker);
|
||||||
|
return builderRate ? base.plus(builderRate) : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveOrderType(input: OrderType, stopPrice?: number | undefined): { type: "LIMIT" | "MARKET" | "CONDITIONAL"; triggerPrice?: Decimal } {
|
||||||
|
if (input === "MARKET") {
|
||||||
|
return { type: "MARKET" };
|
||||||
|
}
|
||||||
|
if (input === "STOP" || input === "STOP_MARKET" || input === "TAKE_PROFIT" || input === "TAKE_PROFIT_MARKET") {
|
||||||
|
const triggerPrice = stopPrice !== undefined ? toDecimal(stopPrice) : undefined;
|
||||||
|
return { type: "CONDITIONAL", triggerPrice };
|
||||||
|
}
|
||||||
|
return { type: "LIMIT" };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtendedOrderBuilder {
|
||||||
|
private readonly ctx: ExtendedOrderContext;
|
||||||
|
|
||||||
|
constructor(context: ExtendedOrderContext) {
|
||||||
|
this.ctx = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
build(params: CreateOrderParams & { marketPrice?: number; now?: number }): BuiltOrderPayload {
|
||||||
|
const market = this.ctx.market;
|
||||||
|
const l2 = market.l2Config;
|
||||||
|
const trading = market.tradingConfig;
|
||||||
|
const isPostOnly = params.timeInForce === "GTX";
|
||||||
|
const timeInForce = resolveTimeInForce(isPostOnly ? "GTT" : params.timeInForce);
|
||||||
|
const now = params.now ?? Date.now();
|
||||||
|
const expiryEpochMillis = now + 60 * 60 * 1000;
|
||||||
|
const nonce = toLong(generateNonce());
|
||||||
|
|
||||||
|
const minPriceChange = toDecimal(trading.minPriceChange);
|
||||||
|
const minQtyChange = toDecimal(trading.minOrderSizeChange);
|
||||||
|
const qty = roundToStep(toDecimal(params.quantity ?? 0), minQtyChange, Decimal.ROUND_DOWN);
|
||||||
|
|
||||||
|
const orderType = resolveOrderType(params.type, params.stopPrice);
|
||||||
|
const basePrice = this.resolvePrice(params, orderType, params.marketPrice, minPriceChange);
|
||||||
|
const price = roundToStep(basePrice, minPriceChange, params.side === "BUY" ? Decimal.ROUND_UP : Decimal.ROUND_DOWN);
|
||||||
|
|
||||||
|
const totalFeeRate = resolveTotalFeeRate(this.ctx, isPostOnly);
|
||||||
|
|
||||||
|
const collateralAmount = qty.times(price);
|
||||||
|
const fee = totalFeeRate.times(collateralAmount);
|
||||||
|
|
||||||
|
const roundingMode = params.side === "BUY" ? Decimal.ROUND_UP : Decimal.ROUND_DOWN;
|
||||||
|
const collateralAmountStark = collateralAmount.times(l2.collateralResolution).integerValue(roundingMode);
|
||||||
|
const feeStark = fee.times(l2.collateralResolution).integerValue(Decimal.ROUND_UP);
|
||||||
|
const syntheticAmountStark = qty.times(l2.syntheticResolution).integerValue(roundingMode);
|
||||||
|
|
||||||
|
const expiration = calcStarknetExpiration(expiryEpochMillis).toString(10);
|
||||||
|
const starkPublicKey = toHexString(getStarkPublicKey(this.ctx.starkPrivateKey as any));
|
||||||
|
const starknetOrderHash = getStarknetOrderMsgHash({
|
||||||
|
positionId: toLong(this.ctx.vaultId).toString(10),
|
||||||
|
baseAssetIdHex: toHexString(l2.syntheticId),
|
||||||
|
baseAmount: syntheticAmountStark.toString(10),
|
||||||
|
quoteAssetIdHex: toHexString(l2.collateralId),
|
||||||
|
quoteAmount: collateralAmountStark.toString(10),
|
||||||
|
feeAssetIdHex: toHexString(l2.collateralId),
|
||||||
|
feeAmount: feeStark.toString(10),
|
||||||
|
expiration,
|
||||||
|
salt: nonce.toString(10),
|
||||||
|
starkPublicKey,
|
||||||
|
domain: this.ctx.domain,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signature = signMessageHash(starknetOrderHash, this.ctx.starkPrivateKey as any);
|
||||||
|
const reduceOnly = params.reduceOnly === "true" || params.reduceOnly === true;
|
||||||
|
const payload: Record<string, unknown> = {
|
||||||
|
id: starknetOrderHash,
|
||||||
|
market: market.name,
|
||||||
|
type: orderType.type,
|
||||||
|
side: params.side,
|
||||||
|
qty: qty.toString(10),
|
||||||
|
price: price.toString(10),
|
||||||
|
timeInForce,
|
||||||
|
expiryEpochMillis,
|
||||||
|
fee: totalFeeRate.toString(10),
|
||||||
|
nonce: nonce.toString(10),
|
||||||
|
reduceOnly,
|
||||||
|
postOnly: isPostOnly,
|
||||||
|
settlement: {
|
||||||
|
signature: {
|
||||||
|
r: toHexString(signature.signature.r),
|
||||||
|
s: toHexString(signature.signature.s),
|
||||||
|
},
|
||||||
|
starkKey: toHexString(signature.starkKey),
|
||||||
|
collateralPosition: toLong(this.ctx.vaultId).toString(10),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (orderType.type === "CONDITIONAL" && orderType.triggerPrice) {
|
||||||
|
const direction = params.side === "BUY" ? "UP" : "DOWN";
|
||||||
|
payload.trigger = {
|
||||||
|
triggerPrice: orderType.triggerPrice.toString(10),
|
||||||
|
triggerPriceType: params.triggerType ?? "LAST",
|
||||||
|
direction,
|
||||||
|
triggerPriceDirection: direction,
|
||||||
|
executionPriceType: params.type === "STOP_MARKET" || params.type === "TAKE_PROFIT_MARKET" ? "MARKET" : "LIMIT",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
payload,
|
||||||
|
orderId: starknetOrderHash,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolvePrice(
|
||||||
|
params: CreateOrderParams,
|
||||||
|
orderType: { type: "LIMIT" | "MARKET" | "CONDITIONAL"; triggerPrice?: Decimal },
|
||||||
|
marketPrice: number | undefined,
|
||||||
|
minPriceChange: Decimal
|
||||||
|
): Decimal {
|
||||||
|
if (params.price !== undefined && params.price !== null) {
|
||||||
|
return toDecimal(params.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackPrice = Number.isFinite(marketPrice) ? marketPrice ?? NaN : NaN;
|
||||||
|
if (orderType.type === "MARKET") {
|
||||||
|
const basis = Number.isFinite(fallbackPrice) ? fallbackPrice! : minPriceChange.toNumber() || 1;
|
||||||
|
const factor = params.side === "BUY" ? 1.0075 : 0.9925;
|
||||||
|
return roundToStep(toDecimal(basis * factor), minPriceChange, params.side === "BUY" ? Decimal.ROUND_UP : Decimal.ROUND_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderType.type === "CONDITIONAL" && orderType.triggerPrice) {
|
||||||
|
return orderType.triggerPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = Number.isFinite(fallbackPrice) ? fallbackPrice! : minPriceChange.toNumber() || 1;
|
||||||
|
return toDecimal(base);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import type { AsterOrder, CreateOrderParams } from "../types";
|
||||||
|
import type {
|
||||||
|
BaseOrderIntent,
|
||||||
|
ClosePositionIntent,
|
||||||
|
LimitOrderIntent,
|
||||||
|
MarketOrderIntent,
|
||||||
|
StopOrderIntent,
|
||||||
|
TrailingStopOrderIntent,
|
||||||
|
} from "../order-schema";
|
||||||
|
import { toStringBoolean } from "../order-schema";
|
||||||
|
|
||||||
|
function applyCommonFields(params: CreateOrderParams, intent: BaseOrderIntent): CreateOrderParams {
|
||||||
|
if (params.quantity === undefined) {
|
||||||
|
params.quantity = intent.quantity;
|
||||||
|
}
|
||||||
|
if (params.timeInForce === undefined && intent.timeInForce) {
|
||||||
|
params.timeInForce = intent.timeInForce;
|
||||||
|
}
|
||||||
|
if (intent.reduceOnly !== undefined) {
|
||||||
|
params.reduceOnly = toStringBoolean(intent.reduceOnly);
|
||||||
|
}
|
||||||
|
if (intent.closePosition !== undefined) {
|
||||||
|
params.closePosition = toStringBoolean(intent.closePosition);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createLimitOrder(intent: LimitOrderIntent): Promise<AsterOrder> {
|
||||||
|
const params: CreateOrderParams = applyCommonFields(
|
||||||
|
{
|
||||||
|
symbol: intent.symbol,
|
||||||
|
side: intent.side,
|
||||||
|
type: "LIMIT",
|
||||||
|
quantity: intent.quantity,
|
||||||
|
price: intent.price,
|
||||||
|
timeInForce: intent.timeInForce ?? "GTC",
|
||||||
|
},
|
||||||
|
intent
|
||||||
|
);
|
||||||
|
return intent.adapter.createOrder(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createMarketOrder(intent: MarketOrderIntent): Promise<AsterOrder> {
|
||||||
|
const params: CreateOrderParams = applyCommonFields(
|
||||||
|
{
|
||||||
|
symbol: intent.symbol,
|
||||||
|
side: intent.side,
|
||||||
|
type: "MARKET",
|
||||||
|
quantity: intent.quantity,
|
||||||
|
price: intent.expectedPrice ?? undefined,
|
||||||
|
timeInForce: "IOC",
|
||||||
|
},
|
||||||
|
intent
|
||||||
|
);
|
||||||
|
return intent.adapter.createOrder(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createStopOrder(intent: StopOrderIntent): Promise<AsterOrder> {
|
||||||
|
const params: CreateOrderParams = applyCommonFields(
|
||||||
|
{
|
||||||
|
symbol: intent.symbol,
|
||||||
|
side: intent.side,
|
||||||
|
type: "STOP_MARKET",
|
||||||
|
quantity: intent.quantity,
|
||||||
|
stopPrice: intent.stopPrice,
|
||||||
|
price: intent.stopPrice,
|
||||||
|
timeInForce: intent.timeInForce ?? "GTC",
|
||||||
|
triggerType: intent.triggerType ?? (intent.side === "BUY" ? "TAKE_PROFIT" : "STOP_LOSS"),
|
||||||
|
closePosition: toStringBoolean(intent.closePosition ?? false),
|
||||||
|
reduceOnly: toStringBoolean(intent.reduceOnly ?? true),
|
||||||
|
},
|
||||||
|
intent
|
||||||
|
);
|
||||||
|
return intent.adapter.createOrder(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTrailingStopOrder(_intent: TrailingStopOrderIntent): Promise<AsterOrder> {
|
||||||
|
throw new Error("Extended exchange does not support trailing stop orders");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createClosePositionOrder(intent: ClosePositionIntent): Promise<AsterOrder> {
|
||||||
|
const params: CreateOrderParams = applyCommonFields(
|
||||||
|
{
|
||||||
|
symbol: intent.symbol,
|
||||||
|
side: intent.side,
|
||||||
|
type: "MARKET",
|
||||||
|
quantity: intent.quantity,
|
||||||
|
reduceOnly: "true",
|
||||||
|
closePosition: toStringBoolean(intent.closePosition ?? true),
|
||||||
|
timeInForce: "IOC",
|
||||||
|
},
|
||||||
|
intent
|
||||||
|
);
|
||||||
|
return intent.adapter.createOrder(params);
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { createRequire } from "module";
|
||||||
|
import path from "path";
|
||||||
|
import wasmInit, {
|
||||||
|
get_order_msg as wasmGetOrderMsgHash,
|
||||||
|
sign_message as wasmSignMessage,
|
||||||
|
} from "@x10xchange/stark-crypto-wrapper-wasm";
|
||||||
|
import { ec as starkEc, hash as starkHash, selector as starkSelector, shortString as starkShortString } from "starknet";
|
||||||
|
import type { ExtendedStarknetDomain } from "./types";
|
||||||
|
|
||||||
|
export type HexString = `0x${string}`;
|
||||||
|
|
||||||
|
export function isHexString(value: string | undefined | null): value is HexString {
|
||||||
|
return typeof value === "string" && value.startsWith("0x");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toHexString(value: string): HexString {
|
||||||
|
return (value.startsWith("0x") ? value : `0x${value}`) as HexString;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromHexString(value: HexString): string {
|
||||||
|
return value.slice(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tryInitExtendedWasm(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const wasmDir = path.dirname(require.resolve("@x10xchange/stark-crypto-wrapper-wasm"));
|
||||||
|
const wasmBuffer = readFileSync(path.join(wasmDir, "stark_crypto_wrapper_wasm_bg.wasm"));
|
||||||
|
await wasmInit({ module_or_path: wasmBuffer });
|
||||||
|
} catch (error) {
|
||||||
|
// Revert to JS implementation if wasm init fails; do not throw to keep bot running.
|
||||||
|
if (process.env.EXTENDED_DEBUG === "1" || process.env.EXTENDED_DEBUG === "true") {
|
||||||
|
console.warn("[ExtendedSigning] WASM init failed, falling back to JS:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const STARKNET_SETTLEMENT_BUFFER_SECONDS = 14 * 24 * 60 * 60;
|
||||||
|
const MILLIS_IN_SECOND = 1000;
|
||||||
|
|
||||||
|
export function calcStarknetExpiration(expiryEpochMillis: number): number {
|
||||||
|
return Math.ceil(expiryEpochMillis / MILLIS_IN_SECOND) + STARKNET_SETTLEMENT_BUFFER_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStarkPublicKey(privateKey: HexString): string {
|
||||||
|
return fromHexString(starkEc.starkCurve.getStarkKey(privateKey) as HexString);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signMessageHash(messageHash: string, starkPrivateKey: HexString): { signature: { r: string; s: string }; starkKey: string } {
|
||||||
|
const starkPublicKey = getStarkPublicKey(starkPrivateKey);
|
||||||
|
try {
|
||||||
|
const signature = wasmSignMessage(starkPrivateKey, messageHash);
|
||||||
|
const result = {
|
||||||
|
signature: {
|
||||||
|
r: fromHexString(signature.r as HexString),
|
||||||
|
s: fromHexString(signature.s as HexString),
|
||||||
|
},
|
||||||
|
starkKey: starkPublicKey,
|
||||||
|
};
|
||||||
|
if (typeof signature.free === "function") {
|
||||||
|
signature.free();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch {
|
||||||
|
const signature = starkEc.starkCurve.sign(messageHash, starkPrivateKey);
|
||||||
|
return {
|
||||||
|
signature: {
|
||||||
|
r: signature.r.toString(16),
|
||||||
|
s: signature.s.toString(16),
|
||||||
|
},
|
||||||
|
starkKey: starkPublicKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsGetObjMsgHash(domainHash: string, publicKey: string, objHash: string): string {
|
||||||
|
const messageFelt = starkShortString.encodeShortString("StarkNet Message");
|
||||||
|
return starkHash.computePoseidonHashOnElements([messageFelt, domainHash, publicKey, objHash]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsGetStarknetDomainObjHash(domain: ExtendedStarknetDomain): string {
|
||||||
|
const selector = starkSelector.getSelector(
|
||||||
|
'"StarknetDomain"("name":"shortstring","version":"shortstring","chainId":"shortstring","revision":"shortstring")'
|
||||||
|
);
|
||||||
|
return starkHash.computePoseidonHashOnElements([
|
||||||
|
selector,
|
||||||
|
starkShortString.encodeShortString(domain.name),
|
||||||
|
starkShortString.encodeShortString(domain.version),
|
||||||
|
starkShortString.encodeShortString(domain.chainId),
|
||||||
|
domain.revision,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function jsGetOrderMsgHash(
|
||||||
|
positionId: string,
|
||||||
|
baseAssetIdHex: string,
|
||||||
|
baseAmount: string,
|
||||||
|
quoteAssetIdHex: string,
|
||||||
|
quoteAmount: string,
|
||||||
|
feeAssetIdHex: string,
|
||||||
|
feeAmount: string,
|
||||||
|
expiration: string,
|
||||||
|
salt: string,
|
||||||
|
userPublicKeyHex: string,
|
||||||
|
domainName: string,
|
||||||
|
domainVersion: string,
|
||||||
|
domainChainId: string,
|
||||||
|
domainRevision: string
|
||||||
|
): string {
|
||||||
|
const domainHash = jsGetStarknetDomainObjHash({
|
||||||
|
name: domainName,
|
||||||
|
version: domainVersion,
|
||||||
|
chainId: domainChainId,
|
||||||
|
revision: parseInt(domainRevision, 10),
|
||||||
|
});
|
||||||
|
|
||||||
|
const orderSelector = starkSelector.getSelector(
|
||||||
|
'"Order"("position_id":"felt","base_asset_id":"AssetId","base_amount":"i64","quote_asset_id":"AssetId","quote_amount":"i64","fee_asset_id":"AssetId","fee_amount":"u64","expiration":"Timestamp","salt":"felt")"PositionId"("value":"u32")"AssetId"("value":"felt")"Timestamp"("seconds":"u64")'
|
||||||
|
);
|
||||||
|
const orderHash = starkHash.computePoseidonHashOnElements([
|
||||||
|
orderSelector,
|
||||||
|
positionId,
|
||||||
|
baseAssetIdHex,
|
||||||
|
baseAmount,
|
||||||
|
quoteAssetIdHex,
|
||||||
|
quoteAmount,
|
||||||
|
feeAssetIdHex,
|
||||||
|
feeAmount,
|
||||||
|
expiration,
|
||||||
|
salt,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return jsGetObjMsgHash(domainHash, userPublicKeyHex, orderHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStarknetOrderMsgHash(args: {
|
||||||
|
positionId: string;
|
||||||
|
baseAssetIdHex: string;
|
||||||
|
baseAmount: string;
|
||||||
|
quoteAssetIdHex: string;
|
||||||
|
quoteAmount: string;
|
||||||
|
feeAssetIdHex: string;
|
||||||
|
feeAmount: string;
|
||||||
|
expiration: string;
|
||||||
|
salt: string;
|
||||||
|
starkPublicKey: string;
|
||||||
|
domain: ExtendedStarknetDomain;
|
||||||
|
}): string {
|
||||||
|
const {
|
||||||
|
positionId,
|
||||||
|
baseAssetIdHex,
|
||||||
|
baseAmount,
|
||||||
|
quoteAssetIdHex,
|
||||||
|
quoteAmount,
|
||||||
|
feeAssetIdHex,
|
||||||
|
feeAmount,
|
||||||
|
expiration,
|
||||||
|
salt,
|
||||||
|
starkPublicKey,
|
||||||
|
domain,
|
||||||
|
} = args;
|
||||||
|
try {
|
||||||
|
const wasmHash = wasmGetOrderMsgHash(
|
||||||
|
positionId,
|
||||||
|
baseAssetIdHex,
|
||||||
|
baseAmount,
|
||||||
|
quoteAssetIdHex,
|
||||||
|
quoteAmount,
|
||||||
|
feeAssetIdHex,
|
||||||
|
feeAmount,
|
||||||
|
expiration,
|
||||||
|
salt,
|
||||||
|
starkPublicKey,
|
||||||
|
domain.name,
|
||||||
|
domain.version,
|
||||||
|
domain.chainId,
|
||||||
|
domain.revision.toString()
|
||||||
|
);
|
||||||
|
return fromHexString(wasmHash as HexString);
|
||||||
|
} catch (error) {
|
||||||
|
if (process.env.EXTENDED_DEBUG === "1" || process.env.EXTENDED_DEBUG === "true") {
|
||||||
|
console.warn("[ExtendedSigning] wasm order hash failed, using JS", error);
|
||||||
|
}
|
||||||
|
return jsGetOrderMsgHash(
|
||||||
|
positionId,
|
||||||
|
baseAssetIdHex,
|
||||||
|
baseAmount,
|
||||||
|
quoteAssetIdHex,
|
||||||
|
quoteAmount,
|
||||||
|
feeAssetIdHex,
|
||||||
|
feeAmount,
|
||||||
|
expiration,
|
||||||
|
salt,
|
||||||
|
starkPublicKey,
|
||||||
|
domain.name,
|
||||||
|
domain.version,
|
||||||
|
domain.chainId,
|
||||||
|
domain.revision.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateNonce(): number {
|
||||||
|
return Math.floor(Math.random() * (2 ** 31 - 1));
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
export interface ExtendedMarket {
|
||||||
|
name: string;
|
||||||
|
assetName?: string;
|
||||||
|
collateralAssetName?: string;
|
||||||
|
marketStats?: {
|
||||||
|
lastPrice?: string;
|
||||||
|
askPrice?: string;
|
||||||
|
bidPrice?: string;
|
||||||
|
markPrice?: string;
|
||||||
|
indexPrice?: string;
|
||||||
|
};
|
||||||
|
tradingConfig: {
|
||||||
|
minOrderSize: string;
|
||||||
|
minOrderSizeChange: string;
|
||||||
|
minPriceChange: string;
|
||||||
|
maxPositionValue: string;
|
||||||
|
};
|
||||||
|
l2Config: {
|
||||||
|
collateralId: string;
|
||||||
|
collateralResolution: number;
|
||||||
|
syntheticId: string;
|
||||||
|
syntheticResolution: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedFees {
|
||||||
|
market: string;
|
||||||
|
makerFeeRate: string;
|
||||||
|
takerFeeRate: string;
|
||||||
|
builderFeeRate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedBalance {
|
||||||
|
collateralName?: string;
|
||||||
|
balance: string;
|
||||||
|
equity?: string;
|
||||||
|
availableForTrade?: string;
|
||||||
|
availableForWithdrawal?: string;
|
||||||
|
unrealisedPnl?: string;
|
||||||
|
initialMargin?: string;
|
||||||
|
marginRatio?: string;
|
||||||
|
updatedTime?: number;
|
||||||
|
exposure?: string;
|
||||||
|
leverage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedPosition {
|
||||||
|
id?: number | string;
|
||||||
|
accountId?: number | string;
|
||||||
|
market: string;
|
||||||
|
side: "LONG" | "SHORT";
|
||||||
|
leverage?: string;
|
||||||
|
size: string;
|
||||||
|
value?: string;
|
||||||
|
openPrice?: string;
|
||||||
|
markPrice?: string;
|
||||||
|
liquidationPrice?: string;
|
||||||
|
margin?: string;
|
||||||
|
unrealisedPnl?: string;
|
||||||
|
realisedPnl?: string;
|
||||||
|
tpTriggerPrice?: string;
|
||||||
|
tpLimitPrice?: string;
|
||||||
|
slTriggerPrice?: string;
|
||||||
|
slLimitPrice?: string;
|
||||||
|
adl?: number;
|
||||||
|
createdAt?: number;
|
||||||
|
updatedAt?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedTrigger {
|
||||||
|
triggerPrice?: string;
|
||||||
|
triggerPriceType?: "LAST" | "MARK" | "INDEX";
|
||||||
|
triggerPriceDirection?: "UP" | "DOWN";
|
||||||
|
direction?: "UP" | "DOWN";
|
||||||
|
executionPriceType?: "LIMIT" | "MARKET";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedOrder {
|
||||||
|
id: string | number;
|
||||||
|
externalId?: string;
|
||||||
|
accountId?: string | number;
|
||||||
|
market: string;
|
||||||
|
type: "LIMIT" | "MARKET" | "CONDITIONAL" | "TPSL" | string;
|
||||||
|
side: "BUY" | "SELL";
|
||||||
|
status: string;
|
||||||
|
price?: string;
|
||||||
|
averagePrice?: string;
|
||||||
|
qty: string;
|
||||||
|
filledQty?: string;
|
||||||
|
payedFee?: string;
|
||||||
|
reduceOnly?: boolean;
|
||||||
|
postOnly?: boolean;
|
||||||
|
trigger?: ExtendedTrigger;
|
||||||
|
takeProfit?: {
|
||||||
|
triggerPrice?: string;
|
||||||
|
triggerPriceType?: ExtendedTrigger["triggerPriceType"];
|
||||||
|
price?: string;
|
||||||
|
priceType?: "LIMIT" | "MARKET";
|
||||||
|
};
|
||||||
|
stopLoss?: {
|
||||||
|
triggerPrice?: string;
|
||||||
|
triggerPriceType?: ExtendedTrigger["triggerPriceType"];
|
||||||
|
price?: string;
|
||||||
|
priceType?: "LIMIT" | "MARKET";
|
||||||
|
};
|
||||||
|
tpSlType?: "ORDER" | "POSITION";
|
||||||
|
createdTime: number;
|
||||||
|
updatedTime?: number;
|
||||||
|
expireTime?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedTrade {
|
||||||
|
m: string; // market
|
||||||
|
S: "BUY" | "SELL";
|
||||||
|
tT: string;
|
||||||
|
T: number;
|
||||||
|
p: string;
|
||||||
|
q: string;
|
||||||
|
i: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedCandle {
|
||||||
|
T: number;
|
||||||
|
o: string;
|
||||||
|
h: string;
|
||||||
|
l: string;
|
||||||
|
c: string;
|
||||||
|
v?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedStarknetDomain {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
chainId: string;
|
||||||
|
revision: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedDepthLevel {
|
||||||
|
p: string;
|
||||||
|
q: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedDepthMessage {
|
||||||
|
ts: number;
|
||||||
|
type?: "SNAPSHOT" | "DELTA";
|
||||||
|
data: {
|
||||||
|
m: string;
|
||||||
|
b?: ExtendedDepthLevel[];
|
||||||
|
a?: ExtendedDepthLevel[];
|
||||||
|
};
|
||||||
|
seq?: number;
|
||||||
|
}
|
||||||
@@ -13,8 +13,9 @@ import * as backpackOrders from "./backpack/order";
|
|||||||
import * as grvtOrders from "./grvt/order";
|
import * as grvtOrders from "./grvt/order";
|
||||||
import * as lighterOrders from "./lighter/order";
|
import * as lighterOrders from "./lighter/order";
|
||||||
import * as paradexOrders from "./paradex/order";
|
import * as paradexOrders from "./paradex/order";
|
||||||
|
import * as extendedOrders from "./extended/order";
|
||||||
|
|
||||||
type ExchangeKey = "aster" | "backpack" | "grvt" | "lighter" | "paradex";
|
type ExchangeKey = "aster" | "backpack" | "grvt" | "lighter" | "paradex" | "extended";
|
||||||
|
|
||||||
interface ExchangeOrderHandlers {
|
interface ExchangeOrderHandlers {
|
||||||
limit(intent: LimitOrderIntent): Promise<AsterOrder>;
|
limit(intent: LimitOrderIntent): Promise<AsterOrder>;
|
||||||
@@ -60,9 +61,16 @@ const handlerMap: Record<ExchangeKey, ExchangeOrderHandlers> = {
|
|||||||
trailingStop: paradexOrders.createTrailingStopOrder,
|
trailingStop: paradexOrders.createTrailingStopOrder,
|
||||||
close: paradexOrders.createClosePositionOrder,
|
close: paradexOrders.createClosePositionOrder,
|
||||||
},
|
},
|
||||||
|
extended: {
|
||||||
|
limit: extendedOrders.createLimitOrder,
|
||||||
|
market: extendedOrders.createMarketOrder,
|
||||||
|
stop: extendedOrders.createStopOrder,
|
||||||
|
trailingStop: extendedOrders.createTrailingStopOrder,
|
||||||
|
close: extendedOrders.createClosePositionOrder,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const knownExchanges: ExchangeKey[] = ["aster", "backpack", "grvt", "lighter", "paradex"];
|
const knownExchanges: ExchangeKey[] = ["aster", "backpack", "grvt", "lighter", "paradex", "extended"];
|
||||||
|
|
||||||
function normalizeExchangeId(value: string | undefined | null): string | undefined {
|
function normalizeExchangeId(value: string | undefined | null): string | undefined {
|
||||||
if (!value) return undefined;
|
if (!value) return undefined;
|
||||||
@@ -115,4 +123,3 @@ export function routeTrailingStopOrder(intent: TrailingStopOrderIntent): Promise
|
|||||||
export function routeCloseOrder(intent: ClosePositionIntent): Promise<AsterOrder> {
|
export function routeCloseOrder(intent: ClosePositionIntent): Promise<AsterOrder> {
|
||||||
return getHandlers(intent).close(intent);
|
return getHandlers(intent).close(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { AsterCredentials } from "./aster-adapter";
|
|||||||
import type { LighterCredentials } from "./lighter/adapter";
|
import type { LighterCredentials } from "./lighter/adapter";
|
||||||
import type { BackpackCredentials } from "./backpack/adapter";
|
import type { BackpackCredentials } from "./backpack/adapter";
|
||||||
import type { ParadexCredentials } from "./paradex/adapter";
|
import type { ParadexCredentials } from "./paradex/adapter";
|
||||||
|
import type { ExtendedCredentials } from "./extended/adapter";
|
||||||
|
|
||||||
interface BuildAdapterOptions {
|
interface BuildAdapterOptions {
|
||||||
symbol: string;
|
symbol: string;
|
||||||
@@ -34,6 +35,11 @@ export function buildAdapterFromEnv(options: BuildAdapterOptions): ExchangeAdapt
|
|||||||
return createExchangeAdapter({ exchange: id, symbol, paradex: credentials });
|
return createExchangeAdapter({ exchange: id, symbol, paradex: credentials });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id === "extended") {
|
||||||
|
const credentials = resolveExtendedCredentials(symbol);
|
||||||
|
return createExchangeAdapter({ exchange: id, symbol, extended: credentials });
|
||||||
|
}
|
||||||
|
|
||||||
return createExchangeAdapter({ exchange: id, symbol, grvt: { symbol } });
|
return createExchangeAdapter({ exchange: id, symbol, grvt: { symbol } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +120,25 @@ function resolveParadexCredentials(): ParadexCredentials {
|
|||||||
return credentials;
|
return credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveExtendedCredentials(symbol: string): ExtendedCredentials {
|
||||||
|
const apiKey = process.env.EXTENDED_API_KEY;
|
||||||
|
const starkPrivateKey = process.env.EXTENDED_STARK_PRIVATE_KEY;
|
||||||
|
const vaultId = process.env.EXTENDED_VAULT_ID;
|
||||||
|
if (!apiKey || !starkPrivateKey || !vaultId) {
|
||||||
|
throw new Error("缺少 EXTENDED_API_KEY / EXTENDED_STARK_PRIVATE_KEY / EXTENDED_VAULT_ID 环境变量");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
apiKey,
|
||||||
|
starkPrivateKey,
|
||||||
|
vaultId,
|
||||||
|
market: process.env.EXTENDED_MARKET ?? symbol,
|
||||||
|
apiHost: process.env.EXTENDED_API_HOST,
|
||||||
|
streamHost: process.env.EXTENDED_STREAM_HOST,
|
||||||
|
privateStreamHost: process.env.EXTENDED_PRIVATE_STREAM_HOST,
|
||||||
|
userAgent: process.env.EXTENDED_USER_AGENT,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function isHex32(value: string): boolean {
|
function isHex32(value: string): boolean {
|
||||||
return /^0x[0-9a-fA-F]{64}$/.test(value.trim());
|
return /^0x[0-9a-fA-F]{64}$/.test(value.trim());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user