BirdCount comprises a proof of concept Stacks and Next.js app.
It implements a simple Clarity counter contract and knits together a web frontend interface, testing frameworks, integration environment, scripting setup, and static IPFS deployment workflow.
NOTE: THIS PROJECT AND SUPPORTING LIBRARIES HAVE NOT BEEN AUDITED, IT IS IN ALPHA STATE. USE AT YOUR OWN RISK / DISCRETION
To access BirdCount, use an IPFS gateway link from the latest release, or visit bird-count.com.
The counter is statically set at
15
but will fetch the counter state dynamically every 3 seconds using whichever network you have selected.
NOTE:
mainnet
is the default network andlocalnet
only works in development mode due to browser security limitations (CORS and mixed content)
If you prefer not using the integration environment, BirdCount can be run locally and pointed at
mainnet
,regtest
(with some limitations), ortestnet
. Skip ahead to Step 4 and for Steps 5 and 6 use another network.
-
To run BirdCount with the integration environment locally, install and run Docker Desktop.
-
Install Clarinet.
-
Clone this repository and bootstrap your
localnet
which consists of a Bitcoin node, Stacks node, Stacks API server, and Stacks and Bitcoin explorers:
cd bird-count
clarinet integrate
- In a different terminal, install the dependencies and start the development server:
yarn && yarn run dev
-
In your browser, install the Hiro Wallet and select
Change Network -> Localnet
. -
Open http://localhost:3000 with your browser to load the app. Click on
Connect Stacks Wallet
and make sure you are connected toLocalnet
, then use the+
button to increment the counter. Open the Chrome DevTools to view the console and network queries.
Once your transaction has confirmed, the counter will automatically update.
To run the tests (both @clarigen/test
and Clarinet examples are included), try:
$ yarn test
yarn run v1.22.10
$ jest && clarinet test tests/*.ts
PASS tests/clarigen/bird-count.test.ts (5.595 s)
Counter contract
β initial counter is 0 (34 ms)
β increment counter (34 ms)
β decrement counter (28 ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 5.754 s
Ran all test suites.
Running file:///Projects/bird-count/tests/bird-count_test.ts
* get-bird-count returns u0 immediately after being deployed ... ok (22ms)
* increment function counts up ... ok (11ms)
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (808ms)
β¨ Done in 12.81s.
Each testing framework can also be run independently npx jest
for the Clarigen test and clarinet test tests/bird-count_test.ts
for the Clarinet one.
There's an example node
script written with @clarigen/node
that will call the public increment
function within the integration environment:
$ yarn ts-node scripts/increment.ts
yarn run v1.22.10
$ /Projects/bird-count/node_modules/.bin/ts-node scripts/increment.ts
http://localhost:3999/extended/v1/tx/0x10242276f35714c18ababdd36bd5a667383f4d820bdbeeb65c649808c82d74e7
β¨ Done in 3.44s.
lingui
is used for translations. To add a new translation, edit lingui.config.js
. Then run:
yarn run lingui
TODO: document the other areas of the app that need to be edited to add a new langauge, control locale and text direction.
BirdCount was developed while participating in the first Clarity Universe cohort.
It was bootstrapped with create-next-app
and then integrated with Clarinet (testing and integration) and Clarigen (testing and boilerplate). It utilizes micro-stacks to connect to Stacks and jotai-query-toolkit for managing query state.
The interface is currently built with components from MUI v5 and the styled-components library but that could change in the future if more flexible options become apparent (suggestions welcome!).
Implementing a fully static
SSG
build using Next.js
export
, "no compromises" DarkMode
, i18n/l10n
(including RTL support) are core principles of the project.
BirdCount is intended to be a starting template for building more functional Stacks apps; it's been built as a proof of concept minimally viable app that's production ready and can be statically deployed on a censorship resistant network like IPFS. To that end, please fork it and build something!
Here are some things you can try:
- Add or modify a Clarity contract in
./contracts
- Run
clarinet check
- Files in
src/utils/clarigen
are automatically generated by runningyarn clarigen
. The resulting boilerplate can be used for running tests and scripts (and eventually, when the@clarigen/web
stabilizes, may power the web app itself.) - Write new tests against your contract
- Create new pages in
src/pages/
usingsrc/pages/index.tsx
, as an example.Next.js
automatically creates routes for additional pages in that directory (e.g., see theabout
page).
Deploying your app manually to IPFS to share your work is pretty simple once you have the IPFS Command-line installed, have done an ipfs init
and have the daemon running:
yarn build && yarn export
ipfs add -r out
ipfs name publish /ipfs/<...Content Identifier (CID) of the out folder...>
When I started, I used to manually run the above, and I also set up an account at pinata to manually pin each release (to make sure at least one IPFS node has a copy):
ipfs pin remote add --service=pinata /ipfs/<...Content Identifier (CID) of the out folder...>
Now all of the above as well as a dnslink
-based name resolution system are handled automatically via the Github Release workflow using Github encrypted secrets. SSL termination is handled using Cloudflare and pinning via Pinata. Yes, you have to actually email Cloudflare to ask them to set up the SSL certificate.
Anyone running ipfs
can support the project by pinning the latest version.
First find the CID hash on the latest release page, or by running:
dig +short txt _dnslink.bird-count.com
"dnslink=/ipfs/<...Content Identifier (CID) of the out folder...>"
(alternatively, you can run ipfs dns bird-count.com
to get the latest CID hash)
To pin this content, run:
ipfs pin add -r /ipfs/<...Content Identifier (CID) of the out folder...>
If you'd like to contribute to the project, here's my current TODO list.
- Fix onboarding to connect (or install) the Hiro Wallet (auto-dismiss popup after connect)
- Infinite query to scan blocks for history and portably cache scanned blocks
- User-defined nodes and explorers
- Sync
localStorage
with gaia hub - Scripts to help the community support the IPFS deployment
- Deployment scripts for contracts (
regtest
,testnet
,mainnet
) - BirdCount V2! A per-
principle
counter. V1 will continue to be maintained indefinitely - A more robust CI setup including
yarn changeset
- Integrate an external translation service
- Refactor with
@clarigen/web
to power the web app
TOFIX:
- Mobile !
- Finish autocomplete search
- Import
localStorage
from json file - Add completed transactions to drawer
- xx-YY locale support
- Fix background gradient with RTL locales
- There is at times an error about a className missmatch when running
yarn run dev
, does not seem to effect production. See e.g., mui/material-ui#18018 and mui/material-ui#27088 - Most of the app is wrapped in
NoSsr
becauseNext.js
does not currently supporti18n
staticexport
. The main implication is that only the English version of the page is built duringexport
and translations are loaded dynamically. - Because of this limitation, the language pages are implemented using
react-router-dom
HashRouter
rather thannext
router'susePath
or domain endpoints, which is less than ideal for SEO but in my view is better than the alternative (no static export). - The hosting environment (IPFS) can't produce responsive headers so that limits some interaction options. For example, the app can't perform an Accept-Language/Content-Language negotiation or respond to Clear-Site-Data.
Huge thanks to both @aulneau and @hstove for helping get these elements working together nicely. Many thanks also to the countless others on the Interwebs, too numerous to individually cite, links to whom I tried to remember to embed within comments of the relevant area of the codebase.