Skip to content

Commit

Permalink
feat: referral system (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
kehiy authored Dec 7, 2023
1 parent 1d6176c commit ff35cb8
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 11 deletions.
9 changes: 8 additions & 1 deletion cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ func main() {
return
}

// load list of validators already received faucet
rs, err := discord.LoadReferralData(cfg)
if err != nil {
log.Println(err)
return
}

///start discord bot
bot, err := discord.Start(cfg, w, ss)
bot, err := discord.Start(cfg, w, ss, rs)
if err != nil {
log.Printf("could not start discord bot: %v\n", err)
return
Expand Down
16 changes: 9 additions & 7 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import (
)

type Config struct {
DiscordToken string `json:"discord_token"`
WalletPath string `json:"wallet_path"`
WalletPassword string `json:"wallet_password"`
Servers []string `json:"servers"`
FaucetAddress string `json:"faucet_address"`
FaucetAmount float64 `json:"faucet_amount"`
ValidatorDataPath string `json:"validator_data_path"`
DiscordToken string `json:"discord_token"`
WalletPath string `json:"wallet_path"`
WalletPassword string `json:"wallet_password"`
Servers []string `json:"servers"`
FaucetAddress string `json:"faucet_address"`
FaucetAmount float64 `json:"faucet_amount"`
ReferralerStakeAmount float64 `json:"referraler_stake_amount"` // who get faucet
ValidatorDataPath string `json:"validator_data_path"`
ReferralDataPath string `json:"referral_data_path"`
}

func Load(path string) (*Config, error) {
Expand Down
100 changes: 98 additions & 2 deletions discord/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/kehiy/RoboPac/config"
"github.com/kehiy/RoboPac/wallet"
"github.com/libp2p/go-libp2p/core/peer"
gonanoid "github.com/matoous/go-nanoid/v2"
"github.com/pactus-project/pactus/crypto"
"github.com/pactus-project/pactus/util"
pactus "github.com/pactus-project/pactus/www/grpc/gen/go"
Expand All @@ -23,13 +24,14 @@ type Bot struct {
faucetWallet *wallet.Wallet
cfg *config.Config
store *SafeStore
referralStore *ReferralStore

cm *client.Mgr
}

// guildID: "795592769300987944"

func Start(cfg *config.Config, w *wallet.Wallet, ss *SafeStore) (*Bot, error) {
func Start(cfg *config.Config, w *wallet.Wallet, ss *SafeStore, rs *ReferralStore) (*Bot, error) {
cm := client.NewClientMgr()

for _, s := range cfg.Servers {
Expand Down Expand Up @@ -171,6 +173,97 @@ func (b *Bot) messageHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
return
}

if strings.ToLower(m.Content) == "my-referral" {
referrals := b.referralStore.GetAllReferrals()
for _, r := range referrals {
if r.DiscordID == m.Author.ID {
msg := fmt.Sprintf("Your referral data:\nPoints: %v\nCode: ```%v```\n", r.Points, r.ReferralCode)
_, _ = s.ChannelMessageSendReply(m.ChannelID, msg, m.Reference())
return
}
}

referralCode, err := gonanoid.New(10)
if err != nil {
msg := "can't generate referral code, please try again later."
_, _ = s.ChannelMessageSendReply(m.ChannelID, msg, m.Reference())
return
}

err = b.referralStore.NewReferral(m.Author.ID, m.Author.Username, referralCode)
if err != nil {
msg := "can't generate referral code, please try again later."
_, _ = s.ChannelMessageSendReply(m.ChannelID, msg, m.Reference())
return
}

msg := fmt.Sprintf("Your referral data:\nPoints: %v\nCode: ```%v```\n", 0, referralCode)
_, _ = s.ChannelMessageSendReply(m.ChannelID, msg, m.Reference())
return
}

if strings.Contains(strings.ToLower(m.Content), "faucet-referral") {
trimmedPrefix := strings.TrimPrefix(strings.ToLower(m.Content), "faucet-referral")

Params := strings.Split(trimmedPrefix, " ")
if len(Params) != 2 {
msg := p.Sprintf("*Invalid* parameters!")
_, _ = s.ChannelMessageSendReply(m.ChannelID, msg, m.Reference())
return
}

address := strings.Trim(Params[0], " ")
referralCode := strings.Trim(Params[1], " ")

peerID, pubKey, isValid, msg := b.validateInfo(address, m.Author.ID)

msg = fmt.Sprintf("%v\ndiscord: %v\naddress: %v",
msg, m.Author.Username, address)

if !isValid {
_, _ = s.ChannelMessageSendReply(m.ChannelID, msg, m.Reference())
return
}

// validate referral.
_, found := b.referralStore.GetData(referralCode)
if !found {
msg := p.Sprintf("*Invalid* referral!")
_, _ = s.ChannelMessageSendReply(m.ChannelID, msg, m.Reference())
return
}

if pubKey != "" {
// check available balance
balance := b.faucetWallet.GetBalance()
if balance.Available < b.cfg.FaucetAmount {
_, _ = s.ChannelMessageSendReply(m.ChannelID, "Insufficient faucet balance. Try again later.", m.Reference())
return
}

amount := b.cfg.ReferralerStakeAmount
ok := b.referralStore.AddPoint(referralCode)
if !ok {
_, _ = s.ChannelMessageSendReply(m.ChannelID, "Can't update referral data. please try again later.", m.Reference())
return
}

// send faucet
txHashFaucet := b.faucetWallet.BondTransaction(pubKey, address, amount)

if txHashFaucet != "" {
err := b.store.SetData(peerID, address, m.Author.Username, m.Author.ID, amount)
if err != nil {
log.Printf("error saving faucet information: %v\n", err)
}

msg := p.Sprintf("%v %.4f test PACs is staked to %v successfully!",
m.Author.Username, amount, address)
_, _ = s.ChannelMessageSendReply(m.ChannelID, msg, m.Reference())
}
}
}

if strings.Contains(strings.ToLower(m.Content), "faucet") {
trimmedPrefix := strings.TrimPrefix(strings.ToLower(m.Content), "faucet")
// faucet message must contain address/public-key
Expand Down Expand Up @@ -240,8 +333,11 @@ func help(s *discordgo.Session, m *discordgo.MessageCreate) {
"To see the faucet account balance, simply type: `balance`\n" +
"To see the faucet address, simply type: `address`\n" +
"To get network information, simply type: `network`\n" +
"To get network health status, simply type: `health`\n" +
"To get peer information, simply type: `peer-info [validator address]`\n" +
"To request faucet for test network: simply post `faucet [validator address]`.",
"To get your referral information, simply type: `my-referral`\n" +
"To request faucet for test network *with referral code*: simply type `faucet-referral [validator address] [referral code]`\n" +
"To request faucet for test network: simply type `faucet [validator address]`.",
Fields: []*discordgo.MessageEmbedField{
{
Name: "Example of requesting `faucet` ",
Expand Down
125 changes: 125 additions & 0 deletions discord/referral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package discord

import (
"fmt"
"log"
"os"
"sync"

"github.com/kehiy/RoboPac/config"
)

type Referral struct {
ReferralCode string `json:"referral_code"`
Points int `json:"points"`
DiscordName string `json:"discord_name"`
DiscordID string `json:"discord_id"`
}

// SafeStore is a thread-safe cache.
type ReferralStore struct {
syncMap *sync.Map
cfg *config.Config
}

func LoadReferralData(cfg *config.Config) (*ReferralStore, error) {
file, err := os.ReadFile(cfg.ReferralDataPath)
if err != nil {
log.Printf("error loading validator data: %v", err)
return nil, fmt.Errorf("error loading data file: %w", err)
}
if len(file) == 0 {
rs := &ReferralStore{
syncMap: &sync.Map{},
cfg: cfg,
}
return rs, nil
}

data, err := unmarshalJSON(file)
if err != nil {
log.Printf("error unmarshalling validator data: %v", err)
return nil, fmt.Errorf("error unmarshalling validator data: %w", err)
}
rs := &ReferralStore{
syncMap: data,
cfg: cfg,
}
return rs, nil
}

// SetData Set a given value to the data storage.
func (rs *ReferralStore) NewReferral(discordId, discordName, referralCode string) error {
rs.syncMap.Store(referralCode, &Referral{
Points: 0,
DiscordName: discordName,
ReferralCode: referralCode,
DiscordID: discordId,
})
// save record
data, err := marshalJSON(rs.syncMap)
if err != nil {
log.Printf("error marshalling validator data file: %v", err)
return fmt.Errorf("error marshalling validator data file: %w", err)
}
if err := os.WriteFile(rs.cfg.ReferralDataPath, data, 0o600); err != nil {
log.Printf("failed to write to %s: %v", rs.cfg.ReferralDataPath, err)
return fmt.Errorf("failed to write to %s: %w", rs.cfg.ReferralDataPath, err)
}
return nil
}

// GetData retrieves the given key from the storage.
func (rs *ReferralStore) GetData(code string) (*Referral, bool) {
entry, found := rs.syncMap.Load(code)
if !found {
return nil, false
}
referral := entry.(*Referral)
return referral, true
}

// GetAllReferrals retrieves all referrals in store.
func (rs *ReferralStore) GetAllReferrals() []*Referral {
result := []*Referral{}

rs.syncMap.Range(func(key, value any) bool {
referral, ok := value.(*Referral)
if !ok {
return true
}
result = append(result, referral)
return true
})

return result
}

// AddPoint add one point for a referral.
func (rs *ReferralStore) AddPoint(code string) bool {
entry, found := rs.syncMap.Load(code)
if !found {
return false
}

if found {
referral := entry.(*Referral)
referral.Points++
rs.syncMap.Store(referral.ReferralCode, referral)
return true
}

// save record
data, err := marshalJSON(rs.syncMap)
if err != nil {
log.Printf("error marshalling validator data file: %v", err)
return false
}

if err := os.WriteFile(rs.cfg.ReferralDataPath, data, 0o600); err != nil {
log.Printf("failed to write to %s: %v", rs.cfg.ReferralDataPath, err)
return false
}

return false
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/bwmarrin/discordgo v0.27.1
github.com/k0kubun/pp v3.0.1+incompatible
github.com/libp2p/go-libp2p v0.31.0
github.com/matoous/go-nanoid/v2 v2.0.0
github.com/pactus-project/pactus v0.17.0
github.com/yudai/pp v2.0.1+incompatible
golang.org/x/text v0.13.0
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
Expand Down Expand Up @@ -37,6 +38,9 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-libp2p v0.31.0 h1:LFShhP8F6xthWiBBq3euxbKjZsoRajVEyBS9snfHxYg=
github.com/libp2p/go-libp2p v0.31.0/go.mod h1:W/FEK1c/t04PbRH3fA9i5oucu5YcgrG0JVoBWT1B7Eg=
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand Down Expand Up @@ -66,6 +70,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
Expand Down Expand Up @@ -113,6 +119,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
Expand Down
2 changes: 1 addition & 1 deletion wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func Open(cfg *config.Config) *Wallet {
func (w *Wallet) BondTransaction(pubKey, toAddress string, amount float64) string {
opts := []pwallet.TxOption{
pwallet.OptionFee(util.CoinToChange(0)),
pwallet.OptionMemo("faucet from PactusBot"),
pwallet.OptionMemo("Faucet from PactusBot"),
}
tx, err := w.wallet.MakeBondTx(w.address, toAddress, pubKey,
util.CoinToChange(amount), opts...)
Expand Down

0 comments on commit ff35cb8

Please sign in to comment.