feat(MintTo): Add Airdrop functionality.
Expose MintTo smart contract function. Expose ContractOwner address. Introduce token owners cache. Issue #9783
This commit is contained in:
parent
a6d33b9912
commit
8c85a62e10
|
@ -407,7 +407,7 @@ func (b *StatusNode) ensService() *ens.Service {
|
|||
|
||||
func (b *StatusNode) collectiblesService() *collectibles.Service {
|
||||
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
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
);
|
|
@ -2,10 +2,13 @@ package collectibles
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/contracts/collectibles"
|
||||
|
@ -15,11 +18,12 @@ import (
|
|||
"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{
|
||||
RPCClient: rpcClient,
|
||||
accountsManager: accountsManager,
|
||||
config: config,
|
||||
db: NewCollectiblesDatabase(appDb),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +31,7 @@ type API struct {
|
|||
RPCClient *rpc.Client
|
||||
accountsManager *account.GethManager
|
||||
config *params.NodeConfig
|
||||
db *Database
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package collectibles
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/account"
|
||||
|
@ -14,9 +16,9 @@ type Service struct {
|
|||
}
|
||||
|
||||
// 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{
|
||||
NewAPI(rpcClient, accountsManager, config),
|
||||
NewAPI(rpcClient, accountsManager, config, appDb),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue