Skip to main content

Quickstart

Build an On-Chain Messaging App

In this quickstart (about 10 minutes), we'll build an onchain chat room.

  1. Create and view messages that are stored off-chain.
  2. Modify our Tarobase policy to have future messages stored on-chain.
  3. Require a fee of $1 USDC in order to send future messages

Step 1: Create Your App on the Tarobase Console

Before you start coding, you need to create an app on the Tarobase Console.

  1. Go to the Tarobase Console.
  2. Sign in with your account or create a new one.
  3. Click on "Create New App" and follow the instructions to set up your new app. You may give your project a name like QuickstartChatRoom and use the default authentication and chain selections.
  4. Once your app is created, you will receive an App ID. Keep this ID handy as you will need it later.

Step 2: Initialize Your Project

You can use any TypeScript or JavaScript framework to build with Tarobase. Here we'll use Create React App, but feel free to use Next.js, Vue, or any other framework you prefer.

First, open a terminal and create a new React project:

npx create-react-app my-chat-app
cd my-chat-app

Install the Tarobase SDK:

npm install @tarobase/js-sdk
# or
yarn add @tarobase/js-sdk

Start the development server:

npm start
# or
yarn start

Step 3: Add Authentication

Let's add wallet authentication to allow users to connect their own wallet to your app, providing a way for your app to identify users.

Replace the contents of src/App.js with the following to include wallet login functionality:

import { init, onAuthStateChanged, login } from '@tarobase/js-sdk';
import { useEffect, useState } from 'react';

// Initialize Tarobase SDK
init({ appId: `INSERT_YOUR_TAROBASE_APP_ID_HERE` });

function App() {
const [user, setUser] = useState(null);
const [loadingUser, setLoadingUser] = useState(false);

useEffect(() => {
// Update the user if the user's auth state has changed.
onAuthStateChanged((user) => {
setLoadingUser(false);
setUser(user);
});
}, [])

const handleLogin = async () => {
try {
setLoadingUser(true);
// Login to Tarobase
await login();
} catch (e) {
console.error("Failed to login", e.message);
} finally {
setLoadingUser(false);
}
}

return (
<div className="App">
<h1>My Tarobase Chat App</h1>
{ loadingUser && <div>Loading...</div> }
{ !loadingUser && user ? (
<p>Connected as: {user.address}</p>
) : (
<button onClick={handleLogin}>Connect Wallet</button>
)}
</div>
);
}

export default App;

Note: Replace 'INSERT_YOUR_TAROBASE_APP_ID_HERE' with the App ID you obtained from the Tarobase Console.

In the above code, we are making use of 3 essential Tarobase SDK functions to handle our app's authentication:

  • init: Initialize your Tarobase app
  • login: Initiate a wallet login. The auth method you selected when setting up your Tarobase app will automatically be used here.
  • onAuthStateChanged: Listen for changes in the user's authentication state. To determine if the user is logged in or not.

Try clicking the Connect Wallet button now to see the wallet connection prompt and log in to your account. You may need to click "enable pop-ups" when prompted to allow the log in to work.

Step 4: Create and View Messages Off-Chain

Now, let's allow users to create and view messages. We'll start by setting up a Tarobase policy for our app.

Understanding Tarobase Policies

Before we continue, let's understand what a Tarobase policy is and the policy language used in Tarobase. Policies are used to protect your data and ensure that only authorized users can perform specific actions on your app both on-chain and off-chain. The policy language is similar to JavaScript but with special variables and operators designed specifically for access control:

  • @prevState: The current state of a document before an update
  • @newState: The proposed new state of a document during an update
  • @walletAddress: The wallet address of the user making the request

The policy language allows you to write conditions using these variables along with standard JavaScript-like operators (&&, ||, ==, etc.).

