github.com/status-im/status-go@v1.1.0/services/communitytokens/api.go (about) 1 package communitytokens 2 3 import ( 4 "context" 5 "fmt" 6 "math/big" 7 8 "github.com/pkg/errors" 9 10 "github.com/ethereum/go-ethereum/accounts/abi/bind" 11 "github.com/ethereum/go-ethereum/common" 12 13 "github.com/ethereum/go-ethereum/log" 14 "github.com/status-im/status-go/contracts/community-tokens/assets" 15 "github.com/status-im/status-go/contracts/community-tokens/collectibles" 16 communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer" 17 "github.com/status-im/status-go/contracts/community-tokens/ownertoken" 18 communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry" 19 "github.com/status-im/status-go/eth-node/crypto" 20 "github.com/status-im/status-go/eth-node/types" 21 "github.com/status-im/status-go/images" 22 "github.com/status-im/status-go/protocol/communities/token" 23 "github.com/status-im/status-go/protocol/protobuf" 24 "github.com/status-im/status-go/services/utils" 25 "github.com/status-im/status-go/services/wallet/bigint" 26 wcommon "github.com/status-im/status-go/services/wallet/common" 27 "github.com/status-im/status-go/transactions" 28 ) 29 30 func NewAPI(s *Service) *API { 31 return &API{ 32 s: s, 33 } 34 } 35 36 type API struct { 37 s *Service 38 } 39 40 type DeploymentDetails struct { 41 ContractAddress string `json:"contractAddress"` 42 TransactionHash string `json:"transactionHash"` 43 CommunityToken *token.CommunityToken `json:"communityToken"` 44 OwnerToken *token.CommunityToken `json:"ownerToken"` 45 MasterToken *token.CommunityToken `json:"masterToken"` 46 } 47 48 const maxSupply = 999999999 49 50 type DeploymentParameters struct { 51 Name string `json:"name"` 52 Symbol string `json:"symbol"` 53 Supply *bigint.BigInt `json:"supply"` 54 InfiniteSupply bool `json:"infiniteSupply"` 55 Transferable bool `json:"transferable"` 56 RemoteSelfDestruct bool `json:"remoteSelfDestruct"` 57 TokenURI string `json:"tokenUri"` 58 OwnerTokenAddress string `json:"ownerTokenAddress"` 59 MasterTokenAddress string `json:"masterTokenAddress"` 60 CommunityID string `json:"communityId"` 61 Description string `json:"description"` 62 CroppedImage *images.CroppedImage `json:"croppedImage,omitempty"` // for community tokens 63 Base64Image string `json:"base64image"` // for owner & master tokens 64 Decimals int `json:"decimals"` 65 } 66 67 func (d *DeploymentParameters) GetSupply() *big.Int { 68 if d.InfiniteSupply { 69 return d.GetInfiniteSupply() 70 } 71 return d.Supply.Int 72 } 73 74 // infinite supply for ERC721 is 2^256-1 75 func (d *DeploymentParameters) GetInfiniteSupply() *big.Int { 76 return GetInfiniteSupply() 77 } 78 79 func GetInfiniteSupply() *big.Int { 80 max := new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil) 81 max.Sub(max, big.NewInt(1)) 82 return max 83 } 84 85 func (d *DeploymentParameters) Validate(isAsset bool) error { 86 if len(d.Name) <= 0 { 87 return errors.New("empty collectible name") 88 } 89 if len(d.Symbol) <= 0 { 90 return errors.New("empty collectible symbol") 91 } 92 var maxForType = big.NewInt(maxSupply) 93 if isAsset { 94 assetMultiplier, _ := big.NewInt(0).SetString("1000000000000000000", 10) 95 maxForType = maxForType.Mul(maxForType, assetMultiplier) 96 } 97 if !d.InfiniteSupply && (d.Supply.Cmp(big.NewInt(0)) < 0 || d.Supply.Cmp(maxForType) > 0) { 98 return fmt.Errorf("wrong supply value: %v", d.Supply) 99 } 100 return nil 101 } 102 103 func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) { 104 err := deploymentParameters.Validate(false) 105 if err != nil { 106 return DeploymentDetails{}, err 107 } 108 transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) 109 110 ethClient, err := api.s.manager.rpcClient.EthClient(chainID) 111 if err != nil { 112 log.Error(err.Error()) 113 return DeploymentDetails{}, err 114 } 115 address, tx, _, err := collectibles.DeployCollectibles(transactOpts, ethClient, deploymentParameters.Name, 116 deploymentParameters.Symbol, deploymentParameters.GetSupply(), 117 deploymentParameters.RemoteSelfDestruct, deploymentParameters.Transferable, 118 deploymentParameters.TokenURI, common.HexToAddress(deploymentParameters.OwnerTokenAddress), 119 common.HexToAddress(deploymentParameters.MasterTokenAddress)) 120 if err != nil { 121 log.Error(err.Error()) 122 return DeploymentDetails{}, err 123 } 124 125 err = api.s.pendingTracker.TrackPendingTransaction( 126 wcommon.ChainID(chainID), 127 tx.Hash(), 128 common.Address(txArgs.From), 129 address, 130 transactions.DeployCommunityToken, 131 transactions.Keep, 132 "", 133 ) 134 if err != nil { 135 log.Error("TrackPendingTransaction error", "error", err) 136 return DeploymentDetails{}, err 137 } 138 139 savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), 140 protobuf.CommunityTokenType_ERC721, token.CommunityLevel, tx.Hash().Hex()) 141 if err != nil { 142 return DeploymentDetails{}, err 143 } 144 145 return DeploymentDetails{ 146 ContractAddress: address.Hex(), 147 TransactionHash: tx.Hash().Hex(), 148 CommunityToken: savedCommunityToken}, nil 149 } 150 151 func decodeSignature(sig []byte) (r [32]byte, s [32]byte, v uint8, err error) { 152 if len(sig) != crypto.SignatureLength { 153 return [32]byte{}, [32]byte{}, 0, fmt.Errorf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength) 154 } 155 copy(r[:], sig[:32]) 156 copy(s[:], sig[32:64]) 157 v = sig[64] + 27 158 return r, s, v, nil 159 } 160 161 func prepareDeploymentSignatureStruct(signature string, communityID string, addressFrom common.Address) (communitytokendeployer.CommunityTokenDeployerDeploymentSignature, error) { 162 r, s, v, err := decodeSignature(common.FromHex(signature)) 163 if err != nil { 164 return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err 165 } 166 communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID) 167 if err != nil { 168 return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err 169 } 170 communitySignature := communitytokendeployer.CommunityTokenDeployerDeploymentSignature{ 171 V: v, 172 R: r, 173 S: s, 174 Deployer: addressFrom, 175 Signer: communityEthAddress, 176 } 177 return communitySignature, nil 178 } 179 180 func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, 181 ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, 182 signerPubKey string, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) { 183 err := ownerTokenParameters.Validate(false) 184 if err != nil { 185 return DeploymentDetails{}, err 186 } 187 188 if len(signerPubKey) <= 0 { 189 return DeploymentDetails{}, fmt.Errorf("signerPubKey is empty") 190 } 191 192 err = masterTokenParameters.Validate(false) 193 if err != nil { 194 return DeploymentDetails{}, err 195 } 196 197 transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) 198 199 deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID) 200 if err != nil { 201 return DeploymentDetails{}, err 202 } 203 204 ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{ 205 Name: ownerTokenParameters.Name, 206 Symbol: ownerTokenParameters.Symbol, 207 BaseURI: ownerTokenParameters.TokenURI, 208 } 209 210 masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{ 211 Name: masterTokenParameters.Name, 212 Symbol: masterTokenParameters.Symbol, 213 BaseURI: masterTokenParameters.TokenURI, 214 } 215 216 signature, err := api.s.Messenger.CreateCommunityTokenDeploymentSignature(context.Background(), chainID, txArgs.From.Hex(), ownerTokenParameters.CommunityID) 217 if err != nil { 218 return DeploymentDetails{}, err 219 } 220 221 communitySignature, err := prepareDeploymentSignatureStruct(types.HexBytes(signature).String(), ownerTokenParameters.CommunityID, common.Address(txArgs.From)) 222 if err != nil { 223 return DeploymentDetails{}, err 224 } 225 226 log.Debug("Signature:", communitySignature) 227 228 tx, err := deployerContractInst.Deploy(transactOpts, ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(signerPubKey)) 229 230 if err != nil { 231 log.Error(err.Error()) 232 return DeploymentDetails{}, err 233 } 234 235 log.Debug("Contract deployed hash:", tx.Hash().String()) 236 237 err = api.s.pendingTracker.TrackPendingTransaction( 238 wcommon.ChainID(chainID), 239 tx.Hash(), 240 common.Address(txArgs.From), 241 common.Address{}, 242 transactions.DeployOwnerToken, 243 transactions.Keep, 244 "", 245 ) 246 if err != nil { 247 log.Error("TrackPendingTransaction error", "error", err) 248 return DeploymentDetails{}, err 249 } 250 251 savedOwnerToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), ownerTokenParameters, txArgs.From.Hex(), 252 api.s.TemporaryOwnerContractAddress(tx.Hash().Hex()), protobuf.CommunityTokenType_ERC721, token.OwnerLevel, tx.Hash().Hex()) 253 if err != nil { 254 return DeploymentDetails{}, err 255 } 256 savedMasterToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), masterTokenParameters, txArgs.From.Hex(), 257 api.s.TemporaryMasterContractAddress(tx.Hash().Hex()), protobuf.CommunityTokenType_ERC721, token.MasterLevel, tx.Hash().Hex()) 258 if err != nil { 259 return DeploymentDetails{}, err 260 } 261 262 return DeploymentDetails{ 263 ContractAddress: "", 264 TransactionHash: tx.Hash().Hex(), 265 OwnerToken: savedOwnerToken, 266 MasterToken: savedMasterToken}, nil 267 } 268 269 // recovery function which starts transaction tracking again 270 func (api *API) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, chainID uint64, contractAddress string) error { 271 return api.s.ReTrackOwnerTokenDeploymentTransaction(ctx, chainID, contractAddress) 272 } 273 274 func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) { 275 276 err := deploymentParameters.Validate(true) 277 if err != nil { 278 return DeploymentDetails{}, err 279 } 280 281 transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) 282 283 ethClient, err := api.s.manager.rpcClient.EthClient(chainID) 284 if err != nil { 285 log.Error(err.Error()) 286 return DeploymentDetails{}, err 287 } 288 289 const decimals = 18 290 address, tx, _, err := assets.DeployAssets(transactOpts, ethClient, deploymentParameters.Name, 291 deploymentParameters.Symbol, decimals, deploymentParameters.GetSupply(), 292 deploymentParameters.TokenURI, 293 common.HexToAddress(deploymentParameters.OwnerTokenAddress), 294 common.HexToAddress(deploymentParameters.MasterTokenAddress)) 295 if err != nil { 296 log.Error(err.Error()) 297 return DeploymentDetails{}, err 298 } 299 300 err = api.s.pendingTracker.TrackPendingTransaction( 301 wcommon.ChainID(chainID), 302 tx.Hash(), 303 common.Address(txArgs.From), 304 address, 305 transactions.DeployCommunityToken, 306 transactions.Keep, 307 "", 308 ) 309 if err != nil { 310 log.Error("TrackPendingTransaction error", "error", err) 311 return DeploymentDetails{}, err 312 } 313 314 savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), 315 protobuf.CommunityTokenType_ERC20, token.CommunityLevel, tx.Hash().Hex()) 316 if err != nil { 317 return DeploymentDetails{}, err 318 } 319 320 return DeploymentDetails{ 321 ContractAddress: address.Hex(), 322 TransactionHash: tx.Hash().Hex(), 323 CommunityToken: savedCommunityToken}, nil 324 } 325 326 func (api *API) DeployCollectiblesEstimate(ctx context.Context, chainID uint64, fromAddress string) (*CommunityTokenFees, error) { 327 return api.s.deployCollectiblesEstimate(ctx, chainID, fromAddress) 328 } 329 330 func (api *API) DeployAssetsEstimate(ctx context.Context, chainID uint64, fromAddress string) (*CommunityTokenFees, error) { 331 return api.s.deployAssetsEstimate(ctx, chainID, fromAddress) 332 } 333 334 func (api *API) DeployOwnerTokenEstimate(ctx context.Context, chainID uint64, fromAddress string, 335 ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, 336 communityID string, signerPubKey string) (*CommunityTokenFees, error) { 337 return api.s.deployOwnerTokenEstimate(ctx, chainID, fromAddress, ownerTokenParameters, masterTokenParameters, communityID, signerPubKey) 338 } 339 340 func (api *API) EstimateMintTokens(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (*CommunityTokenFees, error) { 341 return api.s.mintTokensEstimate(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount) 342 } 343 344 // This is only ERC721 function 345 func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, tokenIds []*bigint.BigInt) (*CommunityTokenFees, error) { 346 return api.s.remoteBurnEstimate(ctx, chainID, contractAddress, fromAddress, tokenIds) 347 } 348 349 func (api *API) EstimateBurn(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, burnAmount *bigint.BigInt) (*CommunityTokenFees, error) { 350 return api.s.burnEstimate(ctx, chainID, contractAddress, fromAddress, burnAmount) 351 } 352 353 func (api *API) EstimateSetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, newSignerPubKey string) (*CommunityTokenFees, error) { 354 return api.s.setSignerPubKeyEstimate(ctx, chainID, contractAddress, fromAddress, newSignerPubKey) 355 } 356 357 func (api *API) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) { 358 return api.s.NewOwnerTokenInstance(chainID, contractAddress) 359 } 360 361 func (api *API) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) { 362 return api.s.manager.NewCommunityTokenDeployerInstance(chainID) 363 } 364 365 func (api *API) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) { 366 return api.s.NewCommunityOwnerTokenRegistryInstance(chainID, contractAddress) 367 } 368 369 func (api *API) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { 370 return api.s.manager.NewCollectiblesInstance(chainID, contractAddress) 371 } 372 373 func (api *API) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { 374 return api.s.manager.NewAssetsInstance(chainID, contractAddress) 375 } 376 377 // Universal minting function for every type of token. 378 func (api *API) MintTokens(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, walletAddresses []string, amount *bigint.BigInt) (string, error) { 379 380 err := api.s.ValidateWalletsAndAmounts(walletAddresses, amount) 381 if err != nil { 382 return "", err 383 } 384 385 transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) 386 387 contractInst, err := NewTokenInstance(api.s, chainID, contractAddress) 388 if err != nil { 389 return "", err 390 } 391 392 tx, err := contractInst.Mint(transactOpts, walletAddresses, amount) 393 if err != nil { 394 return "", err 395 } 396 397 err = api.s.pendingTracker.TrackPendingTransaction( 398 wcommon.ChainID(chainID), 399 tx.Hash(), 400 common.Address(txArgs.From), 401 common.HexToAddress(contractAddress), 402 transactions.AirdropCommunityToken, 403 transactions.Keep, 404 "", 405 ) 406 if err != nil { 407 log.Error("TrackPendingTransaction error", "error", err) 408 return "", err 409 } 410 411 return tx.Hash().Hex(), nil 412 } 413 414 // This is only ERC721 function 415 func (api *API) RemoteDestructedAmount(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { 416 callOpts := &bind.CallOpts{Context: ctx, Pending: false} 417 contractInst, err := api.NewCollectiblesInstance(chainID, contractAddress) 418 if err != nil { 419 return nil, err 420 } 421 422 // total supply = airdropped only (w/o burnt) 423 totalSupply, err := contractInst.TotalSupply(callOpts) 424 if err != nil { 425 return nil, err 426 } 427 428 // minted = all created tokens (airdropped and remotely destructed) 429 mintedCount, err := contractInst.MintedCount(callOpts) 430 if err != nil { 431 return nil, err 432 } 433 434 var res = new(big.Int) 435 res.Sub(mintedCount, totalSupply) 436 437 return &bigint.BigInt{Int: res}, nil 438 } 439 440 // This is only ERC721 function 441 func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, tokenIds []*bigint.BigInt, additionalData string) (string, error) { 442 err := api.s.validateTokens(tokenIds) 443 if err != nil { 444 return "", err 445 } 446 447 transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) 448 449 var tempTokenIds []*big.Int 450 for _, v := range tokenIds { 451 tempTokenIds = append(tempTokenIds, v.Int) 452 } 453 454 contractInst, err := NewTokenInstance(api.s, chainID, contractAddress) 455 if err != nil { 456 return "", err 457 } 458 459 tx, err := contractInst.RemoteBurn(transactOpts, tempTokenIds) 460 if err != nil { 461 return "", err 462 } 463 464 err = api.s.pendingTracker.TrackPendingTransaction( 465 wcommon.ChainID(chainID), 466 tx.Hash(), 467 common.Address(txArgs.From), 468 common.HexToAddress(contractAddress), 469 transactions.RemoteDestructCollectible, 470 transactions.Keep, 471 additionalData, 472 ) 473 if err != nil { 474 log.Error("TrackPendingTransaction error", "error", err) 475 return "", err 476 } 477 478 return tx.Hash().Hex(), nil 479 } 480 481 func (api *API) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { 482 return api.s.manager.GetCollectiblesContractInstance(chainID, contractAddress) 483 } 484 485 func (api *API) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { 486 return api.s.manager.GetAssetContractInstance(chainID, contractAddress) 487 } 488 489 func (api *API) RemainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { 490 return api.s.remainingSupply(ctx, chainID, contractAddress) 491 } 492 493 func (api *API) Burn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, burnAmount *bigint.BigInt) (string, error) { 494 err := api.s.validateBurnAmount(ctx, burnAmount, chainID, contractAddress) 495 if err != nil { 496 return "", err 497 } 498 499 transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) 500 501 newMaxSupply, err := api.s.prepareNewMaxSupply(ctx, chainID, contractAddress, burnAmount) 502 if err != nil { 503 return "", err 504 } 505 506 contractInst, err := NewTokenInstance(api.s, chainID, contractAddress) 507 if err != nil { 508 return "", err 509 } 510 511 tx, err := contractInst.SetMaxSupply(transactOpts, newMaxSupply) 512 if err != nil { 513 return "", err 514 } 515 516 err = api.s.pendingTracker.TrackPendingTransaction( 517 wcommon.ChainID(chainID), 518 tx.Hash(), 519 common.Address(txArgs.From), 520 common.HexToAddress(contractAddress), 521 transactions.BurnCommunityToken, 522 transactions.Keep, 523 "", 524 ) 525 if err != nil { 526 log.Error("TrackPendingTransaction error", "error", err) 527 return "", err 528 } 529 530 return tx.Hash().Hex(), nil 531 } 532 533 // Gets signer public key from smart contract with a given chainId and address 534 func (api *API) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) { 535 return api.s.GetSignerPubKey(ctx, chainID, contractAddress) 536 } 537 538 // Gets signer public key directly from deployer contract 539 func (api *API) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) { 540 return api.s.SafeGetSignerPubKey(ctx, chainID, communityID) 541 } 542 543 // Gets owner token contract address from deployer contract 544 func (api *API) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) { 545 return api.s.SafeGetOwnerTokenAddress(ctx, chainID, communityID) 546 } 547 548 func (api *API) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) { 549 return api.s.SetSignerPubKey(ctx, chainID, contractAddress, txArgs, password, newSignerPubKey) 550 } 551 552 func (api *API) OwnerTokenOwnerAddress(ctx context.Context, chainID uint64, contractAddress string) (string, error) { 553 callOpts := &bind.CallOpts{Context: ctx, Pending: false} 554 contractInst, err := api.NewOwnerTokenInstance(chainID, contractAddress) 555 if err != nil { 556 return "", err 557 } 558 ownerAddress, err := contractInst.OwnerOf(callOpts, big.NewInt(0)) 559 if err != nil { 560 return "", err 561 } 562 return ownerAddress.Hex(), nil 563 }