Skip to content

Commit

Permalink
feat(grc20-launchpad): UI implementation (#1276)
Browse files Browse the repository at this point in the history
* feat(launchpad-grc20): create new screen & setup navigation

* feat(launchpad-grc20): add launchpadERC20 banner & description text

* feat(launchpad-grc20): transform launchpadButton into a generic LargeBoxButton & import it into ERC20 launchpad screen

* style(launchpad-grc20): new banner

* feat(launchpad-grc20): add empty tokens, airdrops & sales page

* ci(launchpad-grc20): run eslint --fix

* ci(launchpad-grc20): resolve @hthieu1110 suggestions & add new hook use screen size

* ci(launchpad-grc20): add network project launchpad erc20 in gno-dev & remove duplicate icon from original launchpad

* feat(launchpad-g/erc20): refactor flow card from TNS service & add 3 flow card to tokens page

* wip(launchpad-g/erc20): add recent created tokens table

* feat(launchpad-g/erc20): add gno function to return 10 last tokens as JSON string

* feat(launchpad-g/erc20): add hooks useLastTokens to retrieve last tokens created from contract

* feat(launchpad-g/erc20): retrieve last tokens from chain & display it

* feat(launchpad-g/erc20): first step of the token creation form

* fix(launchpad-g/erc20): fix various bugs from token creation, add details page with toggle buttons

* feat(launchpad-g/erc20): add token creation tx & redirect after creation

* feat(launchpad-g/erc20): add function get last airdrops & sales in json from conctract

* feat(launchpad-g/erc20): add airdrops & sales pages

* feat(launchpad-g/erc20): add airdrops & sales creation flow

* fix(launchpad-g/erc20): fix unused exports

* fix(launchpad-g/erc20): use tertiary box instead of box

* fix(launchpad-g/erc20): use well-known breakpoints in launchpad banner

* fix(launchpad-g/erc20): use camelCase for pretty functions

* fix(launchpad-g/erc20): move all zod schema to form.ts in utils folder

* Update packages/utils/navigation.ts

Co-authored-by: WaDadidou <[email protected]>

* Update packages/utils/navigation.ts

Co-authored-by: WaDadidou <[email protected]>

* Update packages/utils/navigation.ts

Co-authored-by: WaDadidou <[email protected]>

* fix(launchpad-g/erc20): use control & rules w/ react hook form

* Update packages/screens/LaunchpadERC20/LaunchpadERC20Sales/LaunchpadERC20SalesScreen.tsx

Co-authored-by: WaDadidou <[email protected]>

* Update packages/screens/LaunchpadERC20/LaunchpadERC20Tokens/LaunchpadERC20TokensScreen.tsx

Co-authored-by: WaDadidou <[email protected]>

* Update packages/screens/LaunchpadERC20/LaunchpadERCAirdrops/LaunchpadERC20AirdropsScreen.tsx

Co-authored-by: WaDadidou <[email protected]>

* chore(launchpad-g/erc20): rename create token/airdrop/sale screens files

* fix(launchpad-g/erc20): use onBackPress property from screenContainer for back button in headers

* fix(launchpad-g/erc20): add devOnly to launchpad

* fix(launchpad-g/erc20): add datetime picker

* feat(launchpad-g/erc20): compute merkle root from csv files

* feat(launchpad-g/erc20): compute merkle root from csv files

* Update packages/screens/LaunchpadERC20/LaunchpadERCAirdrops/LaunchpadERC20CreateAirdropForm.tsx

Co-authored-by: WaDadidou <[email protected]>

* fix(launchpad-g/erc20): add label & sublabel for csv input & set the caller value onSubmit

* fix(launchpad-g/erc20): generate networks.json

---------

Co-authored-by: WaDadidou <[email protected]>
  • Loading branch information
MikaelVallenet and WaDadidou authored Sep 25, 2024
1 parent bdfc4a8 commit 9201482
Show file tree
Hide file tree
Showing 56 changed files with 3,683 additions and 98 deletions.
Binary file added assets/banners/launchpadERC20.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 38 additions & 8 deletions gno/r/launchpad_grc20/airdrop_grc20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ package launchpad_grc20
import (
"encoding/hex"
"std"
"strconv"
"time"

"gno.land/p/demo/avl"
"gno.land/p/demo/json"
"gno.land/p/demo/merkle"
"gno.land/p/demo/seqid"
"gno.land/p/demo/ufmt"
"gno.land/p/teritori/jsonutil"
)

type Airdrop struct {
token *Token
id seqid.ID
merkleRoot string
startTimestamp int64
endTimestamp int64
Expand Down Expand Up @@ -52,16 +55,18 @@ func NewAirdrop(tokenName, merkleRoot string, amountPerAddr uint64, startTimesta
panic("invalid timestamps, start must be before end")
}

airdropID := nextAirdropID.Next()

airdrop := Airdrop{
token: token,
id: airdropID,
merkleRoot: merkleRoot,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp,
amountPerAddr: amountPerAddr,
alreadyClaimed: avl.NewTree(),
}

airdropID := nextAirdropID.Next()
token.AirdropsIDs = append(token.AirdropsIDs, airdropID)

airdrops.Set(airdropID.String(), &airdrop)
Expand Down Expand Up @@ -110,6 +115,27 @@ func Claim(airdropID uint64, proofs []merkle.Node) {
airdrop.alreadyClaimed.Set(caller.String(), true)
}

func (a *Airdrop) hasAlreadyClaimed(caller std.Address) bool {
return a.alreadyClaimed.Has(caller.String())
}

func (a *Airdrop) isOnGoing() bool {
now := time.Now().Unix()
return (a.startTimestamp == 0 || a.startTimestamp <= now) &&
(a.endTimestamp == 0 || now < a.endTimestamp)
}

func (a *Airdrop) ToJSON() *json.Node {
return json.ObjectNode("", map[string]*json.Node{
"id": json.StringNode("", ufmt.Sprintf("%d", uint64(a.id))),
"tokenName": json.StringNode("", a.token.banker.GetName()),
"tokenSymbol": json.StringNode("", a.token.banker.GetSymbol()),
"amountPerAddr": json.StringNode("", strconv.FormatUint(a.amountPerAddr, 10)),
"startTimestamp": json.StringNode("", strconv.FormatInt(a.startTimestamp, 10)),
"endTimestamp": json.StringNode("", strconv.FormatInt(a.endTimestamp, 10)),
})
}

func mustGetAirdrop(airdropID uint64) *Airdrop {
id := seqid.ID(airdropID)
airdropRaw, exists := airdrops.Get(id.String())
Expand All @@ -120,12 +146,16 @@ func mustGetAirdrop(airdropID uint64) *Airdrop {
return airdropRaw.(*Airdrop)
}

func (a *Airdrop) hasAlreadyClaimed(caller std.Address) bool {
return a.alreadyClaimed.Has(caller.String())
}
func GetLastAirdropsJSON() string {
nodes := make([]*json.Node, 0, 10)
for i := int(nextAirdropID); i > int(nextAirdropID)-10; i-- {
airdropRaw, exists := airdrops.Get(seqid.ID(i).String())
if !exists {
continue
}

func (a *Airdrop) isOnGoing() bool {
now := time.Now().Unix()
return (a.startTimestamp == 0 || a.startTimestamp <= now) &&
(a.endTimestamp == 0 || now < a.endTimestamp)
airdrop := airdropRaw.(*Airdrop)
nodes = append(nodes, airdrop.ToJSON())
}
return json.ArrayNode("", nodes).String()
}
7 changes: 3 additions & 4 deletions gno/r/launchpad_grc20/render.gno
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,15 @@ func renderTokenPage(res *mux.ResponseWriter, req *mux.Request) {
res.Write("## Usage\n")
res.Write("You can create your own token by referring to the help section in the documentation and using the NewToken function.\nAfter creating your token, you can manage it easily through the provided interface.\nTo view details of any token created by this factory, simply visit the page ``:token/{name}``, replacing ``{name}`` with the token's name.\n")

if len(lastTokensIDcreated) > 0 {
if len(lastTokensCreated) > 0 {
res.Write("## Last tokens created\n")

for _, tokenIDs := range lastTokensIDcreated {
token := mustGetToken(tokenIDs)
for _, token := range lastTokensCreated {
res.Write(ufmt.Sprintf("### Name: %s - Symbol: %s\n", token.banker.GetName(), token.banker.GetSymbol()))
res.Write(ufmt.Sprintf("#### Total Supply: %d %s\n", token.banker.TotalSupply(), token.banker.GetSymbol()))
res.Write(ufmt.Sprintf("#### Decimals: %d\n", token.banker.GetDecimals()))
res.Write(ufmt.Sprintf("#### Admin: %s\n\n", token.admin.Owner().String()))
res.Write(ufmt.Sprintf("> Link: [:token/%s](launchpad_grc20:token/%s)\n\n", tokenIDs, tokenIDs))
res.Write(ufmt.Sprintf("> Link: [:token/%s](launchpad_grc20:token/%s)\n\n", token.banker.GetName(), token.banker.GetName()))
}
}
renderFooter(res, "")
Expand Down
55 changes: 45 additions & 10 deletions gno/r/launchpad_grc20/sale_grc20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package launchpad_grc20
import (
"encoding/hex"
"std"
"strconv"
"time"

"gno.land/p/demo/avl"
Expand All @@ -15,6 +16,7 @@ import (

type Sale struct {
token *Token
id seqid.ID
startTimestamp int64
endTimestamp int64
pricePerToken uint64
Expand Down Expand Up @@ -66,6 +68,10 @@ func NewSale(tokenName, merkleRoot string, startTimestamp, endTimestamp int64, p
panic("price per token must be greater than 0")
}

if limitPerAddr == 0 {
panic("limit per address must be greater than 0")
}

realmAddr := std.CurrentRealm().Addr()
if mintToken {
token.banker.Mint(realmAddr, maxGoal)
Expand All @@ -76,8 +82,11 @@ func NewSale(tokenName, merkleRoot string, startTimestamp, endTimestamp int64, p
}
}

saleID := nextSaleID.Next()

sale := Sale{
token: token,
id: saleID,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp,
pricePerToken: pricePerToken,
Expand All @@ -90,7 +99,6 @@ func NewSale(tokenName, merkleRoot string, startTimestamp, endTimestamp int64, p
merkleRoot: merkleRoot,
}

saleID := nextSaleID.Next()
token.SalesIDs = append(token.SalesIDs, saleID)

sales.Set(saleID.String(), &sale)
Expand Down Expand Up @@ -157,15 +165,6 @@ func Finalize(saleID uint64) {
sale.finalized = true
}

func mustGetSale(saleID uint64) *Sale {
id := seqid.ID(saleID)
sale, exists := sales.Get(id.String())
if !exists {
panic("sale not found")
}
return sale.(*Sale)
}

func (s *Sale) isOnGoing() bool {
return s.startTimestamp <= time.Now().Unix() && (s.endTimestamp == 0 || time.Now().Unix() < s.endTimestamp)
}
Expand Down Expand Up @@ -267,3 +266,39 @@ func (s *Sale) payAllBuyers() {
return false
})
}

func (s *Sale) ToJSON() *json.Node {
return json.ObjectNode("", map[string]*json.Node{
"id": json.StringNode("", ufmt.Sprintf("%d", uint64(s.id))),
"tokenName": json.StringNode("", s.token.banker.GetName()),
"pricePerToken": json.StringNode("", strconv.FormatUint(s.pricePerToken, 10)),
"limitPerAddr": json.StringNode("", strconv.FormatUint(s.limitPerAddr, 10)),
"minGoal": json.StringNode("", strconv.FormatUint(s.minGoal, 10)),
"maxGoal": json.StringNode("", strconv.FormatUint(s.maxGoal, 10)),
"startTimestamp": json.StringNode("", strconv.FormatInt(s.startTimestamp, 10)),
"endTimestamp": json.StringNode("", strconv.FormatInt(s.endTimestamp, 10)),
})
}

func mustGetSale(saleID uint64) *Sale {
id := seqid.ID(saleID)
sale, exists := sales.Get(id.String())
if !exists {
panic("sale not found")
}
return sale.(*Sale)
}

func GetLastSalesJSON() string {
nodes := make([]*json.Node, 0, 10)
for i := int(nextSaleID); i > int(nextSaleID)-10; i-- {
saleRaw, exists := sales.Get(seqid.ID(i).String())
if !exists {
continue
}

sale := saleRaw.(*Sale)
nodes = append(nodes, sale.ToJSON())
}
return json.ArrayNode("", nodes).String()
}
41 changes: 33 additions & 8 deletions gno/r/launchpad_grc20/token_factory_grc20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package launchpad_grc20

import (
"std"
"strconv"

"gno.land/p/demo/avl"
"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/json"
"gno.land/p/demo/ownable"
"gno.land/p/demo/seqid"
)

const LENGTH_LAST_TOKENS_CACHE = 10

type Token struct {
banker *grc20.Banker
admin *ownable.Ownable
Expand All @@ -22,12 +26,11 @@ type Token struct {

var _ grc20.Token = (*Token)(nil)

var lastTokensIDcreated = []string{}

var (
tokens *avl.Tree // name -> token
factoryAdmin *ownable.Ownable
factoryVault std.Address
tokens *avl.Tree // name -> token
factoryAdmin *ownable.Ownable
factoryVault std.Address
lastTokensCreated = []*Token{}
)

// Initialize tori address as admin & vault for fees
Expand Down Expand Up @@ -75,10 +78,10 @@ func NewToken(name, symbol, image string, decimals uint, initialSupply, totalSup

tokens.Set(name, &inst)

if len(lastTokensIDcreated) == 5 {
lastTokensIDcreated = lastTokensIDcreated[:4]
if len(lastTokensCreated) == LENGTH_LAST_TOKENS_CACHE {
lastTokensCreated = lastTokensCreated[:(LENGTH_LAST_TOKENS_CACHE - 1)]
}
lastTokensIDcreated = append([]string{name}, lastTokensIDcreated...)
lastTokensCreated = append([]*Token{&inst}, lastTokensCreated...)
}

func Mint(name string, to std.Address, amount uint64) {
Expand Down Expand Up @@ -160,11 +163,33 @@ func (token Token) TransferFrom(from, to std.Address, amount uint64) error {
return token.Token().TransferFrom(from, to, amount)
}

func (token Token) ToJSON() *json.Node {
return json.ObjectNode("", map[string]*json.Node{
"name": json.StringNode("", token.banker.GetName()),
"symbol": json.StringNode("", token.banker.GetSymbol()),
"decimals": json.StringNode("", strconv.FormatUint(uint64(token.banker.GetDecimals()), 10)),
"admin": json.StringNode("", token.admin.Owner().String()),
"image": json.StringNode("", token.image),
"totalSupply": json.StringNode("", strconv.FormatInt(int64(token.TotalSupply()), 10)),
"totalSupplyCap": json.StringNode("", strconv.FormatInt(int64(token.totalSupplyCap), 10)),
"allowMint": json.BoolNode("", token.allowMint),
"allowBurn": json.BoolNode("", token.allowBurn),
})
}

func SetFactoryVault(vault std.Address) {
factoryAdmin.AssertCallerIsOwner()
factoryVault = vault
}

func GetLastTokensJSON() string {
nodes := make([]*json.Node, len(lastTokensCreated))
for i, token := range lastTokensCreated {
nodes[i] = token.ToJSON()
}
return json.ArrayNode("", nodes).String()
}

func mustGetToken(name string) *Token {
t, exists := tokens.Get(name)
if !exists {
Expand Down
27 changes: 27 additions & 0 deletions go/pkg/networks/features.gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
FeatureTypeNFTBridge = FeatureType("NFTBridge")
FeatureTypeCosmWasmPremiumFeed = FeatureType("CosmWasmPremiumFeed")
FeatureTypeGnoProjectManager = FeatureType("GnoProjectManager")
FeatureTypeLaunchpadERC20 = FeatureType("LaunchpadERC20")
FeatureTypeNFTMarketplaceLeaderboard = FeatureType("NFTMarketplaceLeaderboard")
FeatureTypeCosmWasmNFTsBurner = FeatureType("CosmWasmNFTsBurner")
)
Expand Down Expand Up @@ -82,6 +83,26 @@ func (nb *NetworkBase) GetFeatureGnoProjectManager() (*FeatureGnoProjectManager,
return feature.(*FeatureGnoProjectManager), nil
}

type FeatureLaunchpadERC20 struct {
*FeatureBase
LaunchpadERC20PkgPath string `json:"launchpadERC20PkgPath"`
PaymentsDenom string `json:"paymentsDenom"`
}

var _ Feature = &FeatureLaunchpadERC20{}

func (f FeatureLaunchpadERC20) Type() FeatureType {
return FeatureTypeLaunchpadERC20
}

func (nb *NetworkBase) GetFeatureLaunchpadERC20() (*FeatureLaunchpadERC20, error) {
feature, err := nb.GetFeature(FeatureTypeLaunchpadERC20)
if err != nil {
return nil, err
}
return feature.(*FeatureLaunchpadERC20), nil
}

func UnmarshalFeature(b []byte) (Feature, error) {
var base FeatureBase
if err := json.Unmarshal(b, &base); err != nil {
Expand All @@ -106,6 +127,12 @@ func UnmarshalFeature(b []byte) (Feature, error) {
return nil, errors.Wrap(err, "failed to unmarshal feature GnoProjectManager")
}
return &f, nil
case FeatureTypeLaunchpadERC20:
var f FeatureLaunchpadERC20
if err := json.Unmarshal(b, &f); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal feature LaunchpadERC20")
}
return &f, nil
}
return nil, errors.Errorf("unknown feature type %s", base.Type)
}
8 changes: 7 additions & 1 deletion networks.json
Original file line number Diff line number Diff line change
Expand Up @@ -4467,13 +4467,19 @@
"Organizations",
"SocialFeed",
"UPP",
"GnoProjectManager"
"GnoProjectManager",
"LaunchpadERC20"
],
"featureObjects": [
{
"type": "GnoProjectManager",
"projectsManagerPkgPath": "gno.land/r/teritori/projects_manager",
"paymentsDenom": "ugnot"
},
{
"type": "LaunchpadERC20",
"launchpadERC20PkgPath": "gno.land/r/teritori/launchpad_grc20",
"paymentsDenom": "ugnot"
}
],
"currencies": [
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@reduxjs/toolkit": "^1.8.3",
"@solana/web3.js": "^1.87.6",
"@tanstack/react-query": "^4.12.0",
"@types/crypto-js": "^4.2.2",
"@types/leaflet": "^1.9.8",
"@types/papaparse": "^5.3.14",
"assert": "^2.1.0",
Expand All @@ -84,6 +85,7 @@
"commander": "^10.0.1",
"cosmos-endpoints-scorer": "^0.1.0",
"crypto-browserify": "^3.12.0",
"crypto-js": "^4.2.0",
"draft-convert": "^2.1.13",
"draft-js": "^0.11.7",
"electron-store": "^8.1.0",
Expand Down Expand Up @@ -115,6 +117,7 @@
"long": "^5.2.1",
"lottie-react-native": "6.5.1",
"markdown-it": "^14.1.0",
"merkletreejs": "^0.4.0",
"metamask-react": "^2.4.1",
"moment": "^2.29.4",
"multiformats": "^12.1.3",
Expand Down
Loading

0 comments on commit 9201482

Please sign in to comment.