Set Up a Tarobase Policy for Messages in our Chat Room

  1. Navigate to your project in the Tarobase Console
  2. Go to the "Policy" section under Project Details.
  3. Replace the existing policy with the following:
{
"messages/$messageId": {
"rules": {
"read": "true",
"write": "@prevState == null && @newState.text != null && @newState.createdBy == @walletAddress"
},
"fields": {
"createdBy": "Types.Address",
"text": "Types.String"
}
}
}
  1. Click the Save & Deploy button to apply the policy changes.

Policy Explanation:

  1. Anyone can read messages ("read": "true")
  2. New messages can only be created (written) if:
    • The message doesn't already exist (@prevState == null)
    • The message has text content (@newState.text != null)
    • The message creator's address matches the wallet address of the sender (@newState.createdBy == @walletAddress)
  3. The policy defines createdBy as an address and text as a string, allowing these fields to be stored within each message document.

For more information on policies and how to write and use them, you can visit our Policy Documentation.

Step 5: Update Your App to Send and Receive Messages

First, let's add the new imports we'll need from the SDK to the top of our app:

import { set, subscribe } from '@tarobase/js-sdk';

Now let's update the App component with message functionality:


function App() {
// Existing state
const [user, setUser] = useState(null);
const [loadingUser, setLoadingUser] = useState(false);

// New state for messages
const [messages, setMessages] = useState([]);
const [newMessageText, setNewMessageText] = useState('');
const [lastTransactionId, setLastTransactionId] = useState(null);
const [sendingMessage, setSendingMessage] = useState(false);

useEffect(() => {
onAuthStateChanged((user) => {
setLoadingUser(false);
setUser(user);
});

// Subscribe to messages
subscribe('messages', {
onData: (data) => setMessages(data.reverse()),
onError: (e) => console.error("Failed to get messages", e.message),
});
}, []);

// Existing login handler
const handleLogin = async () => {
setLoadingUser(true);
await login();
setLoadingUser(false);
}

// New message handler
const handleSendMessage = async (e) => {
e.preventDefault();
if (!newMessageText.trim()) return;

setSendingMessage(true);
try {
const setResult = await set('messages', {
createdBy: user.address,
text: newMessageText,
});
if (setResult.transactionId) { setLastTransactionId(setResult.transactionId) }
setNewMessageText('');
} catch (error) {
console.error("Failed to send message", error);
} finally {
setSendingMessage(false);
}
return false;
}

return (
<div className="App">
<h1>My Tarobase Chat App</h1>
{loadingUser && <div>Loading...</div>}
{!loadingUser && user ? (
<>
<p>Connected as: {user.address}</p>
<div style={{ border: '1px solid black', height: '200px', overflow: 'auto', display: 'flex', flexDirection: 'column-reverse', maxWidth: '400px', margin: 'auto' }}>
{messages.map((message, i) => (
<div key={i} style={{ padding: '10px', margin: '5px', borderRadius: '5px', backgroundColor: '#f0f0f0', alignSelf: 'flex-start' }}>
<span style={{ fontWeight: 'bold', fontSize: '12px' }}>{message.createdBy.substring(0, 6)}...{message.createdBy.substring(message.createdBy.length - 4)}: </span>
{message.text}
</div>
))}
</div>
<form onSubmit={handleSendMessage}>
<input
type="text"
value={newMessageText}
onChange={(e) => setNewMessageText(e.target.value)}
/>
<button type="submit">Send Message</button>
</form>
{sendingMessage && <div>Sending message...</div>}
{lastTransactionId && (
<div>
<p>Last Transaction ID: <a href={`https://sepolia.basescan.org/tx/${lastTransactionId}`} target="_blank" rel="noopener noreferrer">{lastTransactionId}</a></p>
</div>
)}
</>
) : (
<button onClick={handleLogin}>Connect Wallet</button>
)}
</div>
);
}

Explanation:

  • subscribe('messages', setMessages);: Subscribes to the messages collection and updates the messages state whenever there are changes.
  • set('messages', { ... }): Adds a new message document to the messages collection. Since no id was specified like messages/abc, a randomly generated id will be used for the newly set message.
  • Messages are displayed in real-time as they are added.

