Ame Component is an onchain component based on EIP-7654 that decomposes complex onchain processes into independent, composable, reusable, and highly adaptable functional units.
getMethods
Get method names based on request method type.getMethodReqAndRes
Get the data types of request parameters and return parameters based on the requested method name.getMethodInstruction
Get the instruction of method.get
Request the contract to retrieve records.post
Request the contract to create a new record.put
Request the contract to update a record.options
Supported request method types.
This is an example of profiles component.
- Users can create new profiles by calling the
createUser
method of post. - Users can query profiles by calling the
getUser
method of get. - Users can change the name by calling the
updateUserName
method of put.
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.0;
import "./Types.sol";
import "./IComponent.sol";
contract Component is IComponent {
//@dev Types contains all data types in solidity
mapping(string => Types.Type[]) methodRequests;
mapping(string => Types.Type[]) methodResponses;
mapping(MethodTypes => string[]) methods;
mapping(string => string) instructions;
//@dev define the data type of this component
struct Profiles {
string name;
uint256 age;
}
mapping(address => Profiles) users;
constructor() {
Types.Type[] memory getReqArray = new Types.Type[](1);
getReqArray[0] = Types.Type.ADDRESS;
Types.Type[] memory dataTypeArray = new Types.Type[](2);
dataTypeArray[0] = Types.Type.STRING;
dataTypeArray[1] = Types.Type.UINT256;
Types.Type[] memory putReqArray = new Types.Type[](2);
putReqArray[0] = Types.Type.ADDRESS;
putReqArray[1] = Types.Type.STRING;
// @dev initialize get, post, put request parameter data types and response data types
setMethod(
"getUser",
MethodTypes.GET,
getReqArray,
dataTypeArray,
"get user profiles"
);
setMethod(
"createUser",
MethodTypes.POST,
dataTypeArray,
dataTypeArray,
"Create user profiles"
);
setMethod(
"updateUserName",
MethodTypes.PUT,
putReqArray,
new Types.Type[](0),
"Update user information"
);
}
function get(string memory _methodName, bytes memory _methodReq)
public
view
returns (bytes memory)
{
if (compareStrings(_methodName, "getUser")) {
address user = abi.decode(_methodReq, (address));
bytes memory userData = abi.encode(
users[user].name,
users[user].age
);
return userData;
} else {
return abi.encode("");
}
}
function post(string memory _methodName, bytes memory _methodReq)
public
payable
returns (bytes memory)
{
if (compareStrings(_methodName, "createUser")) {
(string memory name, uint256 age) = abi.decode(
_methodReq,
(string, uint256)
);
users[msg.sender] = Profiles(name, age);
bytes memory resBytes = abi.encode(name, age);
emit Response(resBytes);
return resBytes;
}
return abi.encode("");
}
function put(string memory _methodName, bytes memory _methodReq)
public
payable
returns (bytes memory)
{
if (compareStrings(_methodName, "updateUserName")) {
(address userAddress, string memory name) = abi.decode(
_methodReq,
(address, string)
);
require(userAddress == msg.sender);
users[userAddress].name = name;
}
return abi.encode("");
}
function options() public pure returns (MethodTypes[] memory) {
MethodTypes[] memory methodTypes = new MethodTypes[](3);
methodTypes[0] = MethodTypes.GET;
methodTypes[1] = MethodTypes.POST;
methodTypes[2] = MethodTypes.PUT;
return methodTypes;
}
function setMethod(
string memory _methodName,
MethodTypes _methodType,
Types.Type[] memory _methodReq,
Types.Type[] memory _methodRes,
string memory _instruction
) private {
methods[_methodType].push(_methodName);
methodRequests[_methodName] = _methodReq;
methodResponses[_methodName] = _methodRes;
instructions[_methodName] = _instruction;
}
function getMethodReqAndRes(string memory _methodName)
public
view
returns (Types.Type[] memory, Types.Type[] memory)
{
return (methodRequests[_methodName], methodResponses[_methodName]);
}
function getMethods(MethodTypes _methodTypes)
public
view
returns (string[] memory)
{
return methods[_methodTypes];
}
function getMethodInstruction(string memory _methodName)
public
view
returns (string memory)
{
return instructions[_methodName];
}
//@dev compares two strings for equality
function compareStrings(string memory _a, string memory _b)
private
pure
returns (bool)
{
return
keccak256(abi.encodePacked(_a)) == keccak256(abi.encodePacked(_b));
}
}
How to customize response status code?
The return value type of each request method is bytes, so you can customize the response status code and other response information according to your needs.
How to disable a request method?
You can add a mapping(string=>bool) to save the request method name and available state. After the application is upgraded, manually set the available state of the disabled request method to false. And check the state every time a request is processed.
Why is there no delete method type?
The data in smart contracts is public, and deleting data is an inefficient operation. In order to facilitate data management and retrieval, you can add a mapping to save the valid state of the data, and add a put request method to set valid and invalid data. And only return valid data in get method.
How to dynamically add new request methods?
Before deploying the contract, you can add a mapping (address=>bool) to record the external contract permissions. When the application adds new functions, give the v2 contract write permissions and add new request methods to the v2 contract.
How to encode request parameters and decode response?
Use getMethodReqAndRes
to get the data type of the method request parameters and the data type of the response value. Then use some js library to encode and decode it.
For example:
var reqDataEncode = web3.eth.abi.encodeParameters(
["string", "uint256"],
["alice", "1"]
);
var resDataDecode = web3.eth.abi.decodeParameters(
["string", "uint256"],
reqDataEncode
);