diff --git a/README.md b/README.md index b7092d8..e3d9f07 100644 --- a/README.md +++ b/README.md @@ -84,12 +84,27 @@ contract Example { } ``` -### protocols/ChatGPT.sol +### protocols/EthJsonRPC.sol -Helper library to send completion requests to ChatGPT. +Helper library to interact with the Ethereum JsonRPC protocol. #### Example usage +```solidity +import "suave-std/protocols/EthJsonRPC.sol"; + +contract Example { + function example() { + EthJsonRPC jsonrpc = new EthJsonRPC("http://..."); + jsonrpc.nonce(address(this)); + } +} +``` + +### protocols/ChatGPT.sol + +Helper library to send completion requests to ChatGPT. + ```solidity import "suave-std/protocols/ChatGPT.sol"; diff --git a/lib/forge-std b/lib/forge-std index 4513bc2..3725a22 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 4513bc2063f23c57bee6558799584b518d387a39 +Subproject commit 3725a22ae52065de9966beaf32de69aee46fb530 diff --git a/src/protocols/EthJsonRPC.sol b/src/protocols/EthJsonRPC.sol new file mode 100644 index 0000000..03b70ed --- /dev/null +++ b/src/protocols/EthJsonRPC.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.13; + +import "src/suavelib/Suave.sol"; +import "solady/src/utils/JSONParserLib.sol"; +import "solady/src/utils/LibString.sol"; + +contract EthJsonRPC { + using JSONParserLib for *; + + string endpoint; + + constructor(string memory _endpoint) { + endpoint = _endpoint; + } + + function nonce(address addr) public returns (uint256) { + bytes memory body = abi.encodePacked( + '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["', + LibString.toHexStringChecksummed(addr), + '","latest"],"id":1}' + ); + + JSONParserLib.Item memory item = doRequest(string(body)); + uint256 val = JSONParserLib.parseUintFromHex(trimQuotes(item.value())); + return val; + } + + function doRequest(string memory body) public returns (JSONParserLib.Item memory) { + Suave.HttpRequest memory request; + request.method = "POST"; + request.url = endpoint; + request.headers = new string[](1); + request.headers[0] = "Content-Type: application/json"; + request.body = bytes(body); + + bytes memory output = Suave.doHTTPRequest(request); + + JSONParserLib.Item memory item = string(output).parse(); + return item.at('"result"'); + } + + function trimQuotes(string memory input) private pure returns (string memory) { + bytes memory inputBytes = bytes(input); + require( + inputBytes.length >= 2 && inputBytes[0] == '"' && inputBytes[inputBytes.length - 1] == '"', "Invalid input" + ); + + bytes memory result = new bytes(inputBytes.length - 2); + + for (uint256 i = 1; i < inputBytes.length - 1; i++) { + result[i - 1] = inputBytes[i]; + } + + return string(result); + } +} diff --git a/test/protocols/EthJsonRPC.t.sol b/test/protocols/EthJsonRPC.t.sol new file mode 100644 index 0000000..47ff4a8 --- /dev/null +++ b/test/protocols/EthJsonRPC.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "src/Test.sol"; +import "solady/src/utils/LibString.sol"; +import "src/protocols/EthJsonRPC.sol"; + +contract EthJsonRPCTest is Test, SuaveEnabled { + function testEthJsonRPCGetNonce() public { + EthJsonRPC ethjsonrpc = getEthJsonRPC(); + + uint256 nonce = ethjsonrpc.nonce(address(this)); + assertEq(nonce, 0); + } + + function getEthJsonRPC() public returns (EthJsonRPC ethjsonrpc) { + try vm.envString("JSONRPC_ENDPOINT") returns (string memory endpoint) { + ethjsonrpc = new EthJsonRPC(endpoint); + } catch { + vm.skip(true); + } + } +}