Flexi MVP is a frontend-first application for managing flexible memberships, venue operations, and marketplace flows.
This repo now also contains a minimal Node.js sponsorship backend that uses the official IOTA TypeScript SDK and implements one real sponsored transaction flow on the application side.
- marketing and landing pages
- a client area for memberships, marketplace activity, transactions, and wallet views
- a venue area for dashboards, membership management, sales, resales, and access validation
- public venue pages
- a minimal Express backend endpoint for sponsored IOTA transactions
- React 18
- TypeScript
- Vite
- Node.js / Express
- IOTA TypeScript SDK (
@iota/iota-sdk) - React Router
- TanStack Query
- Tailwind CSS
- shadcn/ui
- Framer Motion
- Recharts
- Leaflet
- Node.js 24+
- npm
Frontend development:
npm install
npm run devThe Vite development server runs on http://localhost:5173.
The backend follows the IOTA sponsored transaction pattern documented for the TypeScript SDK:
- Build the user action with
onlyTransactionKind. - Reconstruct it with
Transaction.fromKind. - Set
sender,gasOwner, andgasPayment. - Sign the full sponsored transaction bytes with the sponsor key.
- Return the sponsored transaction bytes and sponsor signature so the user wallet can add the second signature and submit.
This is a Gas Station-aligned custom backend flow. It is not a full production Gas Station deployment with operator tooling, quotas, persistence, or packaged infra.
POST /api/iota/sponsor-transaction
Supported action type:
configured_move_call
The backend supports one configured Move-call action. The deployed target is supplied through environment variables, so the MVP can call the existing on-chain package without copying that package into this repo.
Use .env.example as the starting point.
Required:
IOTA_RPC_URL- one sponsor secret source:
IOTA_SPONSOR_SECRET_KEYIOTA_SPONSOR_PRIVATE_KEYIOTA_SPONSOR_MNEMONIC
- one sponsored target source:
IOTA_SPONSORED_ACTION_TARGET- or
IOTA_PACKAGE_ID+IOTA_MODULE_NAME+IOTA_FUNCTION_NAME
Optional:
PORTIOTA_NETWORKIOTA_SPONSOR_DERIVATION_PATHIOTA_SPONSOR_ADDRESSIOTA_SPONSORED_ACTION_TYPE_ARGUMENTSIOTA_SPONSOR_MAX_GAS_COINSIOTA_SPONSOR_MIN_TOTAL_GAS_BALANCE
If you want .env loading without exporting variables manually:
cp .env.example .env
npm run start:envnpm start also works if the environment variables are already exported in your shell.
curl -X POST http://localhost:3033/api/iota/sponsor-transaction \
-H "Content-Type: application/json" \
-d '{
"userAddress": "0x4d4fb0e2cb1b573b8bc40d96fdc81de0cdd50f4f1dc5af149048393ca5c7d100",
"actionType": "configured_move_call",
"payload": {
"arguments": [
{ "kind": "object", "value": "0xobject_id_used_by_your_move_call" },
{ "kind": "pure", "valueType": "u64", "value": "1" },
{ "kind": "pure", "valueType": "string", "value": "basic-plan" }
]
}
}'{
"ok": true,
"actionType": "configured_move_call",
"sender": "0x4d4fb0e2cb1b573b8bc40d96fdc81de0cdd50f4f1dc5af149048393ca5c7d100",
"gasOwner": "0x99f4fbc4f17a1425dd8b50d7c403039bec2d1bdccf3d4f42d5cd0484391d4ed1",
"rpcUrl": "https://api.testnet.iota.cafe",
"network": "testnet",
"target": "0xabc::memberships::mint",
"typeArguments": [],
"transactionKindBytes": "base64-kind-bytes",
"sponsoredTransactionBytes": "base64-full-transaction-bytes",
"sponsorSignature": "base64-sponsor-signature",
"digest": "transaction-digest",
"gasBudget": "1000",
"gasPrice": "100",
"gasPayment": [
{
"objectId": "0x8e88e1c23d9c34b15b175b47c80f0a853717fd0975027ce890d90c6bde2b0a5b",
"version": "7",
"digest": "coin-digest",
"balance": "2000000"
}
]
}The backend intentionally stops after sponsorship. The frontend wallet flow still needs to:
- Call
POST /api/iota/sponsor-transaction. - Receive
sponsoredTransactionBytesandsponsorSignature. - Ask the connected user wallet to sign the exact same sponsored transaction bytes.
- Submit the transaction with both signatures using the wallet SDK or
IotaClient.executeTransactionBlock({ transactionBlock, signature: [sponsorSignature, userSignature] }).
npm run devstarts the local frontend development servernpm run buildcreates a production build indist/npm run build:devcreates a development-mode buildnpm run previewpreviews the production build locallynpm run lintruns ESLintnpm run testruns Vitest oncenpm run test:watchruns Vitest in watch modenpm run startstarts the Express servernpm run start:envstarts the Express server and loads.env
src/App.tsxcontains the route mapsrc/pagescontains page-level screenssrc/componentscontains shared UI, layout, and feature componentssrc/datacontains mock datasets used by the interfaceserver/contains the backend sponsorship modulespubliccontains static files served directly
Vitest is available for unit and component tests. Playwright configuration is included for browser automation.
For the sponsorship backend:
npm testcovers request validation and response shape forPOST /api/iota/sponsor-transaction- a live end-to-end sponsorship test still requires a real sponsor wallet, RPC URL, and deployed Move target