github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/docs/how-to-guides/write-simple-dapp.md (about)

     1  ---
     2  id: write-simple-dapp
     3  ---
     4  
     5  # How to write a simple dApp on Gno.land
     6  
     7  ## Overview
     8  
     9  This guide will show you how to write a complete dApp that combines both a package and a realm.
    10  Our app will allow any user to create a poll, and subsequently vote
    11  YAY or NAY for any poll that has not exceeded the voting deadline.
    12  
    13  ## Defining dApp functionality
    14  
    15  Our dApp will consist of a Poll package, which will handle all things related to the Poll struct,
    16  and a Poll Factory realm, which will handle the user-facing functionality and rendering.
    17  
    18  For simplicity, we will define the functionality in plain text, and leave comments explaining the code.
    19  
    20  ### Poll Package
    21  
    22  - Defines a `Poll` struct
    23  - Defines a `NewPoll` constructor
    24  - Defines `Poll` field getters
    25  - Defines a `Vote` function
    26  - Defines a `HasVoted` check method
    27  - Defines a `VoteCount` getter method
    28  
    29  [embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-1.gno go)
    30  ```go
    31  package poll
    32  
    33  import (
    34  	"std"
    35  
    36  	"gno.land/p/demo/avl"
    37  )
    38  
    39  // Main struct
    40  type Poll struct {
    41  	title       string
    42  	description string
    43  	deadline    int64     // block height
    44  	voters      *avl.Tree // addr -> yes / no (bool)
    45  }
    46  
    47  // Getters
    48  func (p Poll) Title() string {
    49  	return p.title
    50  }
    51  
    52  func (p Poll) Description() string {
    53  	return p.description
    54  }
    55  
    56  func (p Poll) Deadline() int64 {
    57  	return p.deadline
    58  }
    59  
    60  func (p Poll) Voters() *avl.Tree {
    61  	return p.voters
    62  }
    63  
    64  // Poll instance constructor
    65  func NewPoll(title, description string, deadline int64) *Poll {
    66  	return &Poll{
    67  		title:       title,
    68  		description: description,
    69  		deadline:    deadline,
    70  		voters:      avl.NewTree(),
    71  	}
    72  }
    73  
    74  // Vote Votes for a user
    75  func (p *Poll) Vote(voter std.Address, vote bool) {
    76  	p.Voters().Set(voter.String(), vote)
    77  }
    78  
    79  // HasVoted vote: yes - true, no - false
    80  func (p *Poll) HasVoted(address std.Address) (bool, bool) {
    81  	vote, exists := p.Voters().Get(address.String())
    82  	if exists {
    83  		return true, vote.(bool)
    84  	}
    85  	return false, false
    86  }
    87  
    88  // VoteCount Returns the number of yay & nay votes
    89  func (p Poll) VoteCount() (int, int) {
    90  	var yay int
    91  
    92  	p.Voters().Iterate("", "", func(key string, value interface{}) bool {
    93  		vote := value.(bool)
    94  		if vote == true {
    95  			yay = yay + 1
    96  		}
    97  	})
    98  	return yay, p.Voters().Size() - yay
    99  }
   100  ```
   101  
   102  View this code in the Playground [here](https://play.gno.land/p/dwARIIq0meB).
   103  
   104  A few remarks:
   105  
   106  - We are using the `std` library for accessing blockchain-related functionality
   107  and types, such as `std.Address`.
   108  - Since the `map` data type is not deterministic in Go, we need to use the AVL 
   109  tree structure, defined
   110  under `gno.land/p/demo/avl`. It behaves similarly to a map; it maps a key of 
   111  type `string` onto a value of any type - `interface{}`.
   112  - We are importing the `gno.land/p/demo/avl` package directly from on-chain storage.
   113  You can find predeployed packages & libraries which provide additional Gno
   114  functionality in the [Gno monorepo](https://github.com/gnolang/gno), under the `examples/` folder.
   115  
   116  :::info
   117  After testing the `Poll` package, we need to deploy it in order to use it in our realm.
   118  Check out the [deployment](deploy.md) guide to learn how to do this.
   119  :::
   120  
   121  ### Poll Factory Realm
   122  
   123  Moving on, we can create the Poll Factory realm.
   124  
   125  The realm will contain the following functionality:
   126  
   127  - An exported `NewPoll` method, to allow users to create polls
   128  - An exported `Vote` method, to allow users to pledge votes for any active poll
   129  - A `Render` function to display the realm state
   130  
   131  [embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-2.gno go)
   132  ```go
   133  package poll
   134  
   135  import (
   136  	"bytes"
   137  	"std"
   138  
   139  	"gno.land/p/demo/avl"
   140  	"gno.land/p/demo/poll"
   141  	"gno.land/p/demo/seqid"
   142  	"gno.land/p/demo/ufmt"
   143  )
   144  
   145  // state variables
   146  var (
   147  	polls         *avl.Tree // id -> Poll
   148  	pollIDCounter seqid.ID
   149  )
   150  
   151  func init() {
   152  	polls = avl.NewTree()
   153  }
   154  
   155  // NewPoll - Creates a new Poll instance
   156  func NewPoll(title, description string, deadline int64) string {
   157  	// get block height
   158  	if deadline <= std.GetHeight() {
   159  		panic("deadline has to be in the future")
   160  	}
   161  
   162  	// Generate int
   163  	id := pollIDCounter.Next().String()
   164  	p := poll.NewPoll(title, description, deadline)
   165  
   166  	// add new poll in avl tree
   167  	polls.Set(id, p)
   168  
   169  	return ufmt.Sprintf("Successfully created poll #%s!", id)
   170  }
   171  
   172  // Vote - vote for a specific Poll
   173  // yes - true, no - false
   174  func Vote(id string, vote bool) string {
   175  	// get txSender
   176  	txSender := std.GetOrigCaller()
   177  
   178  	// get specific Poll from AVL tree
   179  	pollRaw, exists := polls.Get(id)
   180  
   181  	if !exists {
   182  		panic("poll with specified doesn't exist")
   183  	}
   184  
   185  	// cast Poll into proper format
   186  	poll, _ := pollRaw.(*poll.Poll)
   187  
   188  	voted, _ := poll.HasVoted(txSender)
   189  	if voted {
   190  		panic("you've already voted!")
   191  	}
   192  
   193  	if poll.Deadline() <= std.GetHeight() {
   194  		panic("voting for this poll is closed")
   195  	}
   196  
   197  	// record vote
   198  	poll.Vote(txSender, vote)
   199  
   200  	// update Poll in tree
   201  	polls.Set(id, poll)
   202  
   203  	if vote == true {
   204  		return ufmt.Sprintf("Successfully voted YAY for poll #%s!", id)
   205  	}
   206  	return ufmt.Sprintf("Successfully voted NAY for poll #%s!", id)
   207  }
   208  ```
   209  
   210  :::info
   211  Depending on where you deployed your `Poll` package, you will have to change its 
   212  import path in the realm code. 
   213  :::
   214  
   215  With that we have written the core functionality of the realm, and all that is left is
   216  the [Render function](../concepts/realms.md).
   217  Its purpose is to help us display the state of the realm in Markdown, by formatting the state into a string buffer:
   218  
   219  
   220  [embedmd]:# (../assets/how-to-guides/write-simple-dapp/poll-3.gno go)
   221  ```go
   222  func Render(path string) string {
   223  	var b bytes.Buffer
   224  
   225  	b.WriteString("# Polls!\n\n")
   226  
   227  	if polls.Size() == 0 {
   228  		b.WriteString("### No active polls currently!")
   229  		return b.String()
   230  	}
   231  	polls.Iterate("", "", func(key string, value interface{}) bool {
   232  
   233  		// cast raw data from tree into Poll struct
   234  		p := value.(*poll.Poll)
   235  		ddl := p.Deadline()
   236  
   237  		yay, nay := p.VoteCount()
   238  		yayPercent := 0
   239  		nayPercent := 0
   240  
   241  		if yay+nay != 0 {
   242  			yayPercent = yay * 100 / (yay + nay)
   243  			nayPercent = nay * 100 / (yay + nay)
   244  		}
   245  
   246  		b.WriteString(
   247  			ufmt.Sprintf(
   248  				"## Poll #%s: %s\n",
   249  				key, // poll ID
   250  				p.Title(),
   251  			),
   252  		)
   253  
   254  		dropdown := "<details>\n<summary>Poll details</summary><br>"
   255  
   256  		b.WriteString(dropdown + "Description: " + p.Description())
   257  
   258  		b.WriteString(
   259  			ufmt.Sprintf("<br>Voting until block: %d<br>Current vote count: %d",
   260  				p.Deadline(),
   261  				p.Voters().Size()),
   262  		)
   263  
   264  		b.WriteString(
   265  			ufmt.Sprintf("<br>YAY votes: %d (%d%%)", yay, yayPercent),
   266  		)
   267  		b.WriteString(
   268  			ufmt.Sprintf("<br>NAY votes: %d (%d%%)</details>", nay, nayPercent),
   269  		)
   270  
   271  		dropdown = "<br><details>\n<summary>Vote details</summary>"
   272  		b.WriteString(dropdown)
   273  
   274  		p.Voters().Iterate("", "", func(key string, value interface{}) bool {
   275  
   276  			voter := key
   277  			vote := value.(bool)
   278  
   279  			if vote == true {
   280  				b.WriteString(
   281  					ufmt.Sprintf("<br>%s voted YAY!", voter),
   282  				)
   283  			} else {
   284  				b.WriteString(
   285  					ufmt.Sprintf("<br>%s voted NAY!", voter),
   286  				)
   287  			}
   288  			return false
   289  		})
   290  
   291  		b.WriteString("</details>\n\n")
   292  		return false
   293  	})
   294  	return b.String()
   295  }
   296  ```
   297  
   298  View this code in the Playground [here](https://play.gno.land/p/5jgHw29sGq4).
   299  
   300  To see how to deploy this app, visit the [Deployment guide](./deploy.md). 
   301  
   302  ## Conclusion
   303  
   304  That's it 🎉
   305  
   306  You have successfully built a simple but fully-fledged dApp using Gno!
   307  Now you're ready to conquer new, more complex dApps in Gno.