feat(MintTo): Add Airdrop functionality.

Expose MintTo smart contract function.
Expose ContractOwner address.
Introduce token owners cache.

Issue #9783
This commit is contained in:
Michal Iskierko 2023-03-10 15:09:34 +01:00 committed by Michał Iskierko
parent a6d33b9912
commit 8c85a62e10
6 changed files with 427 additions and 330 deletions

View File

@ -407,7 +407,7 @@ func (b *StatusNode) ensService() *ens.Service {
func (b *StatusNode) collectiblesService() *collectibles.Service { func (b *StatusNode) collectiblesService() *collectibles.Service {
if b.collectiblesSrvc == nil { if b.collectiblesSrvc == nil {
b.collectiblesSrvc = collectibles.NewService(b.rpcClient, b.gethAccountManager, b.config) b.collectiblesSrvc = collectibles.NewService(b.rpcClient, b.gethAccountManager, b.config, b.appDB)
} }
return b.collectiblesSrvc return b.collectiblesSrvc
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS community_token_owners (
chain_id INT NOT NULL,
address TEXT NOT NULL,
owner TEXT NOT NULL COLLATE NOCASE,
amount INT NOT NULL,
PRIMARY KEY(chain_id, address, owner)
);

View File

@ -2,10 +2,13 @@ package collectibles
import ( import (
"context" "context"
"database/sql"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts/collectibles" "github.com/status-im/status-go/contracts/collectibles"
@ -15,11 +18,12 @@ import (
"github.com/status-im/status-go/transactions" "github.com/status-im/status-go/transactions"
) )
func NewAPI(rpcClient *rpc.Client, accountsManager *account.GethManager, config *params.NodeConfig) *API { func NewAPI(rpcClient *rpc.Client, accountsManager *account.GethManager, config *params.NodeConfig, appDb *sql.DB) *API {
return &API{ return &API{
RPCClient: rpcClient, RPCClient: rpcClient,
accountsManager: accountsManager, accountsManager: accountsManager,
config: config, config: config,
db: NewCollectiblesDatabase(appDb),
} }
} }
@ -27,6 +31,7 @@ type API struct {
RPCClient *rpc.Client RPCClient *rpc.Client
accountsManager *account.GethManager accountsManager *account.GethManager
config *params.NodeConfig config *params.NodeConfig
db *Database
} }
type DeploymentDetails struct { type DeploymentDetails struct {
@ -99,3 +104,64 @@ func (api *API) Deploy(ctx context.Context, chainID uint64, deploymentParameters
return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil
} }
func (api *API) newCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
backend, err := api.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return collectibles.NewCollectibles(common.HexToAddress(contractAddress), backend)
}
func (api *API) multiplyWalletAddresses(amount int, contractAddresses []string) []string {
var totalAddresses []string
for i := 1; i <= amount; i++ {
totalAddresses = append(totalAddresses, contractAddresses...)
}
return totalAddresses
}
func (api *API) MintTo(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, users []string, amount int) (string, error) {
if len(users) == 0 {
return "", errors.New("users list is empty")
}
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
if err != nil {
return "", err
}
// if we want to mint 2 tokens to addresses ["a", "b"] we need to mint
// twice to every address - we need to send to smart contract table ["a", "a", "b", "b"]
totalAddresses := api.multiplyWalletAddresses(amount, users)
var usersAddresses = []common.Address{}
for _, k := range totalAddresses {
usersAddresses = append(usersAddresses, common.HexToAddress(k))
}
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
tx, err := contractInst.MintTo(transactOpts, usersAddresses)
if err != nil {
return "", err
}
//save to db
_ = api.db.AddTokenOwners(chainID, contractAddress, totalAddresses)
return tx.Hash().Hex(), nil
}
func (api *API) ContractOwner(ctx context.Context, chainID uint64, contractAddress string) (string, error) {
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
contractInst, err := api.newCollectiblesInstance(chainID, contractAddress)
if err != nil {
return "", err
}
owner, err := contractInst.Owner(callOpts)
if err != nil {
return "", err
}
return owner.String(), nil
}

View File

@ -0,0 +1,77 @@
package collectibles
import (
"database/sql"
"strings"
)
type Database struct {
db *sql.DB
}
type TokenOwner struct {
Address string
Amount int
}
func NewCollectiblesDatabase(db *sql.DB) *Database {
return &Database{db: db}
}
func (db *Database) GetAmount(chainID uint64, contractAddress string, owner string) (int, error) {
const selectQuery = `SELECT amount FROM community_token_owners WHERE chain_id=? AND address=? AND owner=? LIMIT 1`
rows, err := db.db.Query(selectQuery, chainID, contractAddress, owner)
if err != nil {
return -1, err
}
defer rows.Close()
if rows.Next() {
var amount int
err := rows.Scan(&amount)
if err != nil {
return -1, err
}
return amount, nil
}
return 0, nil
}
func (db *Database) setAmount(chainID uint64, contractAddress string, owner string, amount int) error {
const sqlQuery = `INSERT OR REPLACE INTO community_token_owners(chain_id, address, owner, amount) VALUES (?, ?, ?, ?)`
_, err := db.db.Exec(sqlQuery, chainID, contractAddress, owner, amount)
return err
}
func (db *Database) AddTokenOwners(chainID uint64, contractAddress string, owners []string) error {
for _, v := range owners {
lowerVal := strings.ToLower(v)
amount, err := db.GetAmount(chainID, contractAddress, lowerVal)
if err != nil {
return err
}
err = db.setAmount(chainID, contractAddress, lowerVal, amount+1)
if err != nil {
return err
}
}
return nil
}
func (db *Database) GetTokenOwners(chainID uint64, contractAddress string) ([]TokenOwner, error) {
const selectQuery = `SELECT owner, amount FROM community_token_owners WHERE chain_id=? AND address=?`
rows, err := db.db.Query(selectQuery, chainID, contractAddress)
if err != nil {
return nil, err
}
defer rows.Close()
var owners []TokenOwner
for rows.Next() {
var owner TokenOwner
err := rows.Scan(&owner.Address, &owner.Amount)
if err != nil {
return nil, err
}
owners = append(owners, owner)
}
return owners, nil
}

View File

@ -1,6 +1,8 @@
package collectibles package collectibles
import ( import (
"database/sql"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
ethRpc "github.com/ethereum/go-ethereum/rpc" ethRpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
@ -14,9 +16,9 @@ type Service struct {
} }
// Returns a new Collectibles Service. // Returns a new Collectibles Service.
func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, config *params.NodeConfig) *Service { func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, config *params.NodeConfig, appDb *sql.DB) *Service {
return &Service{ return &Service{
NewAPI(rpcClient, accountsManager, config), NewAPI(rpcClient, accountsManager, config, appDb),
} }
} }