Skip to content

Commit

Permalink
Merge pull request #75 from eqlabs/latenssi/token-handling
Browse files Browse the repository at this point in the history
Refactor token handling
  • Loading branch information
latenssi authored Jun 17, 2021
2 parents c8c5f9c + 171e70c commit 1838e47
Show file tree
Hide file tree
Showing 31 changed files with 443 additions and 344 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ ADMIN_PRIVATE_KEY=be44c2dfdf72fdcc8da80d19dbedb0257042de28ceeea1836599a90d2c19a0
CHAIN_ID=flow-emulator
DEFAULT_KEY_TYPE=local
ENCRYPTION_KEY=

ENABLED_TOKENS=FlowToken:0x0ae53cb6e3f42a79,FUSD:0xf8d6e0586b0a20c7
50 changes: 23 additions & 27 deletions accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,57 +30,55 @@ type Account struct {

type AccountToken struct {
ID int `json:"-" gorm:"primaryKey"`
AccountAddress string `json:"-" gorm:"index"`
Name string `json:"name"`
AccountAddress string `json:"-" gorm:"uniqueIndex:addressname;index;not null"`
TokenAddress string `json:"-" gorm:"uniqueIndex:addressname;index;not null"`
TokenName string `json:"name" gorm:"uniqueIndex:addressname;index;not null"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}

// TODO: Add AccountTokens to admin account on startup (FlowToken for now)

// New creates a new account on the Flow blockchain.
// It uses the provided admin account to pay for the creation.
// It generates a new privatekey and returns it (local key)
// or a reference to it (Google KMS resource id).
func New(
a *Account,
k *keys.Private,
ctx context.Context,
fc *client.Client,
km keys.Manager,
) (
newAccount Account,
newPrivateKey keys.Private,
err error,
) {
) error {
// Get admin account authorizer
auth, err := km.AdminAuthorizer(ctx)
if err != nil {
return
return err
}

// Get latest blocks id as reference id
id, err := flow_helpers.LatestBlockId(ctx, fc)
if err != nil {
return
return err
}

// Generate a new key pair
accountKey, newPrivateKey, err := km.GenerateDefault(ctx)
if err != nil {
return
return err
}

*k = *newPrivateKey

tx := flow_templates.CreateAccount([]*flow.AccountKey{accountKey}, nil, auth.Address)
b := templates.NewBuilderFromTx(tx)

t, err := transactions.New(id, b, transactions.General, auth, auth, nil)
if err != nil {
return
t := transactions.Transaction{}
if err := transactions.New(&t, id, b, transactions.General, auth, auth, nil); err != nil {
return err
}

err = t.SendAndWait(ctx, fc)
if err != nil {
return
if err := t.SendAndWait(ctx, fc); err != nil {
return err
}

// Grab the new address from transaction events
Expand All @@ -95,13 +93,12 @@ func New(

// Check that we actually got a new address
if newAddress == flow.EmptyAddress {
err = fmt.Errorf("something went wrong when waiting for address")
return
return fmt.Errorf("something went wrong when waiting for address")
}

newAccount.Address = flow_helpers.FormatAddress(newAddress)
a.Address = flow_helpers.FormatAddress(newAddress)

return
return nil
}

// AddContract is mainly used for testing purposes
Expand Down Expand Up @@ -147,15 +144,14 @@ func AddContract(

b.Tx.AddAuthorizer(adminAuth.Address)

t, err := transactions.New(id, b, transactions.General, userAuth, adminAuth, nil)
if err != nil {
t := transactions.Transaction{}
if err := transactions.New(&t, id, b, transactions.General, userAuth, adminAuth, nil); err != nil {
return nil, err
}

err = t.SendAndWait(ctx, fc)
if err != nil {
if err := t.SendAndWait(ctx, fc); err != nil {
return nil, err
}

return t, nil
return &t, nil
}
3 changes: 2 additions & 1 deletion accounts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import (

// Config struct for account service.
type Config struct {
ChainId flow.ChainID `env:"CHAIN_ID" envDefault:"flow-emulator"`
AdminAccountAddress string `env:"ADMIN_ADDRESS,required"`
ChainId flow.ChainID `env:"CHAIN_ID" envDefault:"flow-emulator"`
}

// ParseConfig parses environment variables to a valid Config.
Expand Down
89 changes: 61 additions & 28 deletions accounts/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"strings"

"github.com/eqlabs/flow-wallet-service/datastore"
"github.com/eqlabs/flow-wallet-service/errors"
Expand Down Expand Up @@ -37,6 +38,27 @@ func NewService(
return &Service{db, km, fc, wp, ts, cfg}
}

func (s *Service) InitAdminAccount() {
a, err := s.db.Account(s.cfg.AdminAccountAddress)
if err != nil {
if !strings.Contains(err.Error(), "record not found") {
panic(err)
}
// Admin account not in database
a = Account{Address: s.cfg.AdminAccountAddress}
s.db.InsertAccount(&a)
}

for _, t := range templates.EnabledTokens() {
at := AccountToken{
AccountAddress: a.Address,
TokenAddress: t.Address,
TokenName: t.CanonName(),
}
s.db.InsertAccountToken(&at) // Ignore errors
}
}

// List returns all accounts in the datastore.
func (s *Service) List(limit, offset int) (result []Account, err error) {
o := datastore.ParseListOptions(limit, offset)
Expand All @@ -48,41 +70,41 @@ func (s *Service) List(limit, offset int) (result []Account, err error) {
// and stores both in datastore.
// It returns a job, the new account and a possible error.
func (s *Service) Create(c context.Context, sync bool) (*jobs.Job, *Account, error) {
var account *Account
a := Account{}
k := keys.Private{}

job, err := s.wp.AddJob(func() (string, error) {
ctx := c
if !sync {
ctx = context.Background()
}

a, key, err := New(ctx, s.fc, s.km)
if err != nil {
if err := New(&a, &k, ctx, s.fc, s.km); err != nil {
return "", err
}

account = &a

// Convert the key to storable form (encrypt it)
accountKey, err := s.km.Save(key)
accountKey, err := s.km.Save(k)
if err != nil {
return "", err
}

// Store account and key
a.Keys = []keys.Storable{accountKey}
err = s.db.InsertAccount(&a)
if err != nil {
if err := s.db.InsertAccount(&a); err != nil {
return "", err
}

// Store an AccountToken named FlowToken for the account as it is automatically
// enabled on all accounts
// Intentionally ignore error
s.db.InsertAccountToken(&AccountToken{
AccountAddress: a.Address,
Name: "FlowToken",
})
t, err := templates.NewToken("FlowToken")
if err == nil { // If err != nil, FlowToken is not enabled for some reason
s.db.InsertAccountToken(&AccountToken{ // Ignore errors
AccountAddress: a.Address,
TokenAddress: t.Address,
TokenName: t.CanonName(),
})
}

return a.Address, nil
})
Expand All @@ -100,60 +122,71 @@ func (s *Service) Create(c context.Context, sync bool) (*jobs.Job, *Account, err

err = job.Wait(sync)

return job, account, err
return job, &a, err
}

// Details returns a specific account.
func (s *Service) Details(address string) (Account, error) {
// Check if the input is a valid address
err := flow_helpers.ValidateAddress(address, s.cfg.ChainId)
if err != nil {
if err := flow_helpers.ValidateAddress(address, s.cfg.ChainId); err != nil {
return Account{}, err
}
address = flow_helpers.HexString(address)

return s.db.Account(address)
}

func (s *Service) SetupFungibleToken(ctx context.Context, sync bool, token templates.Token, address string) (*jobs.Job, *transactions.Transaction, error) {
func (s *Service) SetupFungibleToken(ctx context.Context, sync bool, tokenName, address string) (*jobs.Job, *transactions.Transaction, error) {
// Check if the input is a valid address
err := flow_helpers.ValidateAddress(address, s.cfg.ChainId)
if err != nil {
if err := flow_helpers.ValidateAddress(address, s.cfg.ChainId); err != nil {
return nil, nil, err
}

address = flow_helpers.HexString(address)

token, err := templates.NewToken(tokenName)
if err != nil {
return nil, nil, err
}

raw := templates.Raw{
Code: templates.FungibleSetupCode(token, s.cfg.ChainId),
Code: templates.FungibleSetupCode(token),
}

job, tx, err := s.ts.Create(ctx, sync, address, raw, transactions.FtSetup)

// Handle adding token to account in database
go func() {
err := job.Wait(true)
if err != nil {
if err := job.Wait(true); err != nil && !strings.Contains(err.Error(), "vault exists") {
return
}
// Intentionally ignore error
s.db.InsertAccountToken(&AccountToken{

err = s.db.InsertAccountToken(&AccountToken{
AccountAddress: address,
Name: token.CanonName(),
TokenAddress: token.Address,
TokenName: token.CanonName(),
})

if err != nil && !strings.Contains(err.Error(), "duplicate key value") {
fmt.Printf("error while adding account token: %s\n", err)
}
}()

return job, tx, err
}

func (s *Service) AccountFungibleTokens(address string) ([]AccountToken, error) {
// Check if the input is a valid address
err := flow_helpers.ValidateAddress(address, s.cfg.ChainId)
if err != nil {
if err := flow_helpers.ValidateAddress(address, s.cfg.ChainId); err != nil {
return nil, err
}

address = flow_helpers.HexString(address)

return s.db.AccountTokens(address)
tt, err := s.db.AccountTokens(address)
if err != nil {
return []AccountToken{}, err
}

return tt, nil
}
3 changes: 1 addition & 2 deletions accounts/store_gorm.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ func (s *GormStore) InsertAccount(a *Account) error {
}

func (s *GormStore) AccountTokens(address string) (att []AccountToken, err error) {
// TODO: unique by name and token addres
err = s.db.
Where(&AccountToken{AccountAddress: address}).
Order("name asc").
Order("token_name asc").
Find(&att).Error
return
}
Expand Down
Loading

0 comments on commit 1838e47

Please sign in to comment.