crypto-bug-hunters / bug-less Goto Github PK
View Code? Open in Web Editor NEWπͺ² Bugless, a bug bounty platform powered by Cartesi Rollups
License: Apache License 2.0
πͺ² Bugless, a bug bounty platform powered by Cartesi Rollups
License: Apache License 2.0
Here is some motivation.
The most prominent feature IMO is the content-addressable storage of packages and symlinks.
Doing a quick benchmark, pnpm
takes 2.5s to install dependencies, while npm
takes a whopping 14.8s on my machine.
Another nice feature is that pnpm
forces the developer to be explicit about dependencies, since node_modules
only contains direct dependencies listed in the package.json
file.
fly.io
It's super easy to start, has decent dashboard for memory and logs and starts with $5/month.
If we can split the budget between the initial collaborators, it get less than a $1/month.
I already tested with early versions of sunodo and echo-*
dapp, and it works.
AWS
Has a free tier for 12months for new accounts, we'd need to spin up an free-tier eligible AWS EC2 to run the node + free-tier eligible AWS RDS for PostgreSQL.
It requires little more work to start, but it's free for some time.
edubart β Today at 10:37 AM
I think the dapp could be reworked to not need dynamic requests so its frontend can be hosted as a static website. I don't see the need to recompile frontend on every new bounty, just update the bounty list using GraphQL after every submission in the frontend, the whole dapp frontend could be a SPA (single page application).
https://discord.com/channels/600597137524391947/1166042819782258788/1212755480280174652
For more info about dynamic routes on NextJS, check: https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes
Instead of creating a website for Bugless, a good enough solution to continue moving forward and don't spend energy with it yet would be to add a Hero page where the user could have some brief description, a link for the README file and also did not face directly the list of bounties, which in a first moment will be kind of empty (with only the solidity compiler bounty).
One proposal for the hero page is presented below. The "Explore Bounties" button will direct users to the list of existing bounties and the "Learn More" link will move users to the README file on the github repo. The text in the image is only a suggestion and can changed at will.
This is necessary so that we don't leak the Alchemy API Token at the client-side frontend code.
See: https://docs.alchemy.com/docs/how-to-use-jwts-for-api-requests
related to: #50
Given the scalability issues of the inspect state request, it would be interesting to run the Cartesi Machine on the user side.
The highest contenders IMO are:
Tested with Google Chrome on windows 11.
Steps:
contracts.tsx:4351 DOMException: Failed to execute 'setItem' on 'Storage': Setting the value of 'wagmi.cache' exceeded the quota.
at Object.setItem (webpack-internal:///(app-pages-browser)/./node_modules/@wagmi/core/dist/chunk-TSH6VVF4.js:423:19)
at trySave (webpack-internal:///(app-pages-browser)/./node_modules/@tanstack/query-sync-storage-persister/build/lib/index.mjs:16:17)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@tanstack/query-sync-storage-persister/build/lib/index.mjs:26:21)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@tanstack/query-sync-storage-persister/build/lib/index.mjs:72:9)
Expected: No error message in the console after dropping a valid bounty file.
Only in the backend log (which is not necessarily visible for the user) we can see that the subsequent requests were rejected.
3538f5f0-validator-1 | rejecting: sponsorships already withdrawn
3538f5f0-validator-1 | [INFO actix_web::middleware::logger] 127.0.0.1 "POST /report HTTP/1.1" 202 0 "-" "Go-http-client/1.1" 0.000448
Can we have a better domain?
I'm thinking in something like <chain-name>.bug-less.io
.
For sepolia:
https://sepllia.bug-less.io
for the frontendhttps://sepllia.bug-less.io/graphql
https://sepllia.bug-less.io/inspect
The way we're deploying on fly.io requires us to have frontend and backend on different URLS, this could be solved somehow, but I won't get into these details here.
/cc @crypto-bug-hunters/core-developers comment?
Until now the goal is to deploy bugless to Optimism mainnet so we need to allow the wallet to connect to that network. Currently the wallet is connecting only to Foundry (local development), Ethereum mainnet and Ethereum Sepolia.
To deploy the frontend to fly.io, we need a container image to publish to registry.fly.io
This component would avoid duplicating the logic for conditionally presenting components if there is a wallet connected.
Features where this component would be used:
The command to send dapp address is included in populate.sh
.
However, if that populate
script wasn't run beforehand, instead we add bounties and sponsors manually, then the command to send dapp-address
may not be run. And the following withdrawal would fail. However, in browser, there's no indication of the failure or error message, similar as this issue .
In the newest onchain design, we would not need to send dapp address, because that is included in the input meta data.
Since EggRoll
is deprecated, we should move to Rollmelette
instead.
I will take a look at how it can be done.
Currently the "submit bounty" button is enabled independently if the user has connected their wallet or not.
For the fields of interfaces in frontend/src/model/state.ts, they should be the same case as the JSON data, which follows camelCase as per JSON recommendations.
More context can be found in this PR #48 (comment)
Currently the submit bounty form allows the user to define the current day and dates in the past, which are rejected by the backend.
3538f5f0-validator-1 | rejecting: deadline already over
When testing the application on a local network, you'll need Ether to publish transactions.
We should add a section on the README showing how you can fund your account to interact with the application.
Guilherme β Yesterday at 4:34 PM
Today I managed to compile the Solidity bounty down to a .tar.xz file, but unfortunately was not able to submit it to BugLess. I did some surgery on the front-end code go so that I could see what error that was being returned by wagmi, and it states that there was an HTTP request failure. Do note that this only happens when I try to submit the Solidity bounty. The Lua bounty, for example, works just fine. My theory is that, because the .tar.xz file is 3,6 MB big, the input is too large to be submitted to L1, in terms of gas costs. As comparison, the BusyBox bounty is around 30 KB, the Lua bounty is around 90 KB, and the SQLite bounty is around 300 KB. Maybe we should look for smaller programs?
Guilherme β Yesterday at 7:11 PM
Do not that this is not a limitation of BugLess, but a limitation of the base layer. If we could access other data sources, such as Espresso, then maybe it would be feasible to submit larger programs.
ClΓ‘udio β Yesterday at 7:53 PM
Hi Gui,
what if we submit the bounty in a multy-part compressed file to be reconstucted inside the Cartesi Machine?
We could do a experiment with artificial files of different sizes to know the maximum allowed size. Bounties bigger than this threshold should be compressed in multi-part mode.
We used this approach when processing images with OpenCV inside the cartesi machine. We splited the original image in smaller parts, submitted all parts and then reconstructed the original image inside the Cartesi Machine (cc. @marcus Souza ).
Maybe @carlo also addressed this limitation with cartridges size in Rives.
Cheers!
Marcus Souza β Yesterday at 8:06 PM
Hey ππΌ ,
Yes, in my case, I had to reconstruct the images, as Claudio mentioned. Seeing the above text:
As comparison, the BusyBox bounty is around 30 KB
This is the most similar to my case in terms of size, because i was working with very small images (100x100 pixels the biggest ones). So, the approach was to divide this in the frontend part and send the images as chunks in some transactions ( exploring the maximum size of a transaction payload, this size fits in 4 transactions). Technically, it is possible but it has some drawbacks.
If we could access other data sources, such as Espresso, then maybe it would be feasible to submit larger programs.
I think this can also improve the UX, since sending the transactions sequentially isn't that good in this aspect.
Maybe @carlo also addressed this limitation with cartridges size in Rives.
Probably Carlo's solution is smarter then mine for this, so it is good to take a look π
Carlo β Yesterday at 8:13 PM
Yeah, we do this for the upload of the games as well
Guilherme β Yesterday at 10:52 PM
Thanks for the input, guys! π
Is there some openly available source code that I could take a look to take inspiration from?
edubart β Yesterday at 11:59 PM
The idea is simple, split the file in chunks, submit each chunk as inputs, reassemble them after all chunks are available. Although you have to consider what happens if not all chunks are submitted, and design in a way where you are sure chunks will not mix with others in case two bounties are being submitted at the same time.
edubart β Today at 12:06 AM
You have to consider the cost to upload all this on L1, I think 3.6MB may be very expansive on Ethereum at least, maybe its not the case in other blockchains. In case of very large bounties maybe having its own rollups would be better, but I think this is a complete rework of bugless. Or maybe in the future we could dehash bounties from a cheap data availability layer.
tuler.eth β Today at 12:11 AM
Using a L2 wonβt help much with cost I think. Because it still have to post to L1. Itβs an incentive to research more about other DAs.
edubart β Today at 12:17 AM
Also bounties that need to be on bugless from day zero, don't really need to be inserted onchain in day zero, this would be a waste of money. The dapp could start with some initial bounties baked in, we are doing this for Rives for example, where we do support uploading games, but large games like Doom is baked in from day 0 to save costs.
edubart β Today at 12:26 AM
Another idea I had is whoever wants to create a bounty, make a PR to a bounties repository, then with a DAO or something we do a upgrade of the bonties flash drive. This way bounties are created offchain, while there is some governance on chain to make the machine upgrades, this also a very different design, and requires machine upgrades and some coordinance of the nodes to make the upgrade.
ClΓ‘udio β Today at 11:41 AM
Hi Gui, given all that was said, IMO we should move to prepare a drive for our machine with the Solidity bounty built-in.
With that we could have a first alpha launch with something interesting and really relevant for the community to explore while we think about the feature we want to offer for bigger bounties in the future.
Also, maybe it is a good time for us to have a call and align all this together. What do you guys think about it?
gligneul.eth β Today at 12:02 PM
One thing I considered was deploying a new rollup for each bounty, like the dapp sharding idea. This would circumvent the base layer limit issue.
gligneul.eth β Today at 12:06 PM
This would require a major refactoring on the dapp though; so for this first version it might be better to just embed a particular bounty into the dapp snapshot.
Marcus Souza β Today at 12:25 PM
https://github.com/souzavinny/rollups-examples/blob/main/frontend-biometrics/src/view/layout/home/helpers/send-input.helpers.ts
You can take a look on how biometrics dealed with that time. In the front end, we had this helper that set the maximum size of a chunk and divided the image string to fit the transaction payload. The backend also had treatments to flag if the chunk is the final one or not through notices.
After testing an exploit that failed, we will see a log of its execution in the test exploit text area.
If we submit a new exploit file, the log corresponding to the execution of the last exploit is kept.
It can make the user feel confused.
wagmi and viem versions are outdated.
ExploitLanguage
field to AppBounty
and CreateAppBounty
ExploitLanguage
to /bounty/create
ExploitLanguage
to /bounty/[bountyId]
See the list of supported languages by highlight.js
.
ExploitLanguage
in CreateAppBounty
inputExploitLanguage
in /bounty/create
<CodeHighlight>
from Mantine<RichTextEditor.CodeBlock>
from MantineThe Dockerfile
of some bounties are running make -j4 ...
but there may be more cores available.
We can provide all cores by running make -j`nproc` ...
.
It should at least have a "tooltip" or become a Button, making it clear to the user the intent of the button.
If you run ./populate.sh
without having run sunodo run
in parallel, it will raise several errors. However, it will keep going until the end of the script, instead of exiting on the first error.
I was trying to run the frontend, then I got the error:
Import trace for requested module:
./src/app/page.tsx
β¨― ./src/model/reader.ts:2:0
Module not found: Can't resolve './__generated__/gql'
1 | import { useQuery } from "@apollo/client";
> 2 | import { gql } from "./__generated__/gql";
3 | import { CompletionStatus } from "./__generated__/graphql";
4 | import { BugLessState, AppBounty, SendExploit, Voucher } from "./state";
The command pnpm codegen
should fix this, but readme says nothing about this.
It's good to keep the name of the application consistent throughout our codebase.
For back-end and cloud code, I won't bother, because it might create unnecessary friction.
So, let's at least use "Bugless" for front-end and documentation.
Tested with Google chrome on Windows 11
Steps:
With a connected account and with the console tab of google chrome dev tools opened:
Warning: validateDOMNesting(...): <div> cannot appear as a descendant of <p>.
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Badge/Badge.mjs:80:104)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Group/Group.mjs:75:104)
at p
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Text/Text.mjs:82:104)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Stack/Stack.mjs:67:104)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Group/Group.mjs:75:104)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Stack/Stack.mjs:67:104)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Paper/Paper.mjs:62:104)
at Provider (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/utils/create-safe-context/create-safe-context.mjs:18:23)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Card/Card.mjs:62:104)
at ProfileCard (webpack-internal:///(app-pages-browser)/./src/components/profileCard.tsx:47:11)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Stack/Stack.mjs:67:104)
at ParticipantsBox (webpack-internal:///(app-pages-browser)/./src/app/bounty/[bountyId]/page.tsx:255:11)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Stack/Stack.mjs:67:104)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/Center/Center.mjs:49:104)
at BountyInfoPage (webpack-internal:///(app-pages-browser)/./src/app/bounty/[bountyId]/page.tsx:299:19)
at StaticGenerationSearchParamsBailoutProvider (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/static-generation-searchparams-bailout-provider.js:15:11)
at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:241:11)
at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:62:11)
at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:338:11)
at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:110:11)
at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:152:9)
at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:227:11)
at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js:15:44)
at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:348:11)
at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:241:11)
at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:62:11)
at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:338:11)
at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:110:11)
at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:152:9)
at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:227:11)
at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js:15:44)
at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:348:11)
at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:241:11)
at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:54:9)
at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:62:11)
at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:338:11)
at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:110:11)
at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:152:9)
at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:227:11)
at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js:15:44)
at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:348:11)
at main
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/AppShell/AppShellMain/AppShellMain.mjs:49:104)
at div
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/Box/Box.mjs:62:7)
at Provider (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/utils/create-safe-context/create-safe-context.mjs:18:23)
at eval (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/components/AppShell/AppShell.mjs:88:104)
at Shell (webpack-internal:///(app-pages-browser)/./src/app/shell.tsx:12:11)
at QueryClientProvider (webpack-internal:///(app-pages-browser)/./node_modules/@tanstack/react-query/build/lib/QueryClientProvider.mjs:48:3)
at WagmiConfig (webpack-internal:///(app-pages-browser)/./node_modules/wagmi/dist/index.js:135:3)
at WalletProvider (webpack-internal:///(app-pages-browser)/./src/providers/walletProvider.tsx:50:11)
at MantineThemeProvider (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/MantineProvider/MantineThemeProvider/MantineThemeProvider.mjs:28:3)
at MantineProvider (webpack-internal:///(app-pages-browser)/./node_modules/@mantine/core/esm/core/MantineProvider/MantineProvider.mjs:27:3)
at StyleProvider (webpack-internal:///(app-pages-browser)/./src/providers/styleProvider.tsx:15:19)
at ApolloProvider (webpack-internal:///(app-pages-browser)/./node_modules/@apollo/client/react/context/ApolloProvider.js:14:21)
at GraphQLProvider (webpack-internal:///(app-pages-browser)/./src/providers/graphqlProvider.tsx:13:11)
at body
at html
at Layout (webpack-internal:///(app-pages-browser)/./src/app/layout.tsx:33:11)
at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:54:9)
at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:62:11)
at DevRootNotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/dev-root-not-found-boundary.js:32:11)
at ReactDevOverlay (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:66:9)
at HotReload (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:294:11)
at Router (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:157:11)
at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:82:9)
at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:110:11)
at AppRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:440:13)
at ServerRoot (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:126:11)
at RSCComponent
at Root (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:142:11)
No feedback is given when executing a voucher. Neither a "transaction successful" is shown. An actual execution feedback is desired
This hinders debugging, when you want to artificially advance time past the expiry date of some bounty.
So that we don't expose the Alchemy API Token that will be used by the front-end code, we should use JWT Authentication.
We're gonna need to generate and store the key pair safely, and make the process of importing the public key into Alchemy account and the Application API Token.
https://docs.alchemy.com/docs/how-to-use-jwts-for-api-requests
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.