From 798f4a82a40fa27abcd2fa560ee1c4c18a2ccbcf Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sat, 30 Mar 2024 02:44:06 +0300 Subject: [PATCH] PrivateKeyWallet --- Directory.Build.props | 2 +- Directory.Packages.props | 3 + Thirdweb.Console/Program.cs | 37 +++-- .../Thirdweb.Contracts/ThirdwebContract.cs | 36 +++++ Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs | 9 +- Thirdweb/Thirdweb.Wallets/IWallet.cs | 16 +++ .../EmbeddedWallet.cs | 7 + .../PrivateKeyWallet.cs | 131 ++++++++++++++++++ Thirdweb/Thirdweb.Wallets/ThirdwebAccount.cs | 64 +++++++++ .../ThirdwebAccountOptions.cs | 18 +++ Thirdweb/Thirdweb.Wallets/WalletType.cs | 8 ++ Thirdweb/Thirdweb.csproj | 3 + 12 files changed, 323 insertions(+), 11 deletions(-) create mode 100644 Thirdweb/Thirdweb.Wallets/IWallet.cs create mode 100644 Thirdweb/Thirdweb.Wallets/Thirdweb.Wallets.EmbeddedWallet/EmbeddedWallet.cs create mode 100644 Thirdweb/Thirdweb.Wallets/Thirdweb.Wallets.PrivateKeyWallet/PrivateKeyWallet.cs create mode 100644 Thirdweb/Thirdweb.Wallets/ThirdwebAccount.cs create mode 100644 Thirdweb/Thirdweb.Wallets/ThirdwebAccountOptions.cs create mode 100644 Thirdweb/Thirdweb.Wallets/WalletType.cs diff --git a/Directory.Build.props b/Directory.Build.props index 5968e5f..e120acd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ 0.0.1 - netstandard2.0;netstandard2.1;net6.0;net7.0; + netstandard2.1;net6.0;net7.0; latest diff --git a/Directory.Packages.props b/Directory.Packages.props index 0da372c..5759a8f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,5 +13,8 @@ + + + \ No newline at end of file diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index d501da1..d937303 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -1,24 +1,45 @@ -using dotenv.net; +using System.Numerics; +using dotenv.net; +using Nethereum.Hex.HexTypes; using Newtonsoft.Json; using Thirdweb; DotEnv.Load(); var secretKey = Environment.GetEnvironmentVariable("THIRDWEB_SECRET_KEY"); +var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY"); var clientOptions = new ThirdwebClientOptions(secretKey: secretKey, fetchTimeoutOptions: new TimeoutOptions(storage: 30000, rpc: 10000)); var client = new ThirdwebClient(clientOptions); - Console.WriteLine($"Initialized ThirdwebClient: {JsonConvert.SerializeObject(clientOptions, Formatting.Indented)}"); -var rpc = ThirdwebRPC.GetRpcInstance(client, 1); -var blockNumber = await rpc.SendRequestAsync("eth_blockNumber"); -Console.WriteLine($"Block number: {blockNumber}"); +// var rpc = ThirdwebRPC.GetRpcInstance(client, 421614); +// var blockNumber = await rpc.SendRequestAsync("eth_blockNumber"); +// Console.WriteLine($"Block number: {blockNumber}"); -var contractOptions = new ThirdwebContractOptions(client: client, address: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", chain: 1, abi: "function name() view returns (string)"); +var contractOptions = new ThirdwebContractOptions( + client: client, + address: "0x81ebd23aA79bCcF5AaFb9c9c5B0Db4223c39102e", + chain: 421614, + abi: "[{\"type\": \"constructor\",\"name\": \"\",\"inputs\": [],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"event\",\"name\": \"Approval\",\"inputs\": [{\"type\": \"address\",\"name\": \"owner\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"spender\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"value\",\"indexed\": false,\"internalType\": \"uint256\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"DelegateChanged\",\"inputs\": [{\"type\": \"address\",\"name\": \"delegator\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"fromDelegate\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"toDelegate\",\"indexed\": true,\"internalType\": \"address\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"DelegateVotesChanged\",\"inputs\": [{\"type\": \"address\",\"name\": \"delegate\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"previousBalance\",\"indexed\": false,\"internalType\": \"uint256\"},{\"type\": \"uint256\",\"name\": \"newBalance\",\"indexed\": false,\"internalType\": \"uint256\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"EIP712DomainChanged\",\"inputs\": [],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"FlatPlatformFeeUpdated\",\"inputs\": [{\"type\": \"address\",\"name\": \"platformFeeRecipient\",\"indexed\": false,\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"flatFee\",\"indexed\": false,\"internalType\": \"uint256\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"Initialized\",\"inputs\": [{\"type\": \"uint8\",\"name\": \"version\",\"indexed\": false,\"internalType\": \"uint8\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"PlatformFeeInfoUpdated\",\"inputs\": [{\"type\": \"address\",\"name\": \"platformFeeRecipient\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"platformFeeBps\",\"indexed\": false,\"internalType\": \"uint256\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"PlatformFeeTypeUpdated\",\"inputs\": [{\"type\": \"uint8\",\"name\": \"feeType\",\"indexed\": false,\"internalType\": \"enum IPlatformFee.PlatformFeeType\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"PrimarySaleRecipientUpdated\",\"inputs\": [{\"type\": \"address\",\"name\": \"recipient\",\"indexed\": true,\"internalType\": \"address\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"RoleAdminChanged\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"indexed\": true,\"internalType\": \"bytes32\"},{\"type\": \"bytes32\",\"name\": \"previousAdminRole\",\"indexed\": true,\"internalType\": \"bytes32\"},{\"type\": \"bytes32\",\"name\": \"newAdminRole\",\"indexed\": true,\"internalType\": \"bytes32\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"RoleGranted\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"indexed\": true,\"internalType\": \"bytes32\"},{\"type\": \"address\",\"name\": \"account\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"sender\",\"indexed\": true,\"internalType\": \"address\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"RoleRevoked\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"indexed\": true,\"internalType\": \"bytes32\"},{\"type\": \"address\",\"name\": \"account\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"sender\",\"indexed\": true,\"internalType\": \"address\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"TokensMinted\",\"inputs\": [{\"type\": \"address\",\"name\": \"mintedTo\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"quantityMinted\",\"indexed\": false,\"internalType\": \"uint256\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"TokensMintedWithSignature\",\"inputs\": [{\"type\": \"address\",\"name\": \"signer\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"mintedTo\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"tuple\",\"name\": \"mintRequest\",\"components\": [{\"type\": \"address\",\"name\": \"to\",\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"primarySaleRecipient\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"quantity\",\"internalType\": \"uint256\"},{\"type\": \"uint256\",\"name\": \"price\",\"internalType\": \"uint256\"},{\"type\": \"address\",\"name\": \"currency\",\"internalType\": \"address\"},{\"type\": \"uint128\",\"name\": \"validityStartTimestamp\",\"internalType\": \"uint128\"},{\"type\": \"uint128\",\"name\": \"validityEndTimestamp\",\"internalType\": \"uint128\"},{\"type\": \"bytes32\",\"name\": \"uid\",\"internalType\": \"bytes32\"}],\"indexed\": false,\"internalType\": \"struct ITokenERC20.MintRequest\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"event\",\"name\": \"Transfer\",\"inputs\": [{\"type\": \"address\",\"name\": \"from\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"to\",\"indexed\": true,\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"value\",\"indexed\": false,\"internalType\": \"uint256\"}],\"outputs\": [],\"anonymous\": false},{\"type\": \"function\",\"name\": \"CLOCK_MODE\",\"inputs\": [],\"outputs\": [{\"type\": \"string\",\"name\": \"\",\"internalType\": \"string\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"DEFAULT_ADMIN_ROLE\",\"inputs\": [],\"outputs\": [{\"type\": \"bytes32\",\"name\": \"\",\"internalType\": \"bytes32\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"DOMAIN_SEPARATOR\",\"inputs\": [],\"outputs\": [{\"type\": \"bytes32\",\"name\": \"\",\"internalType\": \"bytes32\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"allowance\",\"inputs\": [{\"type\": \"address\",\"name\": \"owner\",\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"spender\",\"internalType\": \"address\"}],\"outputs\": [{\"type\": \"uint256\",\"name\": \"\",\"internalType\": \"uint256\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"approve\",\"inputs\": [{\"type\": \"address\",\"name\": \"spender\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"amount\",\"internalType\": \"uint256\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"}],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"balanceOf\",\"inputs\": [{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"}],\"outputs\": [{\"type\": \"uint256\",\"name\": \"\",\"internalType\": \"uint256\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"burn\",\"inputs\": [{\"type\": \"uint256\",\"name\": \"amount\",\"internalType\": \"uint256\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"burnFrom\",\"inputs\": [{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"amount\",\"internalType\": \"uint256\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"checkpoints\",\"inputs\": [{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"},{\"type\": \"uint32\",\"name\": \"pos\",\"internalType\": \"uint32\"}],\"outputs\": [{\"type\": \"tuple\",\"name\": \"\",\"components\": [{\"type\": \"uint32\",\"name\": \"fromBlock\",\"internalType\": \"uint32\"},{\"type\": \"uint224\",\"name\": \"votes\",\"internalType\": \"uint224\"}],\"internalType\": \"struct ERC20VotesUpgradeable.Checkpoint\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"clock\",\"inputs\": [],\"outputs\": [{\"type\": \"uint48\",\"name\": \"\",\"internalType\": \"uint48\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"contractType\",\"inputs\": [],\"outputs\": [{\"type\": \"bytes32\",\"name\": \"\",\"internalType\": \"bytes32\"}],\"stateMutability\": \"pure\"},{\"type\": \"function\",\"name\": \"contractURI\",\"inputs\": [],\"outputs\": [{\"type\": \"string\",\"name\": \"\",\"internalType\": \"string\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"contractVersion\",\"inputs\": [],\"outputs\": [{\"type\": \"uint8\",\"name\": \"\",\"internalType\": \"uint8\"}],\"stateMutability\": \"pure\"},{\"type\": \"function\",\"name\": \"decimals\",\"inputs\": [],\"outputs\": [{\"type\": \"uint8\",\"name\": \"\",\"internalType\": \"uint8\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"decreaseAllowance\",\"inputs\": [{\"type\": \"address\",\"name\": \"spender\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"subtractedValue\",\"internalType\": \"uint256\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"}],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"delegate\",\"inputs\": [{\"type\": \"address\",\"name\": \"delegatee\",\"internalType\": \"address\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"delegateBySig\",\"inputs\": [{\"type\": \"address\",\"name\": \"delegatee\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"nonce\",\"internalType\": \"uint256\"},{\"type\": \"uint256\",\"name\": \"expiry\",\"internalType\": \"uint256\"},{\"type\": \"uint8\",\"name\": \"v\",\"internalType\": \"uint8\"},{\"type\": \"bytes32\",\"name\": \"r\",\"internalType\": \"bytes32\"},{\"type\": \"bytes32\",\"name\": \"s\",\"internalType\": \"bytes32\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"delegates\",\"inputs\": [{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"}],\"outputs\": [{\"type\": \"address\",\"name\": \"\",\"internalType\": \"address\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"eip712Domain\",\"inputs\": [],\"outputs\": [{\"type\": \"bytes1\",\"name\": \"fields\",\"internalType\": \"bytes1\"},{\"type\": \"string\",\"name\": \"name\",\"internalType\": \"string\"},{\"type\": \"string\",\"name\": \"version\",\"internalType\": \"string\"},{\"type\": \"uint256\",\"name\": \"chainId\",\"internalType\": \"uint256\"},{\"type\": \"address\",\"name\": \"verifyingContract\",\"internalType\": \"address\"},{\"type\": \"bytes32\",\"name\": \"salt\",\"internalType\": \"bytes32\"},{\"type\": \"uint256[]\",\"name\": \"extensions\",\"internalType\": \"uint256[]\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"getPastTotalSupply\",\"inputs\": [{\"type\": \"uint256\",\"name\": \"timepoint\",\"internalType\": \"uint256\"}],\"outputs\": [{\"type\": \"uint256\",\"name\": \"\",\"internalType\": \"uint256\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"getPastVotes\",\"inputs\": [{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"timepoint\",\"internalType\": \"uint256\"}],\"outputs\": [{\"type\": \"uint256\",\"name\": \"\",\"internalType\": \"uint256\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"getPlatformFeeInfo\",\"inputs\": [],\"outputs\": [{\"type\": \"address\",\"name\": \"\",\"internalType\": \"address\"},{\"type\": \"uint16\",\"name\": \"\",\"internalType\": \"uint16\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"getRoleAdmin\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"internalType\": \"bytes32\"}],\"outputs\": [{\"type\": \"bytes32\",\"name\": \"\",\"internalType\": \"bytes32\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"getRoleMember\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"internalType\": \"bytes32\"},{\"type\": \"uint256\",\"name\": \"index\",\"internalType\": \"uint256\"}],\"outputs\": [{\"type\": \"address\",\"name\": \"\",\"internalType\": \"address\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"getRoleMemberCount\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"internalType\": \"bytes32\"}],\"outputs\": [{\"type\": \"uint256\",\"name\": \"\",\"internalType\": \"uint256\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"getVotes\",\"inputs\": [{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"}],\"outputs\": [{\"type\": \"uint256\",\"name\": \"\",\"internalType\": \"uint256\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"grantRole\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"internalType\": \"bytes32\"},{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"hasRole\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"internalType\": \"bytes32\"},{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"increaseAllowance\",\"inputs\": [{\"type\": \"address\",\"name\": \"spender\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"addedValue\",\"internalType\": \"uint256\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"}],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"initialize\",\"inputs\": [{\"type\": \"address\",\"name\": \"_defaultAdmin\",\"internalType\": \"address\"},{\"type\": \"string\",\"name\": \"_name\",\"internalType\": \"string\"},{\"type\": \"string\",\"name\": \"_symbol\",\"internalType\": \"string\"},{\"type\": \"string\",\"name\": \"_contractURI\",\"internalType\": \"string\"},{\"type\": \"address[]\",\"name\": \"_trustedForwarders\",\"internalType\": \"address[]\"},{\"type\": \"address\",\"name\": \"_primarySaleRecipient\",\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"_platformFeeRecipient\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"_platformFeeBps\",\"internalType\": \"uint256\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"isTrustedForwarder\",\"inputs\": [{\"type\": \"address\",\"name\": \"forwarder\",\"internalType\": \"address\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"mintTo\",\"inputs\": [{\"type\": \"address\",\"name\": \"to\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"amount\",\"internalType\": \"uint256\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"mintWithSignature\",\"inputs\": [{\"type\": \"tuple\",\"name\": \"_req\",\"components\": [{\"type\": \"address\",\"name\": \"to\",\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"primarySaleRecipient\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"quantity\",\"internalType\": \"uint256\"},{\"type\": \"uint256\",\"name\": \"price\",\"internalType\": \"uint256\"},{\"type\": \"address\",\"name\": \"currency\",\"internalType\": \"address\"},{\"type\": \"uint128\",\"name\": \"validityStartTimestamp\",\"internalType\": \"uint128\"},{\"type\": \"uint128\",\"name\": \"validityEndTimestamp\",\"internalType\": \"uint128\"},{\"type\": \"bytes32\",\"name\": \"uid\",\"internalType\": \"bytes32\"}],\"internalType\": \"struct ITokenERC20.MintRequest\"},{\"type\": \"bytes\",\"name\": \"_signature\",\"internalType\": \"bytes\"}],\"outputs\": [],\"stateMutability\": \"payable\"},{\"type\": \"function\",\"name\": \"multicall\",\"inputs\": [{\"type\": \"bytes[]\",\"name\": \"data\",\"internalType\": \"bytes[]\"}],\"outputs\": [{\"type\": \"bytes[]\",\"name\": \"results\",\"internalType\": \"bytes[]\"}],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"name\",\"inputs\": [],\"outputs\": [{\"type\": \"string\",\"name\": \"\",\"internalType\": \"string\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"nonces\",\"inputs\": [{\"type\": \"address\",\"name\": \"owner\",\"internalType\": \"address\"}],\"outputs\": [{\"type\": \"uint256\",\"name\": \"\",\"internalType\": \"uint256\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"numCheckpoints\",\"inputs\": [{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"}],\"outputs\": [{\"type\": \"uint32\",\"name\": \"\",\"internalType\": \"uint32\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"permit\",\"inputs\": [{\"type\": \"address\",\"name\": \"owner\",\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"spender\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"value\",\"internalType\": \"uint256\"},{\"type\": \"uint256\",\"name\": \"deadline\",\"internalType\": \"uint256\"},{\"type\": \"uint8\",\"name\": \"v\",\"internalType\": \"uint8\"},{\"type\": \"bytes32\",\"name\": \"r\",\"internalType\": \"bytes32\"},{\"type\": \"bytes32\",\"name\": \"s\",\"internalType\": \"bytes32\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"primarySaleRecipient\",\"inputs\": [],\"outputs\": [{\"type\": \"address\",\"name\": \"\",\"internalType\": \"address\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"renounceRole\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"internalType\": \"bytes32\"},{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"revokeRole\",\"inputs\": [{\"type\": \"bytes32\",\"name\": \"role\",\"internalType\": \"bytes32\"},{\"type\": \"address\",\"name\": \"account\",\"internalType\": \"address\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"setContractURI\",\"inputs\": [{\"type\": \"string\",\"name\": \"_uri\",\"internalType\": \"string\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"setPlatformFeeInfo\",\"inputs\": [{\"type\": \"address\",\"name\": \"_platformFeeRecipient\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"_platformFeeBps\",\"internalType\": \"uint256\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"setPrimarySaleRecipient\",\"inputs\": [{\"type\": \"address\",\"name\": \"_saleRecipient\",\"internalType\": \"address\"}],\"outputs\": [],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"supportsInterface\",\"inputs\": [{\"type\": \"bytes4\",\"name\": \"interfaceId\",\"internalType\": \"bytes4\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"symbol\",\"inputs\": [],\"outputs\": [{\"type\": \"string\",\"name\": \"\",\"internalType\": \"string\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"totalSupply\",\"inputs\": [],\"outputs\": [{\"type\": \"uint256\",\"name\": \"\",\"internalType\": \"uint256\"}],\"stateMutability\": \"view\"},{\"type\": \"function\",\"name\": \"transfer\",\"inputs\": [{\"type\": \"address\",\"name\": \"to\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"amount\",\"internalType\": \"uint256\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"}],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"transferFrom\",\"inputs\": [{\"type\": \"address\",\"name\": \"from\",\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"to\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"amount\",\"internalType\": \"uint256\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"}],\"stateMutability\": \"nonpayable\"},{\"type\": \"function\",\"name\": \"verify\",\"inputs\": [{\"type\": \"tuple\",\"name\": \"_req\",\"components\": [{\"type\": \"address\",\"name\": \"to\",\"internalType\": \"address\"},{\"type\": \"address\",\"name\": \"primarySaleRecipient\",\"internalType\": \"address\"},{\"type\": \"uint256\",\"name\": \"quantity\",\"internalType\": \"uint256\"},{\"type\": \"uint256\",\"name\": \"price\",\"internalType\": \"uint256\"},{\"type\": \"address\",\"name\": \"currency\",\"internalType\": \"address\"},{\"type\": \"uint128\",\"name\": \"validityStartTimestamp\",\"internalType\": \"uint128\"},{\"type\": \"uint128\",\"name\": \"validityEndTimestamp\",\"internalType\": \"uint128\"},{\"type\": \"bytes32\",\"name\": \"uid\",\"internalType\": \"bytes32\"}],\"internalType\": \"struct ITokenERC20.MintRequest\"},{\"type\": \"bytes\",\"name\": \"_signature\",\"internalType\": \"bytes\"}],\"outputs\": [{\"type\": \"bool\",\"name\": \"\",\"internalType\": \"bool\"},{\"type\": \"address\",\"name\": \"\",\"internalType\": \"address\"}],\"stateMutability\": \"view\"}]" +); var contract = new ThirdwebContract(contractOptions); var readResult = await ThirdwebContract.ReadContract(contract, "name"); - Console.WriteLine($"Contract read result: {readResult}"); -Console.ReadLine(); +var accountOptions = new ThirdwebAccountOptions(client: client, type: WalletType.PrivateKey, privateKey: privateKey); +var account = new ThirdwebAccount(accountOptions); +Console.WriteLine($"Wallet address: {account.GetAddress()}"); + +var message = "Hello, Thirdweb!"; +var signature = account.PersonalSign(message); +Console.WriteLine($"Signed message: {signature}"); + +var balanceBefore = await ThirdwebContract.ReadContract(contract, "balanceOf", account.GetAddress()); +Console.WriteLine($"Balance before mint: {balanceBefore}"); + +var writeResult = await ThirdwebContract.WriteContract(account, contract, "mintTo", account.GetAddress(), 100); +Console.WriteLine($"Contract write result: {writeResult}"); + +var balanceAfter = await ThirdwebContract.ReadContract(contract, "balanceOf", account.GetAddress()); +Console.WriteLine($"Balance after mint: {balanceAfter}"); diff --git a/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs b/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs index 5a3f857..aaa55a0 100644 --- a/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs +++ b/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs @@ -1,4 +1,6 @@ using System.Numerics; +using Nethereum.Hex.HexTypes; +using Nethereum.RPC.Eth.DTOs; namespace Thirdweb { @@ -40,5 +42,39 @@ public static async Task ReadContract(ThirdwebContract contract, string me var resultData = await rpc.SendRequestAsync("eth_call", new { to = contract.Address, data = data, }, "latest"); return function.DecodeTypeOutput(resultData); } + + public static async Task WriteContract(ThirdwebAccount account, ThirdwebContract contract, string method, params object[] parameters) + { + var rpc = ThirdwebRPC.GetRpcInstance(contract.Client, contract.Chain); + + var service = new Nethereum.Contracts.Contract(null, contract.Abi, contract.Address); + var function = service.GetFunction(method); + var data = function.GetData(parameters); + + var transaction = new TransactionInput + { + From = account.GetAddress(), + To = contract.Address, + Data = data, + }; + + // TODO: Implement 1559 + transaction.Gas = new HexBigInteger(await rpc.SendRequestAsync("eth_estimateGas", transaction)); + transaction.GasPrice = new HexBigInteger(await rpc.SendRequestAsync("eth_gasPrice")); + transaction.Nonce = new HexBigInteger(await rpc.SendRequestAsync("eth_getTransactionCount", account.GetAddress(), "latest")); + + string hash; + if (account.Options.Type == WalletType.PrivateKey) + { + var signedTx = account.SignTransaction(transaction, contract.Chain); + Console.WriteLine($"Signed transaction: {signedTx}"); + hash = await rpc.SendRequestAsync("eth_sendRawTransaction", signedTx); + } + else + { + hash = await rpc.SendRequestAsync("eth_sendTransaction", transaction); + } + return hash; + } } } diff --git a/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs b/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs index b1c69e8..674b9ce 100644 --- a/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs +++ b/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs @@ -1,5 +1,10 @@ -using System.Numerics; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Numerics; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json; namespace Thirdweb @@ -112,7 +117,7 @@ private void SendBatchNow() private async Task SendBatchAsync(List batch) { var batchJson = JsonConvert.SerializeObject(batch); - Console.WriteLine($"Sending batch: {batchJson}"); + var requestMessage = new HttpRequestMessage(HttpMethod.Post, _rpcUrl) { Content = new StringContent(batchJson, Encoding.UTF8, "application/json") }; if (!string.IsNullOrEmpty(_clientId)) requestMessage.Headers.Add("x-client-id", _clientId); diff --git a/Thirdweb/Thirdweb.Wallets/IWallet.cs b/Thirdweb/Thirdweb.Wallets/IWallet.cs new file mode 100644 index 0000000..963bf15 --- /dev/null +++ b/Thirdweb/Thirdweb.Wallets/IWallet.cs @@ -0,0 +1,16 @@ +using System.Numerics; +using Nethereum.ABI.EIP712; +using Nethereum.RPC.Eth.DTOs; + +namespace Thirdweb +{ + internal interface IWallet + { + string GetAddress(); + string EthSign(string message); + string PersonalSign(string message); + string SignTypedDataV4(string json); + string SignTypedDataV4(T data, TypedData typedData); + string SignTransaction(TransactionInput transaction, BigInteger chainId); + } +} diff --git a/Thirdweb/Thirdweb.Wallets/Thirdweb.Wallets.EmbeddedWallet/EmbeddedWallet.cs b/Thirdweb/Thirdweb.Wallets/Thirdweb.Wallets.EmbeddedWallet/EmbeddedWallet.cs new file mode 100644 index 0000000..3e1db48 --- /dev/null +++ b/Thirdweb/Thirdweb.Wallets/Thirdweb.Wallets.EmbeddedWallet/EmbeddedWallet.cs @@ -0,0 +1,7 @@ +using Thirdweb; + +internal class EmbeddedWallet : PrivateKeyWallet +{ + internal EmbeddedWallet(string privateKeyHex) + : base(privateKeyHex) { } +} diff --git a/Thirdweb/Thirdweb.Wallets/Thirdweb.Wallets.PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/Thirdweb.Wallets.PrivateKeyWallet/PrivateKeyWallet.cs new file mode 100644 index 0000000..3fa977b --- /dev/null +++ b/Thirdweb/Thirdweb.Wallets/Thirdweb.Wallets.PrivateKeyWallet/PrivateKeyWallet.cs @@ -0,0 +1,131 @@ +using System.Numerics; +using System.Text; +using Nethereum.ABI.EIP712; +using Nethereum.Hex.HexConvertors.Extensions; +using Nethereum.Hex.HexTypes; +using Nethereum.Model; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.RPC.Eth.Mappers; +using Nethereum.Signer; +using Nethereum.Signer.EIP712; +using Thirdweb; + +internal class PrivateKeyWallet : IWallet +{ + private readonly EthECKey _ecKey; + + internal PrivateKeyWallet(string privateKeyHex) + { + if (string.IsNullOrEmpty(privateKeyHex)) + { + throw new ArgumentNullException(nameof(privateKeyHex), "Private key cannot be null or empty."); + } + + _ecKey = new EthECKey(privateKeyHex); + } + + public string GetAddress() + { + return _ecKey.GetPublicAddress(); + } + + public string EthSign(string message) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message), "Message to sign cannot be null."); + } + + var signer = new MessageSigner(); + var signature = signer.Sign(Encoding.UTF8.GetBytes(message), _ecKey); + return signature; + } + + public string PersonalSign(string message) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message), "Message to sign cannot be null."); + } + + var signer = new EthereumMessageSigner(); + var signature = signer.EncodeUTF8AndSign(message, _ecKey); + return signature; + } + + public string SignTypedDataV4(string json) + { + if (json == null) + { + throw new ArgumentNullException(nameof(json), "Json to sign cannot be null."); + } + + var signer = new Eip712TypedDataSigner(); + var signature = signer.SignTypedDataV4(json, _ecKey); + return signature; + } + + public string SignTypedDataV4(T data, TypedData typedData) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data), "Data to sign cannot be null."); + } + + var signer = new Eip712TypedDataSigner(); + var signature = signer.SignTypedDataV4(data, typedData, _ecKey); + return signature; + } + + public string SignTransaction(TransactionInput transaction, BigInteger chainId) + { + if (transaction == null) + { + throw new ArgumentNullException(nameof(transaction)); + } + + if (string.IsNullOrWhiteSpace(transaction.From)) + { + transaction.From = GetAddress(); + } + else if (transaction.From != GetAddress()) + { + throw new Exception("Transaction 'From' address does not match the wallet address"); + } + + var nonce = transaction.Nonce ?? throw new ArgumentNullException(nameof(transaction), "Transaction nonce has not been set"); + + var gasLimit = transaction.Gas; + var value = transaction.Value ?? new HexBigInteger(0); + + string signedTransaction; + if (transaction.Type != null && transaction.Type.Value == TransactionType.EIP1559.AsByte()) + { + var maxPriorityFeePerGas = transaction.MaxPriorityFeePerGas.Value; + var maxFeePerGas = transaction.MaxFeePerGas.Value; + var transaction1559 = new Transaction1559( + chainId, + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + transaction.To, + value, + transaction.Data, + transaction.AccessList.ToSignerAccessListItemArray() + ); + + var signer = new Transaction1559Signer(); + signer.SignTransaction(_ecKey, transaction1559); + signedTransaction = transaction1559.GetRLPEncoded().ToHex(); + } + else + { + var gasPrice = transaction.GasPrice; + var legacySigner = new LegacyTransactionSigner(); + signedTransaction = legacySigner.SignTransaction(_ecKey.GetPrivateKey(), chainId, transaction.To, value.Value, nonce, gasPrice.Value, gasLimit.Value, transaction.Data); + } + + return "0x" + signedTransaction; + } +} diff --git a/Thirdweb/Thirdweb.Wallets/ThirdwebAccount.cs b/Thirdweb/Thirdweb.Wallets/ThirdwebAccount.cs new file mode 100644 index 0000000..ce549a7 --- /dev/null +++ b/Thirdweb/Thirdweb.Wallets/ThirdwebAccount.cs @@ -0,0 +1,64 @@ +using System.Numerics; +using Nethereum.ABI.EIP712; +using Nethereum.RPC.Eth.DTOs; + +namespace Thirdweb +{ + public class ThirdwebAccount + { + internal ThirdwebAccountOptions Options { get; private set; } + + private IWallet _wallet; + + public ThirdwebAccount(ThirdwebAccountOptions options) + { + if (options.Client == null) + { + throw new ArgumentException("Client must be provided"); + } + + Options = options; + + InitializeWallet(); + } + + private void InitializeWallet() + { + _wallet = Options.Type switch + { + WalletType.PrivateKey => new PrivateKeyWallet(Options.PrivateKey), + _ => throw new ArgumentException("Invalid wallet type"), + }; + } + + public string GetAddress() + { + return _wallet.GetAddress(); + } + + public string EthSign(string message) + { + return _wallet.EthSign(message); + } + + public string PersonalSign(string message) + { + return _wallet.PersonalSign(message); + } + + public string SignTypedDataV4(string json) + { + return _wallet.SignTypedDataV4(json); + } + + public string SignTypedDataV4(T data, TypedData typedData) + { + return _wallet.SignTypedDataV4(data, typedData); + } + + public string SignTransaction(TransactionInput transaction, BigInteger chainId) + { + return _wallet.SignTransaction(transaction, chainId); + } + } +} diff --git a/Thirdweb/Thirdweb.Wallets/ThirdwebAccountOptions.cs b/Thirdweb/Thirdweb.Wallets/ThirdwebAccountOptions.cs new file mode 100644 index 0000000..5ace94a --- /dev/null +++ b/Thirdweb/Thirdweb.Wallets/ThirdwebAccountOptions.cs @@ -0,0 +1,18 @@ +using System.Numerics; + +namespace Thirdweb +{ + public class ThirdwebAccountOptions + { + internal ThirdwebClient Client { get; private set; } + internal WalletType Type { get; private set; } + internal string PrivateKey { get; private set; } + + public ThirdwebAccountOptions(ThirdwebClient client, WalletType type, string privateKey) + { + Client = client; + Type = type; + PrivateKey = privateKey; + } + } +} diff --git a/Thirdweb/Thirdweb.Wallets/WalletType.cs b/Thirdweb/Thirdweb.Wallets/WalletType.cs new file mode 100644 index 0000000..b84aabb --- /dev/null +++ b/Thirdweb/Thirdweb.Wallets/WalletType.cs @@ -0,0 +1,8 @@ +namespace Thirdweb +{ + public enum WalletType + { + PrivateKey, + Embedded, + } +} diff --git a/Thirdweb/Thirdweb.csproj b/Thirdweb/Thirdweb.csproj index 61acf63..cdb9025 100644 --- a/Thirdweb/Thirdweb.csproj +++ b/Thirdweb/Thirdweb.csproj @@ -30,6 +30,9 @@ + + +