tangled
alpha
login
or
join now
ciaran.co.za
/
cumulus
0
fork
atom
A Prediction Market on the AT Protocol
0
fork
atom
overview
issues
pulls
pipelines
feat(app.tsx): chart demo
Ciaran
2 weeks ago
ad7686ef
f5b4e5c3
+492
-33
6 changed files
expand all
collapse all
unified
split
bun.lock
index.ts
package.json
src
web
app.tsx
components
ui
chart.tsx
lib
lmsr.ts
+73
bun.lock
···
29
29
"lucide-react": "^0.577.0",
30
30
"pg": "^8.19.0",
31
31
"radix-ui": "^1.4.3",
32
32
+
"recharts": "2.15.4",
32
33
"tailwind-merge": "^3.5.0",
33
34
"tailwindcss": "^4.2.1",
34
35
"usehooks-ts": "^3.1.1",
···
168
169
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
169
170
170
171
"@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="],
172
172
+
173
173
+
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
171
174
172
175
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
173
176
···
627
630
628
631
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
629
632
633
633
+
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
634
634
+
635
635
+
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
636
636
+
637
637
+
"@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
638
638
+
639
639
+
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
640
640
+
641
641
+
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
642
642
+
643
643
+
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
644
644
+
645
645
+
"@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="],
646
646
+
647
647
+
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
648
648
+
649
649
+
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
650
650
+
630
651
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
631
652
632
653
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
···
757
778
758
779
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
759
780
781
781
+
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
782
782
+
783
783
+
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
784
784
+
785
785
+
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
786
786
+
787
787
+
"d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
788
788
+
789
789
+
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
790
790
+
791
791
+
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
792
792
+
793
793
+
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
794
794
+
795
795
+
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
796
796
+
797
797
+
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
798
798
+
799
799
+
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
800
800
+
801
801
+
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
802
802
+
760
803
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
761
804
762
805
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
763
806
764
807
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
808
808
+
809
809
+
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
765
810
766
811
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
767
812
···
781
826
782
827
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
783
828
829
829
+
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
830
830
+
784
831
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
785
832
786
833
"drizzle-kit": ["drizzle-kit@0.31.9", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg=="],
···
835
882
836
883
"event-target-polyfill": ["event-target-polyfill@0.0.4", "", {}, "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ=="],
837
884
885
885
+
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
886
886
+
838
887
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
839
888
840
889
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
···
852
901
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
853
902
854
903
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
904
904
+
905
905
+
"fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="],
855
906
856
907
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
857
908
···
939
990
940
991
"inherits": ["inherits@2.0.3", "", {}, "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="],
941
992
993
993
+
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
994
994
+
942
995
"ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="],
943
996
944
997
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
···
1026
1079
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
1027
1080
1028
1081
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
1082
1082
+
1083
1083
+
"lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
1029
1084
1030
1085
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
1031
1086
1032
1087
"log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
1033
1088
1089
1089
+
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
1090
1090
+
1034
1091
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
1035
1092
1036
1093
"lucide-react": ["lucide-react@0.577.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A=="],
···
1173
1230
1174
1231
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
1175
1232
1233
1233
+
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
1234
1234
+
1176
1235
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
1177
1236
1178
1237
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
···
1189
1248
1190
1249
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
1191
1250
1251
1251
+
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
1252
1252
+
1192
1253
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
1193
1254
1194
1255
"react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
1195
1256
1196
1257
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
1197
1258
1259
1259
+
"react-smooth": ["react-smooth@4.0.4", "", { "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q=="],
1260
1260
+
1198
1261
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
1199
1262
1263
1263
+
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
1264
1264
+
1200
1265
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
1266
1266
+
1267
1267
+
"recharts": ["recharts@2.15.4", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw=="],
1268
1268
+
1269
1269
+
"recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="],
1201
1270
1202
1271
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
1203
1272
···
1367
1436
1368
1437
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
1369
1438
1439
1439
+
"victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="],
1440
1440
+
1370
1441
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.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-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
1371
1442
1372
1443
"vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="],
···
1462
1533
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
1463
1534
1464
1535
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
1536
1536
+
1537
1537
+
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
1465
1538
1466
1539
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
1467
1540
+7
-5
index.ts
···
1
1
import type { ActorIdentifier } from "@atcute/lexicons";
2
2
-
import { createBet, createMarket, createResolution } from "./src/core";
2
2
+
import { createBet, createMarket } from "./src/core";
3
3
import { Client } from '@atcute/client';
4
4
import { PasswordSession } from "@atcute/password-session";
5
5
import { ComAtprotoRepoStrongRef } from "@atcute/atproto";
···
27
27
}
28
28
console.log("marketRef", marketRef);
29
29
30
30
-
const betRecord = await createBet(marketRef, "yes", creds.handle, client);
31
31
-
console.log("bet", betRecord);
30
30
+
for (let i = 0; i < 30; i++) {
31
31
+
const position = Math.random() > 0.5 ? "yes" : "no";
32
32
+
const bet = await createBet(marketRef, position, creds.handle, client);
33
33
+
console.log(`Seeded bet ${i + 1}/30 (${position})`, bet.uri);
34
34
+
}
32
35
33
33
-
const resolutionRecord = await createResolution(marketRef, "yes", creds.handle, client);
34
34
-
console.log("resolution", resolutionRecord);
36
36
+
console.log("Done seeding 30 bets.");
+1
package.json
···
66
66
"lucide-react": "^0.577.0",
67
67
"pg": "^8.19.0",
68
68
"radix-ui": "^1.4.3",
69
69
+
"recharts": "2.15.4",
69
70
"tailwind-merge": "^3.5.0",
70
71
"tailwindcss": "^4.2.1",
71
72
"usehooks-ts": "^3.1.1"
+49
-28
src/web/app.tsx
···
1
1
import { useCumulus } from "./providers/useCumulus";
2
2
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./components/ui/card";
3
3
-
import { formatDistance } from "date-fns"
2
2
+
import { formatDistance, getUnixTime } from "date-fns"
4
3
import { Spinner } from "./components/ui/spinner";
4
4
+
import { LineChart, Line, XAxis, CartesianGrid } from "recharts";
5
5
+
import { ChartContainer, ChartTooltip } from "./components/ui/chart";
6
6
+
import { noPrice, yesPrice } from "./lib/lmsr";
5
7
6
8
export default function App() {
7
7
-
return <div className="p-4 space-y-4">
8
8
-
<h2>Markets</h2>
9
9
-
<Markets />
10
10
-
</div>
11
11
-
}
9
9
+
const { markets } = useCumulus();
10
10
+
11
11
+
if (markets.isLoading) return <Spinner className='m-auto' />
12
12
13
13
-
function Markets() {
13
13
+
return <div className="grid md:grid-cols-2 gap-2">
14
14
+
{markets.data?.map(market => {
15
15
+
let [yes, no] = [0, 0];
16
16
+
let mappedBets = market.bets
17
17
+
?.sort((a, b) => a.createdAt > b.createdAt ? 1 : 0)
18
18
+
.map(bet => {
19
19
+
if (bet.position === "yes") yes++;
20
20
+
if (bet.position === "no") no++;
21
21
+
return {
22
22
+
...bet,
23
23
+
createdAt: formatDistance(bet.createdAt, new Date(), { addSuffix: true }),
24
24
+
timestamp: getUnixTime(bet.createdAt),
25
25
+
yes,
26
26
+
no,
27
27
+
yesPrice: yesPrice(yes, no, market.liquidity),
28
28
+
testNoPrice: 1-yesPrice(yes,no,market.liquidity),
29
29
+
noPrice: noPrice(yes, no, market.liquidity),
30
30
+
}
31
31
+
})
14
32
15
15
-
const { markets } = useCumulus();
16
16
-
17
17
-
if (markets.isLoading) {
18
18
-
return <Card>
19
19
-
<Spinner className='m-auto' />
20
20
-
</Card>
21
21
-
}
22
33
23
23
-
return <>
24
24
-
{markets.data?.map(market => (<Card key={market.cid}>
25
25
-
<CardHeader>
26
26
-
<CardTitle>{market.question}</CardTitle>
27
27
-
<CardDescription>Closes {formatDistance(new Date(market.closesAt), new Date(), { addSuffix: true })}</CardDescription>
28
28
-
</CardHeader>
29
29
-
<CardContent>
30
30
-
<p>Resolution: {market.resolution?.answer.toUpperCase()}</p>
31
31
-
<p>Bets: {market.bets?.length}</p>
32
32
-
</CardContent>
33
33
-
</Card>
34
34
-
))}
35
35
-
</>
34
34
+
return <div key={market.cid}>
35
35
+
<h2>{market.question}</h2>
36
36
+
<p>Closes {formatDistance(new Date(market.closesAt), new Date(), { addSuffix: true })}</p>
37
37
+
<p>{market.bets?.length} Positions</p>
38
38
+
<ChartContainer
39
39
+
className="border-2 rounded-lg"
40
40
+
config={{
41
41
+
yes: { label: "Yes" }, no: { label: "No" }
42
42
+
}}>
43
43
+
<LineChart data={mappedBets}>
44
44
+
<CartesianGrid strokeDasharray="3 3" />
45
45
+
<ChartTooltip />
46
46
+
<XAxis dataKey="createdAt" interval={8} />
47
47
+
<Line dataKey="yes" stroke="var(--color-shell-600)" />
48
48
+
<Line dataKey="no" stroke="var(--color-coral-600)" />
49
49
+
<Line dataKey="yesPrice" stroke="var(--color-coral-600)" />
50
50
+
<Line dataKey="testNoPrice" stroke="var(--color-coral-600)" />
51
51
+
<Line dataKey="noPrice" stroke="var(--color-coral-600)" />
52
52
+
</LineChart>
53
53
+
</ChartContainer>
54
54
+
</div>
55
55
+
})}
56
56
+
</div>
36
57
}
+355
src/web/components/ui/chart.tsx
···
1
1
+
import * as React from "react"
2
2
+
import * as RechartsPrimitive from "recharts"
3
3
+
4
4
+
import { cn } from "@/web/lib/utils"
5
5
+
6
6
+
// Format: { THEME_NAME: CSS_SELECTOR }
7
7
+
const THEMES = { light: "", dark: ".dark" } as const
8
8
+
9
9
+
export type ChartConfig = {
10
10
+
[k in string]: {
11
11
+
label?: React.ReactNode
12
12
+
icon?: React.ComponentType
13
13
+
} & (
14
14
+
| { color?: string; theme?: never }
15
15
+
| { color?: never; theme: Record<keyof typeof THEMES, string> }
16
16
+
)
17
17
+
}
18
18
+
19
19
+
type ChartContextProps = {
20
20
+
config: ChartConfig
21
21
+
}
22
22
+
23
23
+
const ChartContext = React.createContext<ChartContextProps | null>(null)
24
24
+
25
25
+
function useChart() {
26
26
+
const context = React.useContext(ChartContext)
27
27
+
28
28
+
if (!context) {
29
29
+
throw new Error("useChart must be used within a <ChartContainer />")
30
30
+
}
31
31
+
32
32
+
return context
33
33
+
}
34
34
+
35
35
+
function ChartContainer({
36
36
+
id,
37
37
+
className,
38
38
+
children,
39
39
+
config,
40
40
+
...props
41
41
+
}: React.ComponentProps<"div"> & {
42
42
+
config: ChartConfig
43
43
+
children: React.ComponentProps<
44
44
+
typeof RechartsPrimitive.ResponsiveContainer
45
45
+
>["children"]
46
46
+
}) {
47
47
+
const uniqueId = React.useId()
48
48
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
49
49
+
50
50
+
return (
51
51
+
<ChartContext.Provider value={{ config }}>
52
52
+
<div
53
53
+
data-slot="chart"
54
54
+
data-chart={chartId}
55
55
+
className={cn(
56
56
+
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
57
57
+
className
58
58
+
)}
59
59
+
{...props}
60
60
+
>
61
61
+
<ChartStyle id={chartId} config={config} />
62
62
+
<RechartsPrimitive.ResponsiveContainer>
63
63
+
{children}
64
64
+
</RechartsPrimitive.ResponsiveContainer>
65
65
+
</div>
66
66
+
</ChartContext.Provider>
67
67
+
)
68
68
+
}
69
69
+
70
70
+
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
71
71
+
const colorConfig = Object.entries(config).filter(
72
72
+
([, config]) => config.theme || config.color
73
73
+
)
74
74
+
75
75
+
if (!colorConfig.length) {
76
76
+
return null
77
77
+
}
78
78
+
79
79
+
return (
80
80
+
<style
81
81
+
dangerouslySetInnerHTML={{
82
82
+
__html: Object.entries(THEMES)
83
83
+
.map(
84
84
+
([theme, prefix]) => `
85
85
+
${prefix} [data-chart=${id}] {
86
86
+
${colorConfig
87
87
+
.map(([key, itemConfig]) => {
88
88
+
const color =
89
89
+
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
90
90
+
itemConfig.color
91
91
+
return color ? ` --color-${key}: ${color};` : null
92
92
+
})
93
93
+
.join("\n")}
94
94
+
}
95
95
+
`
96
96
+
)
97
97
+
.join("\n"),
98
98
+
}}
99
99
+
/>
100
100
+
)
101
101
+
}
102
102
+
103
103
+
const ChartTooltip = RechartsPrimitive.Tooltip
104
104
+
105
105
+
function ChartTooltipContent({
106
106
+
active,
107
107
+
payload,
108
108
+
className,
109
109
+
indicator = "dot",
110
110
+
hideLabel = false,
111
111
+
hideIndicator = false,
112
112
+
label,
113
113
+
labelFormatter,
114
114
+
labelClassName,
115
115
+
formatter,
116
116
+
color,
117
117
+
nameKey,
118
118
+
labelKey,
119
119
+
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
120
120
+
React.ComponentProps<"div"> & {
121
121
+
hideLabel?: boolean
122
122
+
hideIndicator?: boolean
123
123
+
indicator?: "line" | "dot" | "dashed"
124
124
+
nameKey?: string
125
125
+
labelKey?: string
126
126
+
}) {
127
127
+
const { config } = useChart()
128
128
+
129
129
+
const tooltipLabel = React.useMemo(() => {
130
130
+
if (hideLabel || !payload?.length) {
131
131
+
return null
132
132
+
}
133
133
+
134
134
+
const [item] = payload
135
135
+
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
136
136
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
137
137
+
const value =
138
138
+
!labelKey && typeof label === "string"
139
139
+
? config[label as keyof typeof config]?.label || label
140
140
+
: itemConfig?.label
141
141
+
142
142
+
if (labelFormatter) {
143
143
+
return (
144
144
+
<div className={cn("font-medium", labelClassName)}>
145
145
+
{labelFormatter(value, payload)}
146
146
+
</div>
147
147
+
)
148
148
+
}
149
149
+
150
150
+
if (!value) {
151
151
+
return null
152
152
+
}
153
153
+
154
154
+
return <div className={cn("font-medium", labelClassName)}>{value}</div>
155
155
+
}, [
156
156
+
label,
157
157
+
labelFormatter,
158
158
+
payload,
159
159
+
hideLabel,
160
160
+
labelClassName,
161
161
+
config,
162
162
+
labelKey,
163
163
+
])
164
164
+
165
165
+
if (!active || !payload?.length) {
166
166
+
return null
167
167
+
}
168
168
+
169
169
+
const nestLabel = payload.length === 1 && indicator !== "dot"
170
170
+
171
171
+
return (
172
172
+
<div
173
173
+
className={cn(
174
174
+
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
175
175
+
className
176
176
+
)}
177
177
+
>
178
178
+
{!nestLabel ? tooltipLabel : null}
179
179
+
<div className="grid gap-1.5">
180
180
+
{payload
181
181
+
.filter((item) => item.type !== "none")
182
182
+
.map((item, index) => {
183
183
+
const key = `${nameKey || item.name || item.dataKey || "value"}`
184
184
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
185
185
+
const indicatorColor = color || item.payload.fill || item.color
186
186
+
187
187
+
return (
188
188
+
<div
189
189
+
key={item.dataKey}
190
190
+
className={cn(
191
191
+
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
192
192
+
indicator === "dot" && "items-center"
193
193
+
)}
194
194
+
>
195
195
+
{formatter && item?.value !== undefined && item.name ? (
196
196
+
formatter(item.value, item.name, item, index, item.payload)
197
197
+
) : (
198
198
+
<>
199
199
+
{itemConfig?.icon ? (
200
200
+
<itemConfig.icon />
201
201
+
) : (
202
202
+
!hideIndicator && (
203
203
+
<div
204
204
+
className={cn(
205
205
+
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
206
206
+
{
207
207
+
"h-2.5 w-2.5": indicator === "dot",
208
208
+
"w-1": indicator === "line",
209
209
+
"w-0 border-[1.5px] border-dashed bg-transparent":
210
210
+
indicator === "dashed",
211
211
+
"my-0.5": nestLabel && indicator === "dashed",
212
212
+
}
213
213
+
)}
214
214
+
style={
215
215
+
{
216
216
+
"--color-bg": indicatorColor,
217
217
+
"--color-border": indicatorColor,
218
218
+
} as React.CSSProperties
219
219
+
}
220
220
+
/>
221
221
+
)
222
222
+
)}
223
223
+
<div
224
224
+
className={cn(
225
225
+
"flex flex-1 justify-between leading-none",
226
226
+
nestLabel ? "items-end" : "items-center"
227
227
+
)}
228
228
+
>
229
229
+
<div className="grid gap-1.5">
230
230
+
{nestLabel ? tooltipLabel : null}
231
231
+
<span className="text-muted-foreground">
232
232
+
{itemConfig?.label || item.name}
233
233
+
</span>
234
234
+
</div>
235
235
+
{item.value && (
236
236
+
<span className="font-mono font-medium text-foreground tabular-nums">
237
237
+
{item.value.toLocaleString()}
238
238
+
</span>
239
239
+
)}
240
240
+
</div>
241
241
+
</>
242
242
+
)}
243
243
+
</div>
244
244
+
)
245
245
+
})}
246
246
+
</div>
247
247
+
</div>
248
248
+
)
249
249
+
}
250
250
+
251
251
+
const ChartLegend = RechartsPrimitive.Legend
252
252
+
253
253
+
function ChartLegendContent({
254
254
+
className,
255
255
+
hideIcon = false,
256
256
+
payload,
257
257
+
verticalAlign = "bottom",
258
258
+
nameKey,
259
259
+
}: React.ComponentProps<"div"> &
260
260
+
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
261
261
+
hideIcon?: boolean
262
262
+
nameKey?: string
263
263
+
}) {
264
264
+
const { config } = useChart()
265
265
+
266
266
+
if (!payload?.length) {
267
267
+
return null
268
268
+
}
269
269
+
270
270
+
return (
271
271
+
<div
272
272
+
className={cn(
273
273
+
"flex items-center justify-center gap-4",
274
274
+
verticalAlign === "top" ? "pb-3" : "pt-3",
275
275
+
className
276
276
+
)}
277
277
+
>
278
278
+
{payload
279
279
+
.filter((item) => item.type !== "none")
280
280
+
.map((item) => {
281
281
+
const key = `${nameKey || item.dataKey || "value"}`
282
282
+
const itemConfig = getPayloadConfigFromPayload(config, item, key)
283
283
+
284
284
+
return (
285
285
+
<div
286
286
+
key={item.value}
287
287
+
className={cn(
288
288
+
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
289
289
+
)}
290
290
+
>
291
291
+
{itemConfig?.icon && !hideIcon ? (
292
292
+
<itemConfig.icon />
293
293
+
) : (
294
294
+
<div
295
295
+
className="h-2 w-2 shrink-0 rounded-[2px]"
296
296
+
style={{
297
297
+
backgroundColor: item.color,
298
298
+
}}
299
299
+
/>
300
300
+
)}
301
301
+
{itemConfig?.label}
302
302
+
</div>
303
303
+
)
304
304
+
})}
305
305
+
</div>
306
306
+
)
307
307
+
}
308
308
+
309
309
+
// Helper to extract item config from a payload.
310
310
+
function getPayloadConfigFromPayload(
311
311
+
config: ChartConfig,
312
312
+
payload: unknown,
313
313
+
key: string
314
314
+
) {
315
315
+
if (typeof payload !== "object" || payload === null) {
316
316
+
return undefined
317
317
+
}
318
318
+
319
319
+
const payloadPayload =
320
320
+
"payload" in payload &&
321
321
+
typeof payload.payload === "object" &&
322
322
+
payload.payload !== null
323
323
+
? payload.payload
324
324
+
: undefined
325
325
+
326
326
+
let configLabelKey: string = key
327
327
+
328
328
+
if (
329
329
+
key in payload &&
330
330
+
typeof payload[key as keyof typeof payload] === "string"
331
331
+
) {
332
332
+
configLabelKey = payload[key as keyof typeof payload] as string
333
333
+
} else if (
334
334
+
payloadPayload &&
335
335
+
key in payloadPayload &&
336
336
+
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
337
337
+
) {
338
338
+
configLabelKey = payloadPayload[
339
339
+
key as keyof typeof payloadPayload
340
340
+
] as string
341
341
+
}
342
342
+
343
343
+
return configLabelKey in config
344
344
+
? config[configLabelKey]
345
345
+
: config[key as keyof typeof config]
346
346
+
}
347
347
+
348
348
+
export {
349
349
+
ChartContainer,
350
350
+
ChartTooltip,
351
351
+
ChartTooltipContent,
352
352
+
ChartLegend,
353
353
+
ChartLegendContent,
354
354
+
ChartStyle,
355
355
+
}
+7
src/web/lib/lmsr.ts
···
1
1
+
export function yesPrice(yes: number, no: number, liquidity: number) {
2
2
+
return 1 / (1 + Math.exp((no - yes) / liquidity));
3
3
+
}
4
4
+
5
5
+
export function noPrice(yes: number, no: number, liquidity: number) {
6
6
+
return 1 / (1 + Math.exp((yes - no) / liquidity));
7
7
+
}