github.com/status-im/status-go@v1.1.0/services/communitytokens/service.go (about) 1 package communitytokens 2 3 import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "fmt" 8 "math/big" 9 "strings" 10 11 "github.com/pkg/errors" 12 13 "github.com/ethereum/go-ethereum/accounts/abi/bind" 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethereum/go-ethereum/event" 16 "github.com/ethereum/go-ethereum/log" 17 "github.com/ethereum/go-ethereum/p2p" 18 ethRpc "github.com/ethereum/go-ethereum/rpc" 19 "github.com/status-im/status-go/account" 20 "github.com/status-im/status-go/contracts/community-tokens/mastertoken" 21 "github.com/status-im/status-go/contracts/community-tokens/ownertoken" 22 communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry" 23 "github.com/status-im/status-go/eth-node/crypto" 24 "github.com/status-im/status-go/eth-node/types" 25 "github.com/status-im/status-go/params" 26 "github.com/status-im/status-go/protocol" 27 "github.com/status-im/status-go/protocol/communities" 28 "github.com/status-im/status-go/protocol/communities/token" 29 "github.com/status-im/status-go/protocol/protobuf" 30 "github.com/status-im/status-go/rpc" 31 "github.com/status-im/status-go/services/communitytokens/communitytokensdatabase" 32 "github.com/status-im/status-go/services/utils" 33 "github.com/status-im/status-go/services/wallet/bigint" 34 wcommon "github.com/status-im/status-go/services/wallet/common" 35 "github.com/status-im/status-go/services/wallet/router/fees" 36 "github.com/status-im/status-go/services/wallet/walletevent" 37 "github.com/status-im/status-go/signal" 38 "github.com/status-im/status-go/transactions" 39 ) 40 41 // Collectibles service 42 type Service struct { 43 manager *Manager 44 accountsManager *account.GethManager 45 pendingTracker *transactions.PendingTxTracker 46 config *params.NodeConfig 47 db *communitytokensdatabase.Database 48 Messenger *protocol.Messenger 49 walletFeed *event.Feed 50 walletWatcher *walletevent.Watcher 51 transactor *transactions.Transactor 52 feeManager *fees.FeeManager 53 } 54 55 // Returns a new Collectibles Service. 56 func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, 57 config *params.NodeConfig, appDb *sql.DB, walletFeed *event.Feed, transactor *transactions.Transactor) *Service { 58 return &Service{ 59 manager: &Manager{rpcClient: rpcClient}, 60 accountsManager: accountsManager, 61 pendingTracker: pendingTracker, 62 config: config, 63 db: communitytokensdatabase.NewCommunityTokensDatabase(appDb), 64 walletFeed: walletFeed, 65 transactor: transactor, 66 feeManager: &fees.FeeManager{RPCClient: rpcClient}, 67 } 68 } 69 70 // Protocols returns a new protocols list. In this case, there are none. 71 func (s *Service) Protocols() []p2p.Protocol { 72 return []p2p.Protocol{} 73 } 74 75 // APIs returns a list of new APIs. 76 func (s *Service) APIs() []ethRpc.API { 77 return []ethRpc.API{ 78 { 79 Namespace: "communitytokens", 80 Version: "0.1.0", 81 Service: NewAPI(s), 82 Public: true, 83 }, 84 } 85 } 86 87 // Start is run when a service is started. 88 func (s *Service) Start() error { 89 90 s.walletWatcher = walletevent.NewWatcher(s.walletFeed, s.handleWalletEvent) 91 s.walletWatcher.Start() 92 93 return nil 94 } 95 96 func (s *Service) handleWalletEvent(event walletevent.Event) { 97 if event.Type == transactions.EventPendingTransactionStatusChanged { 98 var p transactions.StatusChangedPayload 99 err := json.Unmarshal([]byte(event.Message), &p) 100 if err != nil { 101 log.Error(errors.Wrap(err, fmt.Sprintf("can't parse transaction message %v\n", event.Message)).Error()) 102 return 103 } 104 if p.Status == transactions.Pending { 105 return 106 } 107 pendingTransaction, err := s.pendingTracker.GetPendingEntry(p.ChainID, p.Hash) 108 if err != nil { 109 log.Error(errors.Wrap(err, fmt.Sprintf("no pending transaction with hash %v on chain %v\n", p.Hash, p.ChainID)).Error()) 110 return 111 } 112 113 var communityToken, ownerToken, masterToken *token.CommunityToken = &token.CommunityToken{}, &token.CommunityToken{}, &token.CommunityToken{} 114 var tokenErr error 115 switch pendingTransaction.Type { 116 case transactions.DeployCommunityToken: 117 communityToken, tokenErr = s.handleDeployCommunityToken(p.Status, pendingTransaction) 118 case transactions.AirdropCommunityToken: 119 communityToken, tokenErr = s.handleAirdropCommunityToken(p.Status, pendingTransaction) 120 case transactions.RemoteDestructCollectible: 121 communityToken, tokenErr = s.handleRemoteDestructCollectible(p.Status, pendingTransaction) 122 case transactions.BurnCommunityToken: 123 communityToken, tokenErr = s.handleBurnCommunityToken(p.Status, pendingTransaction) 124 case transactions.DeployOwnerToken: 125 ownerToken, masterToken, tokenErr = s.handleDeployOwnerToken(p.Status, pendingTransaction) 126 case transactions.SetSignerPublicKey: 127 communityToken, tokenErr = s.handleSetSignerPubKey(p.Status, pendingTransaction) 128 default: 129 return 130 } 131 132 err = s.pendingTracker.Delete(context.Background(), p.ChainID, p.Hash) 133 if err != nil { 134 log.Error(errors.Wrap(err, fmt.Sprintf("can't delete pending transaction with hash %v on chain %v\n", p.Hash, p.ChainID)).Error()) 135 } 136 137 errorStr := "" 138 if tokenErr != nil { 139 errorStr = tokenErr.Error() 140 } 141 142 signal.SendCommunityTokenTransactionStatusSignal(string(pendingTransaction.Type), p.Status == transactions.Success, pendingTransaction.Hash, 143 communityToken, ownerToken, masterToken, errorStr) 144 } 145 } 146 147 func (s *Service) handleAirdropCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { 148 communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) 149 if communityToken == nil { 150 return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) 151 } else { 152 publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), 153 communityToken.Address, protobuf.CommunityTokenAction_AIRDROP) 154 if publishErr != nil { 155 log.Warn("can't publish airdrop action") 156 } 157 } 158 return communityToken, err 159 } 160 161 func (s *Service) handleRemoteDestructCollectible(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { 162 communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) 163 if communityToken == nil { 164 return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) 165 } else { 166 publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), 167 communityToken.Address, protobuf.CommunityTokenAction_REMOTE_DESTRUCT) 168 if publishErr != nil { 169 log.Warn("can't publish remote destruct action") 170 } 171 } 172 return communityToken, err 173 } 174 175 func (s *Service) handleBurnCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { 176 if status == transactions.Success { 177 // get new max supply and update database 178 newMaxSupply, err := s.maxSupply(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.To.String()) 179 if err != nil { 180 return nil, err 181 } 182 err = s.Messenger.UpdateCommunityTokenSupply(int(pendingTransaction.ChainID), pendingTransaction.To.String(), &bigint.BigInt{Int: newMaxSupply}) 183 if err != nil { 184 return nil, err 185 } 186 } 187 188 communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) 189 190 if communityToken == nil { 191 return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) 192 } else { 193 publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), 194 communityToken.Address, protobuf.CommunityTokenAction_BURN) 195 if publishErr != nil { 196 log.Warn("can't publish burn action") 197 } 198 } 199 return communityToken, err 200 } 201 202 func (s *Service) handleDeployOwnerToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, *token.CommunityToken, error) { 203 newMasterAddress, err := s.GetMasterTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex()) 204 if err != nil { 205 return nil, nil, err 206 } 207 newOwnerAddress, err := s.GetOwnerTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex()) 208 if err != nil { 209 return nil, nil, err 210 } 211 212 err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryOwnerContractAddress(pendingTransaction.Hash.Hex()), newOwnerAddress) 213 if err != nil { 214 return nil, nil, err 215 } 216 err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryMasterContractAddress(pendingTransaction.Hash.Hex()), newMasterAddress) 217 if err != nil { 218 return nil, nil, err 219 } 220 221 ownerToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newOwnerAddress) 222 if err != nil { 223 return nil, nil, err 224 } 225 226 masterToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newMasterAddress) 227 if err != nil { 228 return nil, nil, err 229 } 230 231 return ownerToken, masterToken, nil 232 } 233 234 func (s *Service) updateStateAndAddTokenToCommunityDescription(status string, chainID int, address string) (*token.CommunityToken, error) { 235 tokenToUpdate, err := s.Messenger.GetCommunityTokenByChainAndAddress(chainID, address) 236 if err != nil { 237 return nil, err 238 } 239 if tokenToUpdate == nil { 240 return nil, fmt.Errorf("token does not exist in database: chainID=%v, address=%v", chainID, address) 241 } 242 243 if status == transactions.Success { 244 err := s.Messenger.UpdateCommunityTokenState(chainID, address, token.Deployed) 245 if err != nil { 246 return nil, err 247 } 248 err = s.Messenger.AddCommunityToken(tokenToUpdate.CommunityID, chainID, address) 249 if err != nil { 250 return nil, err 251 } 252 } else { 253 err := s.Messenger.UpdateCommunityTokenState(chainID, address, token.Failed) 254 if err != nil { 255 return nil, err 256 } 257 } 258 return s.Messenger.GetCommunityTokenByChainAndAddress(chainID, address) 259 } 260 261 func (s *Service) handleDeployCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { 262 return s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), pendingTransaction.To.String()) 263 } 264 265 func (s *Service) handleSetSignerPubKey(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { 266 267 communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) 268 if err != nil { 269 return nil, err 270 } 271 if communityToken == nil { 272 return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) 273 } 274 275 if status == transactions.Success { 276 _, err := s.Messenger.PromoteSelfToControlNode(types.FromHex(communityToken.CommunityID)) 277 if err != nil { 278 return nil, err 279 } 280 } 281 return communityToken, err 282 } 283 284 // Stop is run when a service is stopped. 285 func (s *Service) Stop() error { 286 s.walletWatcher.Stop() 287 return nil 288 } 289 290 func (s *Service) Init(messenger *protocol.Messenger) { 291 s.Messenger = messenger 292 } 293 294 func (s *Service) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) { 295 backend, err := s.manager.rpcClient.EthClient(chainID) 296 if err != nil { 297 return nil, err 298 } 299 return communityownertokenregistry.NewCommunityOwnerTokenRegistry(common.HexToAddress(contractAddress), backend) 300 } 301 302 func (s *Service) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) { 303 304 backend, err := s.manager.rpcClient.EthClient(chainID) 305 if err != nil { 306 return nil, err 307 } 308 return ownertoken.NewOwnerToken(common.HexToAddress(contractAddress), backend) 309 310 } 311 312 func (s *Service) NewMasterTokenInstance(chainID uint64, contractAddress string) (*mastertoken.MasterToken, error) { 313 backend, err := s.manager.rpcClient.EthClient(chainID) 314 if err != nil { 315 return nil, err 316 } 317 return mastertoken.NewMasterToken(common.HexToAddress(contractAddress), backend) 318 } 319 320 func (s *Service) validateTokens(tokenIds []*bigint.BigInt) error { 321 if len(tokenIds) == 0 { 322 return errors.New("token list is empty") 323 } 324 return nil 325 } 326 327 func (s *Service) validateBurnAmount(ctx context.Context, burnAmount *bigint.BigInt, chainID uint64, contractAddress string) error { 328 if burnAmount.Cmp(big.NewInt(0)) <= 0 { 329 return errors.New("burnAmount is less than 0") 330 } 331 remainingSupply, err := s.remainingSupply(ctx, chainID, contractAddress) 332 if err != nil { 333 return err 334 } 335 if burnAmount.Cmp(remainingSupply.Int) > 1 { 336 return errors.New("burnAmount is bigger than remaining amount") 337 } 338 return nil 339 } 340 341 func (s *Service) remainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { 342 tokenType, err := s.db.GetTokenType(chainID, contractAddress) 343 if err != nil { 344 return nil, err 345 } 346 switch tokenType { 347 case protobuf.CommunityTokenType_ERC721: 348 return s.remainingCollectiblesSupply(ctx, chainID, contractAddress) 349 case protobuf.CommunityTokenType_ERC20: 350 return s.remainingAssetsSupply(ctx, chainID, contractAddress) 351 default: 352 return nil, fmt.Errorf("unknown token type: %v", tokenType) 353 } 354 } 355 356 func (s *Service) prepareNewMaxSupply(ctx context.Context, chainID uint64, contractAddress string, burnAmount *bigint.BigInt) (*big.Int, error) { 357 maxSupply, err := s.maxSupply(ctx, chainID, contractAddress) 358 if err != nil { 359 return nil, err 360 } 361 var newMaxSupply = new(big.Int) 362 newMaxSupply.Sub(maxSupply, burnAmount.Int) 363 return newMaxSupply, nil 364 } 365 366 // RemainingSupply = MaxSupply - MintedCount 367 func (s *Service) remainingCollectiblesSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { 368 callOpts := &bind.CallOpts{Context: ctx, Pending: false} 369 contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress) 370 if err != nil { 371 return nil, err 372 } 373 maxSupply, err := contractInst.MaxSupply(callOpts) 374 if err != nil { 375 return nil, err 376 } 377 mintedCount, err := contractInst.MintedCount(callOpts) 378 if err != nil { 379 return nil, err 380 } 381 var res = new(big.Int) 382 res.Sub(maxSupply, mintedCount) 383 return &bigint.BigInt{Int: res}, nil 384 } 385 386 // RemainingSupply = MaxSupply - TotalSupply 387 func (s *Service) remainingAssetsSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { 388 callOpts := &bind.CallOpts{Context: ctx, Pending: false} 389 contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress) 390 if err != nil { 391 return nil, err 392 } 393 maxSupply, err := contractInst.MaxSupply(callOpts) 394 if err != nil { 395 return nil, err 396 } 397 totalSupply, err := contractInst.TotalSupply(callOpts) 398 if err != nil { 399 return nil, err 400 } 401 var res = new(big.Int) 402 res.Sub(maxSupply, totalSupply) 403 return &bigint.BigInt{Int: res}, nil 404 } 405 406 func (s *Service) ValidateWalletsAndAmounts(walletAddresses []string, amount *bigint.BigInt) error { 407 if len(walletAddresses) == 0 { 408 return errors.New("wallet addresses list is empty") 409 } 410 if amount.Cmp(big.NewInt(0)) <= 0 { 411 return errors.New("amount is <= 0") 412 } 413 return nil 414 } 415 416 func (s *Service) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) { 417 418 callOpts := &bind.CallOpts{Context: ctx, Pending: false} 419 contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress) 420 if err != nil { 421 return "", err 422 } 423 signerPubKey, err := contractInst.SignerPublicKey(callOpts) 424 if err != nil { 425 return "", err 426 } 427 428 return types.ToHex(signerPubKey), nil 429 } 430 431 func (s *Service) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) { 432 // 1. Get Owner Token contract address from deployer contract - SafeGetOwnerTokenAddress() 433 ownerTokenAddr, err := s.SafeGetOwnerTokenAddress(ctx, chainID, communityID) 434 if err != nil { 435 return "", err 436 } 437 // 2. Get Signer from owner token contract - GetSignerPubKey() 438 return s.GetSignerPubKey(ctx, chainID, ownerTokenAddr) 439 } 440 441 func (s *Service) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) { 442 callOpts := &bind.CallOpts{Context: ctx, Pending: false} 443 deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) 444 if err != nil { 445 return "", err 446 } 447 registryAddr, err := deployerContractInst.DeploymentRegistry(callOpts) 448 if err != nil { 449 return "", err 450 } 451 registryContractInst, err := s.NewCommunityOwnerTokenRegistryInstance(chainID, registryAddr.Hex()) 452 if err != nil { 453 return "", err 454 } 455 communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID) 456 if err != nil { 457 return "", err 458 } 459 ownerTokenAddress, err := registryContractInst.GetEntry(callOpts, communityEthAddress) 460 461 return ownerTokenAddress.Hex(), err 462 } 463 464 func (s *Service) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) { 465 return s.manager.GetCollectibleContractData(chainID, contractAddress) 466 } 467 468 func (s *Service) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) { 469 return s.manager.GetAssetContractData(chainID, contractAddress) 470 } 471 472 func (s *Service) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) { 473 return s.manager.DeploymentSignatureDigest(chainID, addressFrom, communityID) 474 } 475 476 func (s *Service) ProcessCommunityTokenAction(message *protobuf.CommunityTokenAction) error { 477 communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(message.ChainId), message.ContractAddress) 478 if err != nil { 479 return err 480 } 481 if communityToken == nil { 482 return fmt.Errorf("can't find community token in database: chain %v, address %v", message.ChainId, message.ContractAddress) 483 } 484 485 if message.ActionType == protobuf.CommunityTokenAction_BURN { 486 // get new max supply and update database 487 newMaxSupply, err := s.maxSupply(context.Background(), uint64(communityToken.ChainID), communityToken.Address) 488 if err != nil { 489 return nil 490 } 491 err = s.Messenger.UpdateCommunityTokenSupply(communityToken.ChainID, communityToken.Address, &bigint.BigInt{Int: newMaxSupply}) 492 if err != nil { 493 return err 494 } 495 communityToken, _ = s.Messenger.GetCommunityTokenByChainAndAddress(int(message.ChainId), message.ContractAddress) 496 } 497 498 signal.SendCommunityTokenActionSignal(communityToken, message.ActionType) 499 500 return nil 501 } 502 503 func (s *Service) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) { 504 505 if len(newSignerPubKey) <= 0 { 506 return "", fmt.Errorf("signerPubKey is empty") 507 } 508 509 transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, s.accountsManager, s.config.KeyStoreDir, txArgs.From, password)) 510 511 contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress) 512 if err != nil { 513 return "", err 514 } 515 516 tx, err := contractInst.SetSignerPublicKey(transactOpts, common.FromHex(newSignerPubKey)) 517 if err != nil { 518 return "", err 519 } 520 521 err = s.pendingTracker.TrackPendingTransaction( 522 wcommon.ChainID(chainID), 523 tx.Hash(), 524 common.Address(txArgs.From), 525 common.HexToAddress(contractAddress), 526 transactions.SetSignerPublicKey, 527 transactions.Keep, 528 "", 529 ) 530 if err != nil { 531 log.Error("TrackPendingTransaction error", "error", err) 532 return "", err 533 } 534 535 return tx.Hash().Hex(), nil 536 } 537 538 func (s *Service) maxSupplyCollectibles(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { 539 callOpts := &bind.CallOpts{Context: ctx, Pending: false} 540 contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress) 541 if err != nil { 542 return nil, err 543 } 544 return contractInst.MaxSupply(callOpts) 545 } 546 547 func (s *Service) maxSupplyAssets(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { 548 callOpts := &bind.CallOpts{Context: ctx, Pending: false} 549 contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress) 550 if err != nil { 551 return nil, err 552 } 553 return contractInst.MaxSupply(callOpts) 554 } 555 556 func (s *Service) maxSupply(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { 557 tokenType, err := s.db.GetTokenType(chainID, contractAddress) 558 if err != nil { 559 return nil, err 560 } 561 562 switch tokenType { 563 case protobuf.CommunityTokenType_ERC721: 564 return s.maxSupplyCollectibles(ctx, chainID, contractAddress) 565 case protobuf.CommunityTokenType_ERC20: 566 return s.maxSupplyAssets(ctx, chainID, contractAddress) 567 default: 568 return nil, fmt.Errorf("unknown token type: %v", tokenType) 569 } 570 } 571 572 func (s *Service) CreateCommunityTokenAndSave(chainID int, deploymentParameters DeploymentParameters, 573 deployerAddress string, contractAddress string, tokenType protobuf.CommunityTokenType, privilegesLevel token.PrivilegesLevel, transactionHash string) (*token.CommunityToken, error) { 574 575 contractVersion := "" 576 if privilegesLevel == token.CommunityLevel { 577 contractVersion = s.currentVersion() 578 } 579 580 tokenToSave := &token.CommunityToken{ 581 TokenType: tokenType, 582 CommunityID: deploymentParameters.CommunityID, 583 Address: contractAddress, 584 Name: deploymentParameters.Name, 585 Symbol: deploymentParameters.Symbol, 586 Description: deploymentParameters.Description, 587 Supply: &bigint.BigInt{Int: deploymentParameters.GetSupply()}, 588 InfiniteSupply: deploymentParameters.InfiniteSupply, 589 Transferable: deploymentParameters.Transferable, 590 RemoteSelfDestruct: deploymentParameters.RemoteSelfDestruct, 591 ChainID: chainID, 592 DeployState: token.InProgress, 593 Decimals: deploymentParameters.Decimals, 594 Deployer: deployerAddress, 595 PrivilegesLevel: privilegesLevel, 596 Base64Image: deploymentParameters.Base64Image, 597 TransactionHash: transactionHash, 598 Version: contractVersion, 599 } 600 601 return s.Messenger.SaveCommunityToken(tokenToSave, deploymentParameters.CroppedImage) 602 } 603 604 const ( 605 MasterSuffix = "-master" 606 OwnerSuffix = "-owner" 607 ) 608 609 func (s *Service) TemporaryMasterContractAddress(hash string) string { 610 return hash + MasterSuffix 611 } 612 613 func (s *Service) TemporaryOwnerContractAddress(hash string) string { 614 return hash + OwnerSuffix 615 } 616 617 func (s *Service) HashFromTemporaryContractAddress(address string) string { 618 if strings.HasSuffix(address, OwnerSuffix) { 619 return strings.TrimSuffix(address, OwnerSuffix) 620 } else if strings.HasSuffix(address, MasterSuffix) { 621 return strings.TrimSuffix(address, MasterSuffix) 622 } 623 return "" 624 } 625 626 func (s *Service) GetMasterTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { 627 ethClient, err := s.manager.rpcClient.EthClient(chainID) 628 if err != nil { 629 return "", err 630 } 631 632 receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash)) 633 if err != nil { 634 return "", err 635 } 636 637 deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) 638 if err != nil { 639 return "", err 640 } 641 642 logMasterTokenCreatedSig := []byte("DeployMasterToken(address)") 643 logMasterTokenCreatedSigHash := crypto.Keccak256Hash(logMasterTokenCreatedSig) 644 645 for _, vLog := range receipt.Logs { 646 if vLog.Topics[0].Hex() == logMasterTokenCreatedSigHash.Hex() { 647 event, err := deployerContractInst.ParseDeployMasterToken(*vLog) 648 if err != nil { 649 return "", err 650 } 651 return event.Arg0.Hex(), nil 652 } 653 } 654 return "", fmt.Errorf("can't find master token address in transaction: %v", txHash) 655 } 656 657 func (s *Service) GetOwnerTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { 658 ethClient, err := s.manager.rpcClient.EthClient(chainID) 659 if err != nil { 660 return "", err 661 } 662 663 receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash)) 664 if err != nil { 665 return "", err 666 } 667 668 deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) 669 if err != nil { 670 return "", err 671 } 672 673 logOwnerTokenCreatedSig := []byte("DeployOwnerToken(address)") 674 logOwnerTokenCreatedSigHash := crypto.Keccak256Hash(logOwnerTokenCreatedSig) 675 676 for _, vLog := range receipt.Logs { 677 if vLog.Topics[0].Hex() == logOwnerTokenCreatedSigHash.Hex() { 678 event, err := deployerContractInst.ParseDeployOwnerToken(*vLog) 679 if err != nil { 680 return "", err 681 } 682 return event.Arg0.Hex(), nil 683 } 684 } 685 return "", fmt.Errorf("can't find owner token address in transaction: %v", txHash) 686 } 687 688 func (s *Service) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, chainID uint64, contractAddress string) error { 689 communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(chainID), contractAddress) 690 if err != nil { 691 return err 692 } 693 if communityToken == nil { 694 return fmt.Errorf("can't find token with address %v on chain %v", contractAddress, chainID) 695 } 696 if communityToken.DeployState != token.InProgress { 697 return fmt.Errorf("token with address %v on chain %v is not in progress", contractAddress, chainID) 698 } 699 700 hashString := communityToken.TransactionHash 701 if hashString == "" && (communityToken.PrivilegesLevel == token.OwnerLevel || communityToken.PrivilegesLevel == token.MasterLevel) { 702 hashString = s.HashFromTemporaryContractAddress(communityToken.Address) 703 } 704 705 if hashString == "" { 706 return fmt.Errorf("can't find transaction hash for token with address %v on chain %v", contractAddress, chainID) 707 } 708 709 transactionType := transactions.DeployCommunityToken 710 if communityToken.PrivilegesLevel == token.OwnerLevel || communityToken.PrivilegesLevel == token.MasterLevel { 711 transactionType = transactions.DeployOwnerToken 712 } 713 714 _, err = s.pendingTracker.GetPendingEntry(wcommon.ChainID(chainID), common.HexToHash(hashString)) 715 if errors.Is(err, sql.ErrNoRows) { 716 // start only if no pending transaction in database 717 err = s.pendingTracker.TrackPendingTransaction( 718 wcommon.ChainID(chainID), 719 common.HexToHash(hashString), 720 common.HexToAddress(communityToken.Deployer), 721 common.Address{}, 722 transactionType, 723 transactions.Keep, 724 "", 725 ) 726 log.Debug("retracking pending transaction with hashId ", hashString) 727 } else { 728 log.Debug("pending transaction with hashId is already tracked ", hashString) 729 } 730 return err 731 } 732 733 func (s *Service) publishTokenActionToPrivilegedMembers(communityID string, chainID uint64, contractAddress string, actionType protobuf.CommunityTokenAction_ActionType) error { 734 decodedCommunityID, err := types.DecodeHex(communityID) 735 if err != nil { 736 return err 737 } 738 return s.Messenger.PublishTokenActionToPrivilegedMembers(decodedCommunityID, chainID, contractAddress, actionType) 739 }