-
Notifications
You must be signed in to change notification settings - Fork 58
feat(docs): Add Spark Mini, a Gas-Free Microblogging with IOTA Gas Station workshop #9497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ginowine
wants to merge
4
commits into
develop
Choose a base branch
from
devx/gas-station-workshop
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
375 changes: 375 additions & 0 deletions
375
docs/content/developer/workshops/gas-station-workshop.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,375 @@ | ||
| --- | ||
| description: Workshop on Microblogging Platform using IOTA Gas Station | ||
| tags: | ||
| - tutorial | ||
| --- | ||
|
|
||
| # IOTA Gas Station Workshop | ||
|
|
||
| In this hands-on workshop you will build Spark Mini, a fully functional, decentralized microblogging platform where anyone can post messages without owning a single token or paying gas fees. | ||
| ## Learning Objectives | ||
|
|
||
| By the end of this workshop, you will: | ||
|
|
||
| - Understand [sponsored transactions](https://docs.iota.org/developer/iota-101/transactions/sponsored-transactions/about-sponsored-transactions) and how the [Gas Station](https://docs.iota.org/operator/gas-station/) enables gas-free UX. | ||
| - Deploy a local Gas Station and fund it for sponsorship. | ||
| - Build a React frontend with [IOTA dApp Kit](https://docs.iota.org/developer/ts-sdk/dapp-kit/) for wallet connection and UI. | ||
| - Implement on-chain posting using the [IOTA TypeScript SDK](https://docs.iota.org/developer/ts-sdk/typescript/). | ||
| - Integrate the full sponsored flow: reserve gas, user sign, Gas Station co-sign/submit. | ||
| - Test and debug your dApp with real testnet transactions. | ||
|
|
||
| ## Why IOTA Gas Station? | ||
|
|
||
| In traditional blockchains, users must buy tokens, fund wallets, and pay `gas fees` for every action, like posting a message. This creates massive friction, preventing adoption. IOTA Gas Station solves this by letting dApp operators cover fees, so users focus on creating content. Spark Mini demonstrates this with a Twitter-like platform: posts are immutable on IOTA testnet, but completely free. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| Before starting, ensure you have: | ||
|
|
||
| - Basic Knowledge: Familiarity with JavaScript/TypeScript, React, and command line. No prior IOTA experience needed. | ||
| - Tools: | ||
| - Node.js (v18+) and npm/yarn. | ||
| - Git. | ||
| - Docker Desktop (for Gas Station deployment). | ||
| - VS Code (recommended). | ||
| - Hardware: Mac/Linux/Windows with 4GB+ RAM. | ||
|
|
||
| If you're missing anything, install now: | ||
|
|
||
| - [Node.js](https://nodejs.org/en/download) | ||
| - [Docker](https://docs.docker.com/desktop/setup/install/mac-install/) | ||
| - [VS Code](https://code.visualstudio.com/download) | ||
|
|
||
| ## Part 1 – Project Setup & Architecture | ||
|
|
||
| We'll build a full-stack dApp: | ||
|
|
||
| - Frontend (`spark-mini-frontend`): React + Vite + dApp Kit for UI, wallet connect, and tx submission. | ||
| - Gas Station (official Docker): Sponsors fees; no custom backend needed for this workshop. | ||
| - On-Chain Logic: Uses a demo Move package for storing posts (immutable on testnet). | ||
|
|
||
| ## 1.1 Create the Workshop Folder | ||
|
|
||
| ```Bash | ||
| mkdir spark-mini-workshop && cd spark-mini-workshop | ||
| ``` | ||
| ### Explanation | ||
|
|
||
| This creates a dedicated folder for the entire workshop. All code and services will live here. | ||
|
|
||
| ## 1.2 Clone and Launch the Official IOTA Gas Station | ||
|
|
||
| ```Bash | ||
| git clone https://github.com/iotaledger/gas-station.git | ||
| cd gas-station | ||
| ../utils/./gas-station-tool.sh generate-sample-config --config-path config.yaml --docker-compose -n testnet | ||
| GAS_STATION_AUTH=supersecret123 docker compose up -d | ||
| ``` | ||
| ### Explanation | ||
|
|
||
| We are deploying the official, production-ready Gas Station using Docker. The `generate-sample-config` tool creates a testnet-ready configuration. The environment variable sets the auth token to `supersecret123`. | ||
|
|
||
| ## 1.3 Verify the Gas Station is Healthy | ||
|
|
||
| ```Bash | ||
| curl http://localhost:9527/health | ||
| ``` | ||
| ### Expected output: | ||
|
|
||
| ```JSON | ||
| {"status":"healthy"} | ||
| ``` | ||
| ### Explanation | ||
|
|
||
| A successful response confirms the RPC server is listening on port 9527 and ready to sponsor transactions. | ||
|
|
||
| ## 1.4 Create the React Frontend | ||
|
|
||
| ```bash | ||
| cd .. | ||
| npx create-vite@latest frontend --template react-ts | ||
| cd frontend | ||
| npm install | ||
| npm install @iota/dapp-kit @iota/iota-sdk @tanstack/react-query axios | ||
| ``` | ||
|
|
||
| ### Explanation | ||
| We scaffold a modern Vite + React + TypeScript project and install: | ||
|
|
||
| - @iota/dapp-kit – wallet connection and hooks | ||
| - @iota/iota-sdk – transaction building | ||
| - `axios` – HTTP calls to the Gas Station | ||
|
|
||
| ## 1.5 Start the Dev Server | ||
|
|
||
| ```Bash | ||
| npm run dev | ||
| ``` | ||
| ### Explanation | ||
| Your frontend is now live at http://localhost:5173. You’ll see a blank page for now, that’s expected. | ||
|
|
||
| ## Part 2 – Wallet Connection with dApp Kit | ||
|
|
||
| ### 2.1 Replace `src/main.tsx` (Providers) | ||
|
|
||
| ```tsx | ||
| // src/main.tsx | ||
| import React from 'react'; | ||
| import ReactDOM from 'react-dom/client'; | ||
| import App from './App.tsx'; | ||
| import { | ||
| createNetworkConfig, | ||
| IotaClientProvider, | ||
| WalletProvider, | ||
| } from '@iota/dapp-kit'; | ||
| import { getFullnodeUrl } from '@iota/iota-sdk/client'; | ||
| import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | ||
| import '@iota/dapp-kit/dist/index.css'; | ||
|
|
||
| const { networkConfig } = createNetworkConfig({ | ||
| testnet: { url: getFullnodeUrl('testnet') }, | ||
| }); | ||
|
|
||
| const queryClient = new QueryClient(); | ||
|
|
||
| ReactDOM.createRoot(document.getElementById('root')!).render( | ||
| <React.StrictMode> | ||
| <QueryClientProvider client={queryClient}> | ||
| <IotaClientProvider networks={networkConfig} defaultNetwork="testnet"> | ||
| <WalletProvider> | ||
| <App /> | ||
| </WalletProvider> | ||
| </IotaClientProvider> | ||
| </QueryClientProvider> | ||
| </React.StrictMode> | ||
| ); | ||
| ``` | ||
| ### Explanation | ||
|
|
||
| These providers are mandatory for any dApp Kit hook to work (`useWallets`, `useSignTransaction`, etc.). The CSS import gives us the beautiful default ConnectButton styling. | ||
|
|
||
| ### 2.2 Basic UI with ConnectButton | ||
|
|
||
| ```tsx | ||
| // src/App.tsx (replace everything) | ||
| import React from 'react'; | ||
| import { ConnectButton, useWallets } from '@iota/dapp-kit'; | ||
|
|
||
| export default function App() { | ||
| const wallets = useWallets(); | ||
| const connectedWallet = wallets.find(w => w.accounts.length > 0); | ||
| const address = connectedWallet?.accounts[0]?.address; | ||
|
|
||
| return ( | ||
| <div style={{ maxWidth: 600, margin: '40px auto', textAlign: 'center', fontFamily: 'system-ui' }}> | ||
| <h1>Spark Mini</h1> | ||
| <p>Gas-free microblogging on IOTA testnet</p> | ||
| {!connectedWallet ? ( | ||
| <> | ||
| <h2>Connect your wallet to start posting</h2> | ||
| <ConnectButton /> | ||
| </> | ||
| ) : ( | ||
| <p>Connected: {address?.slice(0, 12)}...{address?.slice(-8)}</p> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ### Explanation | ||
|
|
||
| `useWallets` returns all detected wallets. When a user connects, `accounts.length > 0`. The `ConnectButton` automatically opens the official modal and handles all wallet standards. | ||
| Test it: Click Connect → Choose Firefly → Success! You now see your address. | ||
|
|
||
| ## Part 3 – Fund the Gas Station Sponsor | ||
|
|
||
| ### 3.1 Find the Sponsor Address | ||
|
|
||
| ```Bash | ||
| cat gas-station/config.yaml | grep -A 5 signer-config | ||
| ``` | ||
|
|
||
| You’ll see something like: | ||
|
|
||
| ```yaml | ||
| address: "0xc6254a6dd24bc7cc975a0890d4092c3d0c0996e8783a2e3ff338f47705405b72" | ||
| ``` | ||
| ### Explanation | ||
|
|
||
| This is the address that will pay for everyone’s transactions. | ||
|
|
||
| ### 3.2 Fund It via Faucet | ||
|
|
||
| ### 3.3 Verify Funding | ||
|
|
||
| ### Explanation | ||
|
|
||
| You should see a positive balance and many coin objects after a few seconds. | ||
|
|
||
| ## Part 4 – Implement the Sponsored Post Flow | ||
|
|
||
| ### 4.1 Add Constants & Imports | ||
|
|
||
| ```tsx | ||
| // Top of src/App.tsx (add these) | ||
| import { useIotaClient, useSignTransaction } from '@iota/dapp-kit'; | ||
| import { Transaction } from '@iota/iota-sdk/transactions'; | ||
| import axios from 'axios'; | ||
|
|
||
| const GAS_STATION_URL = 'http://localhost:9527'; | ||
| const GAS_STATION_AUTH = 'supersecret123'; | ||
| const PACKAGE_ID = '0x1111111111111111111111111111111111111111111111111111111111111111'; // Official demo package | ||
Ginowine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
| ### Explanation | ||
|
|
||
| - P`ACKAGE_ID` is the official demo media package that contains post_message(string). | ||
| - The Gas Station URL and auth token match our Docker setup. | ||
|
|
||
| ## 4.2 Full Post Handler (copy-paste this function) | ||
|
|
||
| ```tsx | ||
| const client = useIotaClient(); | ||
| const { mutateAsync: signTransaction } = useSignTransaction(); | ||
| const [content, setContent] = useState(''); | ||
| const [posts, setPosts] = useState<Array<{content: string, author: string, txid: string}>>([]); | ||
|
|
||
| const handlePost = async () => { | ||
| if (!address || !content.trim()) return; | ||
|
|
||
| try { | ||
| // 1. Build transaction | ||
| const tx = new Transaction(); | ||
| tx.setSender(address!); | ||
| tx.moveCall({ | ||
| target: `${PACKAGE_ID}::media::post_message`, | ||
| arguments: [tx.pure.string(content)], | ||
| }); | ||
|
|
||
| // 2. Reserve gas | ||
| const reserveRes = await axios.post( | ||
| `${GAS_STATION_URL}/v1/reserve_gas`, | ||
| { gas_budget: 50_000_000, reserve_duration_secs: 15 }, | ||
| { headers: { Authorization: `Bearer ${GAS_STATION_AUTH}` } } | ||
| ); | ||
| const { sponsor_address, reservation_id, gas_coins } = reserveRes.data.result; | ||
|
|
||
| // 3. Attach sponsor gas data | ||
| tx.setGasOwner(sponsor_address); | ||
| tx.setGasPayment(gas_coins); | ||
| tx.setGasBudget(50_000_000); | ||
|
|
||
| // 4. Build unsigned bytes | ||
| const unsignedTxBytes = await tx.build({ client }); | ||
|
|
||
| // 5. User signs (wallet popup!) | ||
| const { signature, reportTransactionEffects } = await signTransaction({ transaction: tx }); | ||
|
|
||
| // 6. Send to Gas Station for co-sign + execution | ||
| const txBytesBase64 = btoa(String.fromCharCode(...new Uint8Array(unsignedTxBytes))); | ||
| const executeRes = await axios.post( | ||
| `${GAS_STATION_URL}/v1/execute_tx`, | ||
| { reservation_id, tx_bytes: txBytesBase64, user_sig: signature }, | ||
| { headers: { Authorization: `Bearer ${GAS_STATION_AUTH}` } } | ||
| ); | ||
|
|
||
| // 7. Report success to wallet | ||
| reportTransactionEffects(executeRes.data.effects); | ||
|
|
||
| // 8. Update UI | ||
| setPosts(prev => [{ | ||
| content, | ||
| author: address!.slice(0, 10) + '...', | ||
| txid: executeRes.data.effects.transactionDigest, | ||
| }, ...prev]); | ||
| setContent(''); | ||
| alert('Spark posted on-chain!'); | ||
|
|
||
| } catch (err: any) { | ||
| console.error(err); | ||
| alert('Failed: ' + (err.response?.data?.error || err.message)); | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ### Explanation | ||
|
|
||
| This is the complete sponsored transaction flow: | ||
|
|
||
| - User builds intent | ||
| - Reserves gas from sponsor | ||
| - Attaches sponsor’s gas objects | ||
| - Signs with wallet (Firefly popup) | ||
Ginowine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - Gas Station co-signs and submits | ||
| - Success → on-chain forever! | ||
|
|
||
| ## 4.3 Final UI with Textarea & Feed | ||
|
|
||
| Replace the return block with this beautiful final version (includes textarea, button, and feed). | ||
|
|
||
| ```tsx | ||
| return ( | ||
| <div style={{ maxWidth: 600, margin: '40px auto', fontFamily: 'system-ui' }}> | ||
| <h1>Spark Mini</h1> | ||
| <p>Gas-free microblogging on IOTA testnet</p> | ||
|
|
||
| {!address ? ( | ||
| <div style={{ textAlign: 'center', marginTop: 80 }}> | ||
| <h2>Connect your wallet to post</h2> | ||
| <ConnectButton /> | ||
| </div> | ||
| ) : ( | ||
| <> | ||
| <p>Connected: {address.slice(0, 12)}...{address.slice(-8)}</p> | ||
| <textarea | ||
| value={content} | ||
| onChange={e => setContent(e.target.value)} | ||
| placeholder="What's your spark? (280 chars)" | ||
| maxLength={280} | ||
| rows={4} | ||
| style={{ width: '100%', padding: 12, fontSize: 16, borderRadius: 8, border: '1px solid #ccc' }} | ||
| /> | ||
| <button | ||
| onClick={handlePost} | ||
| style={{ marginTop: 12, padding: '12px 24px', background: '#0068FF', color: 'white', border: 'none', borderRadius: 8, fontSize: 16 }} | ||
| > | ||
| Post Spark | ||
| </button> | ||
|
|
||
| <hr style={{ margin: '40px 0' }} /> | ||
|
|
||
| {posts.map((p, i) => ( | ||
| <div key={i} style={{ padding: 16, border: '1px solid #eee', borderRadius: 12, marginBottom: 12 }}> | ||
| <strong>{p.author}:</strong> {p.content} | ||
| <br /> | ||
| <small> | ||
| <a href={`https://explorer.iota.org/testnet/transaction/${p.txid}`} target="_blank"> | ||
Ginowine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| View on Explorer | ||
| </a> | ||
| </small> | ||
| </div> | ||
| ))} | ||
| </> | ||
| )} | ||
| </div> | ||
| ); | ||
| ``` | ||
|
|
||
| ### Explanation | ||
|
|
||
| Clean, responsive UI with live feed and explorer links. Every post is permanently stored on IOTA testnet. | ||
|
|
||
| ## Final Test | ||
|
|
||
| - `npm run dev` | ||
| - Connect Firefly | ||
Ginowine marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - Type a message → Post Spark | ||
| - Approve in wallet | ||
| - Success! Your message appears + explorer link works | ||
|
|
||
| ## Resources | ||
|
|
||
| - Gas Station Docs: https://docs.iota.org/operator/gas-station | ||
| - dApp Kit: https://docs.iota.org/developer/ts-sdk/dapp-kit | ||
| - Discord: | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.