github.com/status-im/status-go@v1.1.0/services/communitytokens/manager.go (about) 1 package communitytokens 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/ethereum/go-ethereum/accounts/abi/bind" 8 "github.com/ethereum/go-ethereum/common" 9 "github.com/ethereum/go-ethereum/common/math" 10 "github.com/ethereum/go-ethereum/signer/core/apitypes" 11 "github.com/status-im/status-go/contracts/community-tokens/assets" 12 "github.com/status-im/status-go/contracts/community-tokens/collectibles" 13 communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer" 14 "github.com/status-im/status-go/eth-node/crypto" 15 "github.com/status-im/status-go/eth-node/types" 16 "github.com/status-im/status-go/protocol/communities" 17 "github.com/status-im/status-go/rpc" 18 "github.com/status-im/status-go/services/wallet/bigint" 19 ) 20 21 type Manager struct { 22 rpcClient *rpc.Client 23 } 24 25 func NewManager(rpcClient *rpc.Client) *Manager { 26 return &Manager{ 27 rpcClient: rpcClient, 28 } 29 } 30 31 func (m *Manager) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { 32 backend, err := m.rpcClient.EthClient(chainID) 33 if err != nil { 34 return nil, err 35 } 36 return collectibles.NewCollectibles(common.HexToAddress(contractAddress), backend) 37 } 38 39 func (m *Manager) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) { 40 backend, err := m.rpcClient.EthClient(chainID) 41 if err != nil { 42 return nil, err 43 } 44 deployerAddr, err := communitytokendeployer.ContractAddress(chainID) 45 if err != nil { 46 return nil, err 47 } 48 return communitytokendeployer.NewCommunityTokenDeployer(deployerAddr, backend) 49 } 50 51 func (m *Manager) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { 52 contractInst, err := m.NewCollectiblesInstance(chainID, contractAddress) 53 if err != nil { 54 return nil, err 55 } 56 return contractInst, nil 57 } 58 59 func (m *Manager) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { 60 backend, err := m.rpcClient.EthClient(chainID) 61 if err != nil { 62 return nil, err 63 } 64 return assets.NewAssets(common.HexToAddress(contractAddress), backend) 65 } 66 67 func (m *Manager) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { 68 contractInst, err := m.NewAssetsInstance(chainID, contractAddress) 69 if err != nil { 70 return nil, err 71 } 72 return contractInst, nil 73 } 74 75 func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) { 76 callOpts := &bind.CallOpts{Context: context.Background(), Pending: false} 77 78 contract, err := m.GetCollectiblesContractInstance(chainID, contractAddress) 79 if err != nil { 80 return nil, err 81 } 82 totalSupply, err := contract.MaxSupply(callOpts) 83 if err != nil { 84 return nil, err 85 } 86 transferable, err := contract.Transferable(callOpts) 87 if err != nil { 88 return nil, err 89 } 90 remoteBurnable, err := contract.RemoteBurnable(callOpts) 91 if err != nil { 92 return nil, err 93 } 94 95 return &communities.CollectibleContractData{ 96 TotalSupply: &bigint.BigInt{Int: totalSupply}, 97 Transferable: transferable, 98 RemoteBurnable: remoteBurnable, 99 InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0, 100 }, nil 101 } 102 103 func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) { 104 callOpts := &bind.CallOpts{Context: context.Background(), Pending: false} 105 contract, err := m.GetAssetContractInstance(chainID, contractAddress) 106 if err != nil { 107 return nil, err 108 } 109 totalSupply, err := contract.MaxSupply(callOpts) 110 if err != nil { 111 return nil, err 112 } 113 114 return &communities.AssetContractData{ 115 TotalSupply: &bigint.BigInt{Int: totalSupply}, 116 InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0, 117 }, nil 118 } 119 120 func convert33BytesPubKeyToEthAddress(pubKey string) (common.Address, error) { 121 decoded, err := types.DecodeHex(pubKey) 122 if err != nil { 123 return common.Address{}, err 124 } 125 communityPubKey, err := crypto.DecompressPubkey(decoded) 126 if err != nil { 127 return common.Address{}, err 128 } 129 return common.Address(crypto.PubkeyToAddress(*communityPubKey)), nil 130 } 131 132 // Simpler version of hashing typed structured data alternative to typedStructuredDataHash. Keeping this for reference. 133 func customTypedStructuredDataHash(domainSeparator []byte, signatureTypedHash []byte, signer string, deployer string) types.Hash { 134 // every field should be 32 bytes, eth address is 20 bytes so padding should be added 135 emptyOffset := [12]byte{} 136 hashedEncoded := crypto.Keccak256Hash(signatureTypedHash, emptyOffset[:], common.HexToAddress(signer).Bytes(), 137 emptyOffset[:], common.HexToAddress(deployer).Bytes()) 138 rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, hashedEncoded.Bytes())) 139 return crypto.Keccak256Hash(rawData) 140 } 141 142 // Returns a typed structured hash according to https://eips.ethereum.org/EIPS/eip-712 143 // Domain separator from smart contract is used. 144 func typedStructuredDataHash(domainSeparator []byte, signer string, addressFrom string, deployerContractAddress string, chainID uint64) (types.Hash, error) { 145 myTypedData := apitypes.TypedData{ 146 Types: apitypes.Types{ 147 "Deploy": []apitypes.Type{ 148 {Name: "signer", Type: "address"}, 149 {Name: "deployer", Type: "address"}, 150 }, 151 "EIP712Domain": []apitypes.Type{ 152 {Name: "name", Type: "string"}, 153 {Name: "version", Type: "string"}, 154 {Name: "chainId", Type: "uint256"}, 155 {Name: "verifyingContract", Type: "address"}, 156 }, 157 }, 158 PrimaryType: "Deploy", 159 // Domain field should be here to keep correct structure but 160 // domainSeparator from smart contract is used. 161 Domain: apitypes.TypedDataDomain{ 162 Name: "CommunityTokenDeployer", // name from Deployer smart contract 163 Version: "1", // version from Deployer smart contract 164 ChainId: math.NewHexOrDecimal256(int64(chainID)), 165 VerifyingContract: deployerContractAddress, 166 }, 167 Message: apitypes.TypedDataMessage{ 168 "signer": signer, 169 "deployer": addressFrom, 170 }, 171 } 172 173 typedDataHash, err := myTypedData.HashStruct(myTypedData.PrimaryType, myTypedData.Message) 174 if err != nil { 175 return types.Hash{}, err 176 } 177 rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, string(typedDataHash))) 178 return crypto.Keccak256Hash(rawData), nil 179 } 180 181 // Creates 182 func (m *Manager) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) { 183 callOpts := &bind.CallOpts{Pending: false} 184 communityEthAddr, err := convert33BytesPubKeyToEthAddress(communityID) 185 if err != nil { 186 return nil, err 187 } 188 189 deployerAddr, err := communitytokendeployer.ContractAddress(chainID) 190 if err != nil { 191 return nil, err 192 } 193 deployerContractInst, err := m.NewCommunityTokenDeployerInstance(chainID) 194 if err != nil { 195 return nil, err 196 } 197 198 domainSeparator, err := deployerContractInst.DOMAINSEPARATOR(callOpts) 199 if err != nil { 200 return nil, err 201 } 202 203 structedHash, err := typedStructuredDataHash(domainSeparator[:], communityEthAddr.Hex(), addressFrom, deployerAddr.Hex(), chainID) 204 if err != nil { 205 return nil, err 206 } 207 208 return structedHash.Bytes(), nil 209 }