Case study
HashgreenSwap
Frontend Engineer · Hashgreen Labs · 2023 – Present
The AMM swap interface for Chia CAT tokens, built on the Project Pyke exchange backend.
- Next.js
- Radix UI
- Redux Toolkit
- Nx

Swap confirm & wallet signature
Swapping on a logarithmic curve
Type an amount and get a live quote — minimum received, price impact, and fee — in either direction, exact-in or exact-out.
I built the swap engine. I ported Pyke's AMM pricing out of the implementation paper into one pure function and wired both quote directions — exact-out inverts the same invariant, it isn't a second approximation.
Pyke is not constant-product (x·y=k) — calcUserSwap in utils/swap.ts solves a log-space invariant with a pool fee φ and a safety margin ε = 1e-5; price impact is |1 − actual/spot|. The function runs client-side on every keystroke.
- Rate
- 1 XCH ≈ 0.3800 hUSDC
- Minimum received
- 362.4656 hUSDC
- Price impact
- 4.13%
- Liquidity fee (300 bps)
- 30.0000 XCH
Mirrors src/utils/swap.ts · src/hooks/useSwapQuote.ts
Sign once, settle on-chain
Execute with no centralized order book: the browser assembles a signed Chia offer, the chain settles it, and the UI tracks it to confirmation.
I unified three wallet backends behind one `IWallet.createOffer` interface — Goby, Hoogii, and Chia over WalletConnect — so the app never knows which wallet signed, and I built the recent-transaction tracker on top.
A swap is a Chia offer signed with BLS keys in the wallet; the CLVM puzzle-reveal bundle is built client-side, then the offer walks VALID → IN_MEMPOOL → ON_CHAIN — INVALID means a coin it spent was used elsewhere first.
Connect a wallet to sign the offer.
Mirrors src/wallets/IWallet.ts · src/components/RecentTransactions.tsx
One-sided liquidity, pool-share math
Add liquidity — including Zap, a single-asset deposit — with share-of-pool and LP minted shown as you type.
I built the liquidity forms and the paired-input hook that locks the two deposits to the pool ratio, then the Zap toggle that collapses them into one input for single-sided deposits.
In balanced mode useAssetInputPair pins the other side to the reserve ratio; Zap converts part of one asset before minting — which is why the backend counts add_liquidity and add_liquidity_zap as distinct tx types.
- Share of pool
- 5.7471%
- LP received
- 76,219.51 LP
- Pool ratio
- 1 XCH : 0.3800 hUSDC
Mirrors src/hooks/useAssetInputPair.ts · src/components/LiquidityPanel.tsx
Shipping it: one image, four environments
One container image is promoted through sandbox → UAT → staging → prod on Kubernetes, each with its own config and autoscaling.
I owned delivery — the multi-stage Dockerfile that ships Next's standalone output as a non-root container, the Helm chart with per-env values, and the GitHub Actions that build, push, and helm-upgrade.
The build is base → deps → builder → runner on node:20-alpine; an ARG selects .env.{uat|stg|sandbox|prod}, output: 'standalone' keeps the image tiny, and the runner drops to uid 1001.
image: repository: registry.hashgreen.net/pyke-swap tag: sha-3f9c1a2 replicaCount: 2 ingress: host: swap.uat.hashgreen.net autoscaling: minReplicas: 2 maxReplicas: 4 targetCPUUtilizationPercentage: 70
Mirrors Dockerfile · chart/values.{env}.yaml · .github/workflows/deploy.yml
One library, three languages
Every page reuses the same primitives and tokens, and the UI switches EN / Simplified / Traditional Chinese at runtime — no reload.
I built the shared `@hashgreen/ui-libraries` package the app consumes, and wired `next-i18next` with a chained backend so a language flips in place without a round-trip.
Language runs through i18next-chained-backend — localStorage first, HTTP second — with per-namespace JSON in public/locales/{en,zh-CN,zh-TW}, and the color and text tokens live once in the shared package.
- You pay
- 1,000.00 XCH
- You receive
- 364.29 hUSDC
- Minimum received
- 362.47 hUSDC
- Price impact
- 4.13%
- Liquidity fee
- 30.00 XCH
Mirrors libs/ui-libraries/pyke · next-i18next.config.js · public/locales/