⚠️ Branding and naming By using this code, you agree that you will not copy and redistribute the work or any derivative version under the name “Formfunction” or “Formfunction, Inc.,” or use any associated logos or branding with the Formfunction brand. You must replace all written mentions or images in the codebase that are associated with the name Formfunction with your own name and imagery. You may not attempt to mislead people into thinking that your work is the original Formfunction website which has been closed down as of March 29, 2023.
This is the code for Formfunction, an NFT marketplace created for independent artists and creators. The marketplace was officially closed on March 29, 2023. However, any interested parties can fork this code in order to create a similar marketplace. This repository is meant to serve as a reference, and will NOT be actively maintained.
The general stack is as follows:
- Frontend: React, Relay, TypeScript, deployed with Cloudflare pages.
- Backend: AWS RDS for DB, Express for our webserver, Apollo for our custom GraphQL server, Hasura for an auto-generated GraphQL server + DB management + DB webhooks, Prisma for DB reads/writes, deployed via GitHub actions.
- Solana: we have a few Solana programs, see links below for more info.
The code for Formfunction's Solana programs can be found here:
- Formfunction Auction House—Solana program and TypeScript SDK that handles on-chain marketplace transactions (e.g. bidding, buying editions).
- Formfunction Gumdrop—Solana program and TypeScript SDK for the participation NFT feature.
- Formfunction Program Shared—Various utils that the program repositories use.
- Formfunction Campaign Treasury Manager—Unfinished implementation of treasury management for campaigns.
- Formfunction Candy Machine—A fork of Metaplex's Candy Machine that adds the ability to use a Merkle allowlist.
In order to get the code running locally, please follow the instructions below. You should be able to get it running in ~10 minutes or less.
In order to deploy this code, this information may be helpful. We used the following SaaS when running Formfunction (amongst other SaaS which aren't as relevant to this codebase):
- DigitalOcean for hosting the stateless servers
- AWS RDS for Postgres (Amazon RDS Aurora to be precise)
- Hasura for DB migrations/webhooks/GraphQL API/etc.
- Firebase for blob storage
- Postmark for emails
- LaunchDarkly for feature flags
- Sentry for error reporting
- Grafana Loki for realtime logging
- Helius for Solana RPC
- Imgix and Mux for asset optimization
- Airplane for cron jobs
- Mixpanel for analytics
Of course, these can be swapped out for other options, and some (like DigitalOcean and RDS) do not affect the code at all. However, some of our code does assume we use certain SaaS (like LaunchDarkly and Sentry), so it will be easier to run this code if you use the same SaaS.
NOTE: all commands are assumed to be run from the repo root (unless explicitly stated otherwise) on NodeJS version
16.15.0. We recommend usingnvmto manage NodeJS versions.
1) If cloning for the first time:
- Install yarn if you don't have it:
npm install --global yarn - Run
yarnto install packages - Run the frontend:
yarn start-frontend
- NOTE: it is expected for this to not work until the server is fully set up
-
Set up the server: follow these instructions
-
Run
git config core.hooksPath .huskyto set up Husky (which we use for commit hooks) -
Add your Wallet address to
getEmployeeWalletsinpackages/frontend/src/utils/getEmployeeWallets.ts. This is used for connecting local frontend to the prod server using the linkhttp://localhost:3000/?pointToProd=1. -
Also add your wallet address here https://app.launchdarkly.com/default/local/features/teamWallets/variations. This will allow you to connect our dev evironment to prod server using the link
https://dev.formfunction.xyz/?pointToProd=1.
2) Install Graphite
- We use Graphite for code reviews and merging changes. It allows us to easily stack changes and stay unblocked while code reviews are in progress.
- Once installed, run
gt repo ignored-branches --add prodto ensure Graphite doesn't track theprodbranch
NOTE: all commands are assumed to be run from the repo root (unless explicitly stated otherwise) on NodeJS version
16.15.0. We recommend usingnvmto manage NodeJS versions.
- Update GraphQL schema on backend
- Update files in
packages/server/src/schema/...if you need to update our custom GraphQL server (Apollo) - Otherwise, update the DB through Hasura console
- For both cases, run
yarn fix-serverto update the server
- Update files in
- Update GraphQL schema on frontend by running
yarn frontend gen-graphql - Write queries/mutations in frontend code
- Run
yarn frontend relayto generate Relay code
Common issues
- Backend schema won't update - the easiest way to verify this is to either:
- If updating custom GraphQL server: check
http://localhost:4000/graphqlto see if your changes are reflected - If updating Hasura: check
http://localhost:9695/console/api/api-explorerto see if your changes are reflected - If changes are not being reflected, there's most likely an issue with your server (try re-running
yarn start-server)
- If updating custom GraphQL server: check
- Frontend schema won't update - typically this is because the backend server is not updating properly
- You can try going to
http://localhost:9695/console/remote-schemas/manage/schemasand clickingReloadforexpress-schema
- You can try going to
- Relay codegen doesn't work - typically this is due to errors in the queries/mutations or because the frontend schema did not update; the error message should give some hints
- Pulling
mainresults in type errors - this usually happens if your server isn't running when you pull, leading to pull hooks failing and codegen not being updated.- try to
yarn fix-server. this may not work due to the type errors, in which case: yarn swcinpackages/server/, thenyarn start-serverin root, then tryyarn fix-serveragain in a separate terminal window
- try to
- You randomly end up with a lot of generated files in your git diff (generally when switching branches) - if your Relay compiler was on --watch, restarting the compiler will often clear the git diff.
- If the commit hooks hang after pulling
mainwith a message likeINFO A new version (v2.13.0) is available for CLI, update? (y/N)you can separately later run thehasura update-clicommand to update the Hasura CLI.
NOTE: all commands are assumed to be run from the repo root (unless explicitly stated otherwise) on NodeJS version
16.15.0. We recommend usingnvmto manage NodeJS versions.
If you've already done the initial setup above, do the following:
- Shared modules:
yarn shared tscto build (do this first) - Server:
yarn start-server- To get changes to propagate to your local server, run
yarn server swc - If you encounter issues, try running
yarn fix-serveras a first step - More troubleshooting instructions are available here
- To get changes to propagate to your local server, run
- Frontend:
yarn start-frontend
We use a combination of Github Actions and Cloudflare Pages to deploy our stack:
- Server: Github Actions that run based on pushes to
mainorprod(see.ymlfiles in.github/workflowsfor more details) - Frontend: We use Cloudflare Pages which pulls and builds our CRA app and deploys on Cloudflare. Specifically, we have Github Actions that trigger these builds either on their own or after a successful server build is complete.
All pushes to main will deploy our dev stack automatically. The dev site can be accessed by going to dev.formfunction.xyz.
Go to dev and manually test functionality as necessary.
Before starting, ensure that you have no pending changes (i.e., clean working directory) on main and prod.
Run the following steps to push to prod:
git checkout maingit pullgit checkout prodgit pullgit rebase maingit push -f --no-verify
Finally, go to the prod branch and check that you see:
This branch is up to date with main.
If not, cancel the latest deployment.
Hygiene:
- All changes should be pushed to
mainfirst and pushes toprodmust only be done by rebasingmain - As a corollary:
prodshould always be behind or caught up withmain, never aheadprodandmainshould never diverge (i.e., should maintain the same history)
- To prevent accidental pushes to
prodwe have apre-pushhook that will stop pushes without--no-verify
- Sometimes we may need to push a single change to
prodwithout rebasing all ofmain- e.g.,
mainhas a lot of commits and we don't want to potentially introduce more changes on top ofprodthat's already broken
- e.g.,
- In cases like this, the following steps can be taken:
- Push the commit with the fix onto
main - Checkout
mainand ensuremainis in sync withorigin/main(i.e.,git pull --rebase) - Checkout
prodand rungit cherry-pick <commit hash>with the hash of the fix commit - Run
git push -f --no-verifyto push the commit toprod
Once we're ready to push the remainder of main to prod, we should take the following steps:
- Checkout
mainand pull latest - Checkout
prodand rungit reset --hard HEAD~1(this ensures that your local copy ofprodis at the state prior to pushing the hotfix) - Run
git rebase main - Run
git push -f --no-verify
There are a few specific VS Code settings configured in the .vscode/ folder which should provide some conveniences for development. These include:
typescript-plugin-css-modulesplugin setup to help type-check CSS class names in React code.- Some recommended extensions such as
css-sort-classnamewhich can help to sort CSS files alphabetically by classname.
These should work out of the box with no additional configuration for VS Code users.
The pNFTs feature relies on some Airplane jobs to work correctly. However, these jobs don't run on localhost! So how do you test pNFTs locally?
- First, make sure
GUMDROP_CONFIG_AUTHORITYis set to the correct value inpackages/server/.env. This will ensure theauctionWonUpdatePnftDropHasura webhook works properly. Use this key for devnet. - Then run
yarn server process-finished-auctions. This will populate theClaimtable. - Finally, run
yarn server process-finished-pnft-drops. This will close any distributors which need to be closed. - If you want to hit the
updateDistributorForAuctionMintendpoint to manually update a distributor for an auction you can runcurl --header "check: fofu" -X POST http://localhost:4000/intern/updateDistributorForAuctionMint -H "Content-Type: application/json" -d '{ "mint": "<auction-nft-mint>" }'.
