⚠️ 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 usingnvm
to manage NodeJS versions.
1) If cloning for the first time:
- Install yarn if you don't have it:
npm install --global yarn
- Run
yarn
to 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 .husky
to set up Husky (which we use for commit hooks) -
Add your Wallet address to
getEmployeeWallets
inpackages/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 prod
to ensure Graphite doesn't track theprod
branch
NOTE: all commands are assumed to be run from the repo root (unless explicitly stated otherwise) on NodeJS version
16.15.0
. We recommend usingnvm
to 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-server
to 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 relay
to 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/graphql
to see if your changes are reflected - If updating Hasura: check
http://localhost:9695/console/api/api-explorer
to 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/schemas
and clickingReload
forexpress-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
main
results 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 swc
inpackages/server/
, thenyarn start-server
in root, then tryyarn fix-server
again 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
main
with a message likeINFO A new version (v2.13.0) is available for CLI, update? (y/N)
you can separately later run thehasura update-cli
command 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 usingnvm
to manage NodeJS versions.
If you've already done the initial setup above, do the following:
- Shared modules:
yarn shared tsc
to 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-server
as 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
main
orprod
(see.yml
files in.github/workflows
for 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 main
git pull
git checkout prod
git pull
git rebase main
git 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
main
first and pushes toprod
must only be done by rebasingmain
- As a corollary:
prod
should always be behind or caught up withmain
, never aheadprod
andmain
should never diverge (i.e., should maintain the same history)
- To prevent accidental pushes to
prod
we have apre-push
hook that will stop pushes without--no-verify
- Sometimes we may need to push a single change to
prod
without rebasing all ofmain
- e.g.,
main
has a lot of commits and we don't want to potentially introduce more changes on top ofprod
that's already broken
- e.g.,
- In cases like this, the following steps can be taken:
- Push the commit with the fix onto
main
- Checkout
main
and ensuremain
is in sync withorigin/main
(i.e.,git pull --rebase
) - Checkout
prod
and rungit cherry-pick <commit hash>
with the hash of the fix commit - Run
git push -f --no-verify
to push the commit toprod
Once we're ready to push the remainder of main
to prod
, we should take the following steps:
- Checkout
main
and pull latest - Checkout
prod
and rungit reset --hard HEAD~1
(this ensures that your local copy ofprod
is 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-modules
plugin setup to help type-check CSS class names in React code.- Some recommended extensions such as
css-sort-classname
which 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_AUTHORITY
is set to the correct value inpackages/server/.env
. This will ensure theauctionWonUpdatePnftDrop
Hasura webhook works properly. Use this key for devnet. - Then run
yarn server process-finished-auctions
. This will populate theClaim
table. - Finally, run
yarn server process-finished-pnft-drops
. This will close any distributors which need to be closed. - If you want to hit the
updateDistributorForAuctionMint
endpoint 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>" }'
.