![A lightbulb and circuit board](/_next/image?url=%2Fapi%2Fmedia%2Ffile%2FIdea-Banner-reduced.jpg&w=1920&q=90)
How to Turn Your Idea into a Solana Program (Smart Contract)
Let's design a Solana program. The Solana ecosystem offers numerous articles on understanding the Solana programming model and creating small demo programs that are an excellent way for beginners to learn Solana and Anchor. However, real programs have more complex considerations.
In this article, we’ll look at a real program we want to make, and cover:
- Defining our data as Solana accounts
- Creating relationships between our data
- Storing tokens inside our program
- Using indexes to speed up our reads
- Using sharding to speed up writes
The aim of this example is to help you take your own ideas and understand how to model them as Solana programs and accounts.
Note: This article assumes you are familiar with the basics of Anchor.
Solana Program Example — Building a Prediction Market
The program we'll be designing today is a prediction market similar to Polymarket, Hedgehog, or Drift BET. If you’re not familiar with prediction markets, they allow people to bet on different outcomes for an event. This could be which team wins the Superbowl, who wins the ‘Best Director’ Oscar, whether the government will make an announcement by a particular time, or any other real-world event with a specific outcome. If people bet on the winning outcome, they receive winnings from the pool of bets.
Prediction Market Architecture
Here’s the core architecture of a prediction market and how each item relates to one another:
- There are multiple events.
- Each event has multiple outcomes. Eventually, one of these outcomes will be determined to be the winning outcome.
- Users place multiple bets on each outcome. When a user places a bet, their funds are added to the win pool for that event.
When the event is ‘resolved’ (i.e. when we know the winning outcome):
- Users that bet on the winning outcome will be able to claim their winnings
- Winners will receive a share of the win pool (minus a house take)
- Each winner's share of the win pool will be based on their portion of the bets on the winning outcome.
1. Designing Our App as Solana Accounts
If we were designing this program to use a relational database, we would be thinking about:
- Similar data items being stored as rows in a table
- Primary keys used to identify each piece of data uniquely
- The table’s columns define the expected attributes for each data item and their types
In Solana, these concepts roughly map to:
- Similar data items are stored using the same account type
- Addresses are used to identify each piece of data uniquely
- The struct (keys and data types) defines the attributes of each item
Here's the same data, shown in both a traditional database table and as Solana accounts:
2. Mapping Relationships Between Data
Relationships between data items work very differently in Solana. Traditional databases use relationships (i.e., the logical connection between tables). Solana handles one-to-many relationships as a vector of addresses, with each address containing an account with the item.
For example, events have 'outcomes' that are a vector of outcome addresses. Anchor shows this as Vec<Pubkey>, although PDA addresses (like our outcome) aren't really public keys.
3. Storing Tokens
Unlike traditional databases, Solana programs can also store funds—not just balance numbers—inside an account.
In our example, the event needs a token account for its win pool. The event's PDA will own the win pool account. When users make bets, they send tokens to this account, but more importantly, when they claim their bets, our program will sign the transaction as the event account to move tokens out of the win pool.
4. Reducing Read Effort
Our program needs to feel as responsive as possible to users, and as developers, we want to avoid unnecessary paying for unnecessary account reads. We can make our program both more responsive and more efficient with running totals and indexes.
Calculate Running Totals
When users claim their winnings, we'll need to know precisely how much was bet on each outcome. Prediction markets determine a winning user's payment based on the formula win pool × bet amount ÷ total bets on the winning outcome.
Right now, we only store the amount of each bet in the bet's account. To get the total bet on a particular outcome, we'd have to read every bet account and add up the amounts.
Instead, let's add a field to each outcome—total_amount—and increment when users place bets. That way, when users win, we can easily determine the payment amount without having to read every bet on that outcome.
Use Indexes
We'll also need to find all the bets from a particular user account. We could fetch every single bet account using getProgramAccounts() and filter for those with a better set to that user's address. While Helius’ fast getprogramAccounts() makes this a lot faster than on other RPC providers, indexes are a common alternative.
So, let's make an index to store each user's bets. When a user makes a new bet, we'll create this item if it doesn't exist and add the bet to that user's list of bets:
We'll also add tags to each event, so we can easily find all the events with the tag 'sports', ‘politics’, 'europe', 'usa', 'politics' etc. We’ll make another index for that:
We can now easily retrieve all the events of interest by looking at the event tag account.
5. Reducing Write Contention
Remember how each event has a single token account that stores the bets for that event? Every time a user adds a new bet, tokens will be moved into this account. In other words, the account will be written to.
Solana is fast because it parallelizes operations. However, updating the balance on a single account can't be done in parallel—it must be done sequentially because every account must have a single balance at any given time.
If a new event is announced and receives a lot of incoming bets, many writes will occur to the event's token account simultaneously, and our program's transactions may seem slow. This is called write contention—multiple transactions are competing for access to the account.
One way to allow parallelism in these cases is through sharding. A resource is split into multiple pieces—called shards—that can be accessed in parallel.
How Sharding Works
Incoming payments are sent to separate win_pool shards based on the value of the final byte of the better's public key, using the shard_num() macro. This ensures incoming payments are fast.
Later, an admin instruction handler can consolidate these into a single win pool account, ensuring we have liquidity in the same account to pay out the winners.
We also need to consider write contention if a popular event is finalized and all the winners claim their winnings at the same time. We'll also need to move funds from the win pool as fast as possible.
The best option here is to avoid a claim process. Instead, if we send the funds sequentially as soon as the event has been finalized, users don't have to deal with slow claims at all—their winnings will already be deposited into their account.
However, you need to actually have users before you can worry about crowds of users betting or claiming their winnings. If you're planning your Solana program and haven't launched, optimizations to handle large amounts of user traffic that you don't have are unnecessary. You should consider the additional complexity of larger optimizations like this if you don’t need them to launch, with an awareness that you may need to implement them when your program becomes more popular.
Conclusion
In this article, we took a deep dive into creating a real-world application on Solana. You now possess practical knowledge of defining Solana accounts, establishing data relationships, storing tokens, and optimizing performance using indexes and sharding.
If you have any follow-up questions, feel free to reach out to @heliuslabs on X or join the Helius Discord. We’ll be expanding on this prediction market example in the future, so be sure to follow those accounts for updates.
With these new skills, it’s time to turn your ideas into Solana programs and bring them to life within the Solana ecosystem. Happy coding!
Thanks, Ichigo, for reviewing this article and to r0bre for pointing out the account sharding technique used.
Additional Resources
Related Articles
Subscribe to Helius
Stay up-to-date with the latest in Solana development and receive updates when we post