-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScriptable GMX.scriptable
More file actions
10 lines (10 loc) · 7.08 KB
/
Scriptable GMX.scriptable
File metadata and controls
10 lines (10 loc) · 7.08 KB
1
2
3
4
5
6
7
8
9
10
{
"always_run_in_app": false,
"icon": {
"color": "deep-blue",
"glyph": "chart-line"
},
"name": "Scriptable GMX",
"script": "// Variables used by Scriptable.\n// These must be at the very top of the file. Do not edit.\n// icon-color: deep-blue; icon-glyph: chart-line;\n// @ts-check\n\n/**\n * @typedef {Object} Position\n * @property {string} id\n * @property {string} sizeInUsd\n * @property {string} market\n * @property {number} pnl\n * @property {boolean} isLong\n */\n\n/**\n * @typedef {Object} Market\n * @property {string} name\n * @property {string} marketToken\n * @property {string} indexToken\n * @property {string} longToken\n * @property {string} shortToken\n */\n\nconst ALMOST_PRECISION = BigInt(10) ** BigInt(28);\n\nconst logo = Image.fromData(\n Data.fromBase64String(\n \"iVBORw0KGgoAAAANSUhEUgAAAB4AAAAVCAYAAABR915hAAADwUlEQVRIS7WVW0hUQRjHZ+acXYs1NaIlL62aaVmbl1TsQuZDCRZIUZTSiwiaFtVDSTcoqdCCiii0yAdDI6IeIiMqgy4+WJQutbpewkspsVnodlsx3TPTd87Zs3t2zVyX8mVxzsz/913+3wxGfv4FmN7mOIgQLCQtr/NHAvtzKLDRNNfBcRbKYW6MHzOi1FTrdHX8AuseW+4zjmYxjiHGo+ejqxPW/XdwyF1LASXoKuMQAjCT4XjfSKaxajrwaWU8u9ZiwAGkFaA6RgAoQiEAxLFRrNEmfFsX1+sr3HcwY1hf09XIOLxSAcq/DFEpe2r63rJ0BSrD1Be4z+Dwi937Kc/OeEIRQFWZE3rMtmVp+T8DR5V3LRa0vAnKqvXMUi61soY45BA4lmbbFt86FXzKjFOKmjXD4XOaAWCUzeTMklAGphqHNWcwcgCIR+1BNpLWvTf219/gU4JjD74/CX094u6lUlp8ghJhBPG4Qm00sfQAPztYEHfIb/Dikg8pWEteghiRDeQqrdlA+tKeHc8UIk/3ugwn9xv2QTUEDmd8Lo59MRl80oyj8vtmBGq0ZhCKURsIshtDSEh+dy66UxRdUNZjYAS3wS2mc821NGK0f3xsZNlgaaL9T/BJwUl51krKo+IJo0NQaWdlxHm1WMzRD0Ww77Jqr+QFCLJ64EBMic/g1E3WTMqRJ3BQFnCODLi2yVwTmoEQZvG7BzZApvrOSxHXROG40v4HMG7yNeo8I7aHEpY9cHhBgzd8Qsbp2UNBiNEOKG+oGwrHCPuJGTW23A7rTynqCf4lzOwEiI5wjqTWy5G9C/dY53IaxztYCxLN5vIEj74IWhzffzjSpoZPAK9aM3QTgNuULBU4InTn6/p51eLh5FxrLQjvcH4ztUaGpos3Vvyuj9vh+rzh4QmpAvRWz6novEnBa1KHcxHBN7z7CvP66GXDnGzxYErOl41gnHuuRwKygxacfHM9tEz8biz8eAfAOepqSaOoQZu7Kwz1CtyV8fpF9jAH72iDTSHeh2DNDK61QeRivxPg+2x1cJAlFTgh483N8Kak/L4QigPA5Z6tgv1fMRKMHReipLfbDY7+8RQE13qX2DWbYsmI22hqAznPDIzPYkvMdfPsiTsGsxCmD5TgFA1oV0N71XypchI4W28vAaEqFZR5AFVOVbvc61KBd5nVvr6nzxc1l28dvAL/F0o66oAJLrZUh1XjTcGjMQ7ExBLPcGeh3ozkx94LPtFA8hhRnm5/9VB/OyHrk06jwxaAGpQ73qljFxgy/gZN22TBlCSCsAAAAABJRU5ErkJggg==\"\n )\n);\nlogo.size = new Size(16, 16);\n\nconst colors = {\n bg: \"#08091b\",\n green: config.runsInAccessoryWidget ? \"#ffffff\" : \"#0fde8d\",\n red: config.runsInAccessoryWidget ? \"#ffffff\" : \"#ff506a\",\n text: \"#ffffff\",\n secondaryText: config.runsInAccessoryWidget ? \"#ffffff\" : \"#a0a3c4\",\n};\n\n/**\n * @param {string} name\n * @returns {string}\n */\nfunction reformatMarketName(name) {\n return name.split(\"[\")[0].trim();\n}\n\n/**\n * @param {string} name\n * @returns {string}\n */\nfunction getPoolNameFromMarketName(name) {\n return name.replace(/^.*\\[/, \"\").replace(/\\]$/, \"\");\n}\n\n/**\n * @param {string} name\n * @returns {string}\n */\nfunction getIndexTokenFromMarketName(name) {\n return name.split(\"/\")[0];\n}\n\n/**\n * @param {string} account\n * @returns {Promise<Position[]>}\n */\nasync function fetchPositionsForAccount(account) {\n const request = new Request(\n \"https://gmx.squids.live/gmx-synthetics-arbitrum:prod/api/graphql\"\n );\n\n const query = /* gql */ `\n query ($account: String!, $limit: Int!) {\n positions(\n where: {account_eq: $account, isSnapshot_eq: false},\n orderBy: unrealizedPnl_DESC,\n limit: $limit\n ) {\n id\n sizeInUsd\n market\n unrealizedPnl\n unrealizedFees\n isLong\n }\n }`;\n\n const bodyString = JSON.stringify({\n query,\n variables: {\n account,\n limit: isAccessoryCircular ? 1 : 7,\n },\n });\n\n request.headers = {\n \"Content-Type\": \"application/json\",\n };\n request.body = bodyString;\n request.method = \"POST\";\n\n console.log(\"Sending request...\");\n const response = await request.loadJSON();\n\n // convert pnl\n\n const positions = response.data.positions.map((position) => ({\n ...position,\n pnl:\n Number(\n (BigInt(position.unrealizedPnl) - BigInt(position.unrealizedFees)) /\n ALMOST_PRECISION\n ) / 100,\n }));\n\n return positions;\n}\n\n/**\n * @returns {Promise<Market[]>}\n */\nasync function fetchMarets() {\n const request = new Request(\"https://arbitrum-api.gmxinfra.io/markets\");\n const response = await request.loadJSON();\n return response.markets;\n}\n\n/**\n * @param {ListWidget} widget\n * @returns {Promise<ListWidget>}\n */\nasync function fetchData(widget) {\n try {\n const account =\n args.widgetParameter ?? \"0x8918F029ce357837294D71B2270eD403aac0eEc8\";\n\n if (!account) {\n throw new Error(\"No account provided\");\n }\n\n const [positions, markets] = await Promise.all([\n fetchPositionsForAccount(account),\n fetchMarets(),\n ]);\n\n const vStack = widget.addStack();\n vStack.layoutVertically();\n\n for (const position of positions) {\n const horizontalStack = vStack.addStack();\n horizontalStack.layoutHorizontally();\n horizontalStack.centerAlignContent();\n const market = markets.find((m) => m.marketToken === position.market);\n\n horizontalStack.url = `https://metamask.app.link/dapp/https://app.gmx.io/#/trade/${\n position.isLong ? \"long\" : \"short\"\n }?pool=${getPoolNameFromMarketName(\n market.name\n )}&to=${getIndexTokenFromMarketName(market.name)}&mode=market`;\n\n const sign = position.pnl > 0 ? \"+\" : \"-\";\n const pnl = Math.abs(position.pnl);\n\n const string = `${sign}$${pnl.toFixed(2)} `;\n const pnlText = horizontalStack.addText(string);\n\n pnlText.font = Font.regularSystemFont(14);\n if (position.pnl > 0) {\n pnlText.textColor = new Color(colors.green);\n } else {\n pnlText.textColor = new Color(colors.red);\n }\n\n if (!isAccessoryCircular) {\n const marketText = horizontalStack.addText(\n reformatMarketName(market.name)\n );\n marketText.font = Font.lightSystemFont(12);\n marketText.textColor = new Color(colors.secondaryText);\n }\n }\n\n return widget;\n } catch (error) {\n console.error(`Error occurred: ${error.message}`);\n console.error(`Error stack: ${error.stack}`);\n\n // Create a new widget for the error\n\n const errorText = widget.addText(\"Error: \" + error.message);\n errorText.font = Font.regularSystemFont(14);\n errorText.textColor = new Color(colors.red);\n errorText.centerAlignText();\n\n return widget;\n }\n}\n\nconst widget = new ListWidget();\nconst isAccessory = config.runsInAccessoryWidget;\nconst isAccessoryCircular = config.widgetFamily === \"accessoryCircular\";\nif (!isAccessory) {\n widget.setPadding(8, 10, 8, 10);\n widget.backgroundColor = new Color(colors.bg);\n}\n\nconst image = widget.addImage(logo);\nimage.imageSize = new Size(16, 16);\n\n// Execute the request\nfetchData(widget).then((widget) => {\n if (!isAccessoryCircular) {\n widget.addSpacer();\n }\n if (config.runsInWidget) {\n Script.setWidget(widget);\n } else {\n widget.presentAccessoryCircular();\n }\n Script.complete();\n});\n",
"share_sheet_inputs": []
}