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 {
|
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
|
@ -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 (
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue