package communitytokens import ( "context" "fmt" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/status-im/status-go/contracts/community-tokens/assets" "github.com/status-im/status-go/contracts/community-tokens/collectibles" communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/wallet/bigint" ) type Manager struct { rpcClient *rpc.Client } func NewManager(rpcClient *rpc.Client) *Manager { return &Manager{ rpcClient: rpcClient, } } func (m *Manager) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { backend, err := m.rpcClient.EthClient(chainID) if err != nil { return nil, err } return collectibles.NewCollectibles(common.HexToAddress(contractAddress), backend) } func (m *Manager) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) { backend, err := m.rpcClient.EthClient(chainID) if err != nil { return nil, err } deployerAddr, err := communitytokendeployer.ContractAddress(chainID) if err != nil { return nil, err } return communitytokendeployer.NewCommunityTokenDeployer(deployerAddr, backend) } func (m *Manager) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { contractInst, err := m.NewCollectiblesInstance(chainID, contractAddress) if err != nil { return nil, err } return contractInst, nil } func (m *Manager) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { backend, err := m.rpcClient.EthClient(chainID) if err != nil { return nil, err } return assets.NewAssets(common.HexToAddress(contractAddress), backend) } func (m *Manager) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { contractInst, err := m.NewAssetsInstance(chainID, contractAddress) if err != nil { return nil, err } return contractInst, nil } func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) { callOpts := &bind.CallOpts{Context: context.Background(), Pending: false} contract, err := m.GetCollectiblesContractInstance(chainID, contractAddress) if err != nil { return nil, err } totalSupply, err := contract.MaxSupply(callOpts) if err != nil { return nil, err } transferable, err := contract.Transferable(callOpts) if err != nil { return nil, err } remoteBurnable, err := contract.RemoteBurnable(callOpts) if err != nil { return nil, err } return &communities.CollectibleContractData{ TotalSupply: &bigint.BigInt{Int: totalSupply}, Transferable: transferable, RemoteBurnable: remoteBurnable, InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0, }, nil } func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) { callOpts := &bind.CallOpts{Context: context.Background(), Pending: false} contract, err := m.GetAssetContractInstance(chainID, contractAddress) if err != nil { return nil, err } totalSupply, err := contract.MaxSupply(callOpts) if err != nil { return nil, err } return &communities.AssetContractData{ TotalSupply: &bigint.BigInt{Int: totalSupply}, InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0, }, nil } func convert33BytesPubKeyToEthAddress(pubKey string) (common.Address, error) { decoded, err := types.DecodeHex(pubKey) if err != nil { return common.Address{}, err } communityPubKey, err := crypto.DecompressPubkey(decoded) if err != nil { return common.Address{}, err } return common.Address(crypto.PubkeyToAddress(*communityPubKey)), nil } // Simpler version of hashing typed structured data alternative to typedStructuredDataHash. Keeping this for reference. func customTypedStructuredDataHash(domainSeparator []byte, signatureTypedHash []byte, signer string, deployer string) types.Hash { // every field should be 32 bytes, eth address is 20 bytes so padding should be added emptyOffset := [12]byte{} hashedEncoded := crypto.Keccak256Hash(signatureTypedHash, emptyOffset[:], common.HexToAddress(signer).Bytes(), emptyOffset[:], common.HexToAddress(deployer).Bytes()) rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, hashedEncoded.Bytes())) return crypto.Keccak256Hash(rawData) } // Returns a typed structured hash according to https://eips.ethereum.org/EIPS/eip-712 // Domain separator from smart contract is used. func typedStructuredDataHash(domainSeparator []byte, signer string, addressFrom string, deployerContractAddress string, chainID uint64) (types.Hash, error) { myTypedData := apitypes.TypedData{ Types: apitypes.Types{ "Deploy": []apitypes.Type{ {Name: "signer", Type: "address"}, {Name: "deployer", Type: "address"}, }, "EIP712Domain": []apitypes.Type{ {Name: "name", Type: "string"}, {Name: "version", Type: "string"}, {Name: "chainId", Type: "uint256"}, {Name: "verifyingContract", Type: "address"}, }, }, PrimaryType: "Deploy", // Domain field should be here to keep correct structure but // domainSeparator from smart contract is used. Domain: apitypes.TypedDataDomain{ Name: "CommunityTokenDeployer", // name from Deployer smart contract Version: "1", // version from Deployer smart contract ChainId: math.NewHexOrDecimal256(int64(chainID)), VerifyingContract: deployerContractAddress, }, Message: apitypes.TypedDataMessage{ "signer": signer, "deployer": addressFrom, }, } typedDataHash, err := myTypedData.HashStruct(myTypedData.PrimaryType, myTypedData.Message) if err != nil { return types.Hash{}, err } rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, string(typedDataHash))) return crypto.Keccak256Hash(rawData), nil } // Creates func (m *Manager) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) { callOpts := &bind.CallOpts{Pending: false} communityEthAddr, err := convert33BytesPubKeyToEthAddress(communityID) if err != nil { return nil, err } deployerAddr, err := communitytokendeployer.ContractAddress(chainID) if err != nil { return nil, err } deployerContractInst, err := m.NewCommunityTokenDeployerInstance(chainID) if err != nil { return nil, err } domainSeparator, err := deployerContractInst.DOMAINSEPARATOR(callOpts) if err != nil { return nil, err } structedHash, err := typedStructuredDataHash(domainSeparator[:], communityEthAddr.Hex(), addressFrom, deployerAddr.Hex(), chainID) if err != nil { return nil, err } return structedHash.Bytes(), nil }