Test the Messaging App:

Now that we've set up our chat app, test it out! Type a message in the input box and click Send Message to post it to the chat. You should see your message appear in the chat window.

Step 6: Modify the Policy to Store Messages On-Chain and Require a Fee

Let's enhance our messaging app by adding two key features:

  1. Store all messages on-chain for permanent record-keeping
  2. Require users to pay a $1 USDC fee to post messages

With Tarobase's policy system, we can add these features by simply updating the policy rules - no changes to our application code are needed.

Update your policy in the Tarobase console with the following configuration:

{
"messages/$messageId": {
"rules": {
"read": "true",
"write": "@prevState == null && @newState.text != null && @newState.createdBy == @walletAddress"
},
"fields": {
"createdBy": "Types.Address",
"text": "Types.String"
},
"onchain": true,
"triggers": [
"create: @TokenPlugin.transferWholeTokens(@TokenPlugin.USDC, @walletAddress, 0x000000000000000000000000000000000000dEaD, 1)"
]
}
}

Let's take a closer look at the changes to the policy we made and how they work together to enable this functionality.

{
"messages/$messageId": {
// ... previous rules and fields remain the same ...

// Enable on-chain storage
"onchain": true,

// Define additional actions to execute on message creation
"triggers": [
// Transfer 1 USDC from sender to specified address
"create: @TokenPlugin.transferWholeTokens(@TokenPlugin.USDC, @walletAddress, 0x000000000000000000000000000000000000dEaD, 1)"
]
}
}

The policy uses triggers to specify additional on-chain actions after a successful write:

  • @TokenPlugin is a plugin provided by Tarobase to give your app access to additional token functionality
  • @TokenPlugin.transferWholeTokens(...) transfers 1 USDC token from the sender's wallet (@walletAddress)
  • The recipient address 0x000000000000000000000000000000000000dEaD is a "burn address" - tokens sent here can never be recovered
  • You can optionally replace the burn address with your own wallet address to collect the fees to your wallet instead

After making these changes to your policy, make sure to click Save & Deploy to apply them. This step will take a bit longer than the last because Tarobase is deploying a new smart contract to the Base Sepolia network that will power your app's on-chain message storage and fee collection functionality.

For more information on policies, triggers, and plugins, you can visit our Policy Documentation.

Step 7: Test the On-Chain Messaging App

  1. Get Testnet USDC Funds: Head to the USDC faucet to send yourself some USDC so that you can test sending messages in the app. When filling out the form in the faucet, select USDC, select the network of Base Sepolia, and copy over the wallet address that shows on the top of your app when you connect your wallet. Then click Send 10 USDC, and 10 USDC should be sent to your wallet shortly. alt text
  2. Restart the App: If necessary, restart your development server to ensure changes take effect.
  3. Connect Wallet: Authenticate again if prompted.
  4. Send a Message:
    • Type a message and click "Send Message".
  5. Confirm Transactions:
    • Your wallet should prompt you to confirm 2 transactions for the $1 USDC transfer. The first is to allow your Tarobase app to transfer USDC on your behalf, and the second is to send $1 USDC and place your message on-chain.
    • Confirm the transaction to send the message and the $1.
  6. Verify On-Chain Storage:
    • The message will appear in the chat room. The message was stored both on-chain and it is available on Tarobase for you to retrieve and subscribe to like you normally would

Conclusion

This quickstart demonstrates how to use Tarobase to build an on-chain messaging app. You started with off-chain messages and seamlessly transitioned to storing messages on-chain and requiring a fee, all by updating the Tarobase policy with minimal or no changes to your code.

Quick Feedback Survey

We are an early-stage product, and would greatly appreciate you fill out this survey after having completed the quickstart guide to help us improve. Thank you!

For more advanced features and best practices, check out our other guides:

Feel free to reach out in the discord if you have any questions or need further assistance. Happy coding!