diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index ef6e04d..6e812fa --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,10 @@ node-spec.json node-spec.csv yarn-error.log # hardhat things +typechain-types cache artifacts -typechain-types .env + +**/*/.yalc +**/*/yalc.lock \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js old mode 100644 new mode 100755 diff --git a/.vscode/launch.json b/.vscode/launch.json old mode 100644 new mode 100755 diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100644 new mode 100755 index 8a3d3d8..dca3dff --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,4 +2,4 @@ "prettier.configPath": ".prettierrc.js", "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true -} \ No newline at end of file +} diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 93e7c51..54b6551 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ -# interX +# blocktopia Turn your static 3d spaces into on-chain interactive, and gamified experiences. This gives world builders a node-based interface and protocol to create an interoperable behavior graph for a virtual world. ![ClickToAnimateWithButton](https://user-images.githubusercontent.com/891755/202081868-2c602aee-cabd-49cd-8b81-459071e17749.gif) - This respository import's Ben Houston's [behave-graph](https://github.com/bhouston/behave-graph) lib, and incorporates code from [behave-flow](https://github.com/beeglebug/behave-flow) for the user interface. It takes the existing functionality, and adds the following improvements: - adds a side by side view of the behave-flow editor, with the nodes on the left, and scene on the right, with changes propagating in real-time to the scene on the right diff --git a/babel.config.js b/babel.config.js old mode 100644 new mode 100755 index b37dc6a..b4166ac --- a/babel.config.js +++ b/babel.config.js @@ -9,7 +9,12 @@ module.exports = { }, }, ], - '@babel/preset-react', + [ + '@babel/preset-react', + { + runtime: 'automatic', + }, + ], '@babel/preset-typescript', ], }; diff --git a/contracts/BehaviorGraph.sol b/contracts/BehaviorGraph.sol old mode 100644 new mode 100755 index 4b1ec68..281c092 --- a/contracts/BehaviorGraph.sol +++ b/contracts/BehaviorGraph.sol @@ -1,126 +1,236 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; -import '@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol'; -import '@openzeppelin/contracts/access/Ownable.sol'; -import '@openzeppelin/contracts/utils/Counters.sol'; +import 'hardhat/console.sol'; + +import './Interfaces.sol'; +import './Nodes.sol'; +import './NodeState.sol'; enum NodeType { - Action, - DataSource + ExternalInvoke, + Counter, + Add, + Gate, + VariableSet } -struct TokenGateRule { - bool active; - address tokenContract; +struct NodeDefinition { + string id; + NodeType nodeType; + bool defined; + // will only be set if this is a variable + ValueType inputValueType; } - -struct Node { - string id; - NodeType nodeType; - TokenGateRule tokenGateRule; +struct NodeConfig { + uint8 variableId; + uint8 invocationId; + bool invocationNameDefined; + bool variableIdDefined; } -contract BehaviorGraph is ERC721, ERC721URIStorage, Ownable { - using Counters for Counters.Counter; - - mapping(uint256 => mapping(string => Node)) private _tokenNodes; - mapping(uint256 => mapping(string => uint256)) private _tokenNodeEmitCount; - - Counters.Counter private _nodeCounter; - - Counters.Counter private _tokenIdCounter; +struct NodeDefinitionAndValues { + NodeDefinition definition; + InitialValues initialValues; + NodeConfig config; +} - event SafeMint(uint256 tokenId, address to, string uri, Node[] nodes); +struct EdgeDefinition { + string fromNode; + string toNode; + uint8 fromSocket; + uint8 toSocket; +} - error InvalidActionId(string nodeId); - error MissingTokens(string nodeId, address tokenAddress); +struct EdgeToNode { + uint16 toNode; + uint8 toSocket; + bool set; +} - event ActionExecuted(address executor, uint256 tokenId, string actionId, uint256 count); +contract BehaviorGraph is NodeState, HasVariables, IBehaviorGraph { + uint256 private _id; + mapping(string => uint16) private _nodeIndeces; + // edges between nodes, indexed by token id, node index, and socket index + mapping(uint16 => mapping(uint8 => EdgeToNode)) private _tokenEdges; + + // node node definition, mapped by node index and token id + mapping(uint16 => NodeType) private _nodeTypes; + mapping(uint16 => ValueType) private _inputValueTypes; + mapping(uint16 => uint8) private _nodeVariableIds; + mapping(uint8 => uint16) private _invocationNodes; + + error InvalidActionId(uint16 nodeId); + error CannotTriggerExternally(uint16 nodeId); + + SocketIndecesByNodeType private _socketIndecesByNodeType; + + constructor( + uint256 id, + NodeDefinitionAndValues[] memory _nodes, + EdgeDefinition[] memory _edges, + SocketIndecesByNodeType memory socketIndecesByNodeType + ) { + _socketIndecesByNodeType = socketIndecesByNodeType; + _id = id; + + // for each node definition and values, create a node and set the initial values + for (uint16 nodeIndex = 0; nodeIndex < _nodes.length; nodeIndex++) { + NodeDefinitionAndValues memory nodeAndValues = _nodes[nodeIndex]; + NodeDefinition memory node = nodeAndValues.definition; + NodeType nodeType = node.nodeType; + NodeConfig memory nodeConfig = nodeAndValues.config; + + _nodeIndeces[node.id] = nodeIndex; + _nodeTypes[nodeIndex] = nodeType; + _inputValueTypes[nodeIndex] = node.inputValueType; + if (nodeConfig.variableIdDefined) _nodeVariableIds[nodeIndex] = nodeConfig.variableId; + if (nodeConfig.invocationNameDefined) _invocationNodes[nodeConfig.invocationId] = nodeIndex; + + _setInitialValues(nodeIndex, nodeAndValues.initialValues); + + // store the indeces for the sockets, so that they can be mapped by int later. + // _setInputOutputNodeSocketIndeces(nodeType, node.inputSockets, node.outputSockets); + } + for (uint16 i = 0; i < _edges.length; i++) { + EdgeDefinition memory edge = _edges[i]; - constructor() ERC721("MyToken", "MTK") {} + uint16 fromNode = _getNodeIndex(edge.fromNode); + uint16 toNode = _getNodeIndex(edge.toNode); + uint8 fromSocket = edge.fromSocket; - function _baseURI() internal pure override returns (string memory) { - return "ipfs://"; - } + // get the to node type + uint8 toSocket = edge.toSocket; - function safeMint(string memory sceneUri, Node[] calldata _nodes) public returns(uint256) { - uint256 tokenId = _tokenIdCounter.current(); - _tokenIdCounter.increment(); - address to = msg.sender; - _safeMint(to, tokenId); - _setTokenURI(tokenId, sceneUri); - _createNodes(tokenId, _nodes); - emit SafeMint(tokenId, to, sceneUri, _nodes); - - return tokenId; + _tokenEdges[fromNode][fromSocket] = EdgeToNode(toNode, toSocket, true); } - - // The following functions are overrides required by Solidity. - function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { - super._burn(tokenId); + } + + function _getNodeIndex(string memory nodeId) private view returns (uint16) { + return _nodeIndeces[nodeId]; + } + + function _getNodeType(uint16 nodeIndex) private view returns (NodeType) { + return _nodeTypes[nodeIndex]; + } + + function getNodeStateVal(uint16 _nodeId, string memory _stateVar) external view returns (int256) { + return _getNodeStateVal(_nodeId, _stateVar); + } + + function setNodeIntStateVal(uint16 _nodeId, string memory _stateVar, int256 val) external { + _setNodeIntStateVal(_nodeId, _stateVar, val); + } + + function writeToOutput(uint16 _nodeId, uint8 _socketId, int256 val) external { + _writeToIntOutput(_nodeId, _socketId, val); + } + + function getBoolInputVal(uint16 _nodeId, uint8 _socketName) external view returns (bool) { + return _getBoolInputVal(_nodeId, _socketName); + } + + function getInputValueType(uint16 _nodeId) external view returns (ValueType) { + return _inputValueTypes[_nodeId]; + } + + function getStringInputVal(uint16 _nodeId, uint8 _socketName) external view returns (string memory) { + return _getStringInputVal(_nodeId, _socketName); + } + + function getIntInputVal(uint16 _nodeId, uint8 _socketName) external view returns (int256) { + return _getIntInputVal(_nodeId, _socketName); + } + + function setVariable(uint8 variableId, int256 val) external { + _setVariable(variableId, val); + } + + function setVariable(uint8 variableId, bool val) external { + _setVariable(variableId, val); + } + + // function getSocketNames() public pure returns(SocketNames memory) { + // return SocketNames(IN_OUT_SOCKET_A, IN_OUT_SOCKET_B, IN_OUT_SOCKET_RESULT, FLOW_SOCKET_NAME, GATE_TRUE_SOCKET_NAME, GATE_FALSE_SOCKET_NAME, VARIABLE_NAME_SOCKET); + // } + + function _getEdge(uint16 _nodeId, uint8 _socketIndex) private view returns (EdgeToNode memory) { + EdgeToNode memory edge = _tokenEdges[_nodeId][_socketIndex]; + return edge; + } + + function triggerEdge(uint16 _nodeId, uint8 _socketIndex) external override returns (GraphUpdate[] memory) { + return _triggerEdge(_nodeId, _socketIndex); + } + + function _triggerEdge(uint16 _nodeId, uint8 _socketIndex) private returns (GraphUpdate[] memory) { + EdgeToNode memory edge = _getEdge(_nodeId, _socketIndex); + // console.log("triggering edge to node: %i %i %b", edge.toNode, edge.toSocket, edge.set); + if (edge.set) { + return _triggerNode(edge.toNode, edge.toSocket); } - function tokenURI(uint256 tokenId) - public - view - override(ERC721, ERC721URIStorage) - returns (string memory) - { - return super.tokenURI(tokenId); - } - - function _createNodes(uint256 tokenId, Node[] calldata _nodes) private { - for(uint256 i = 0; i < _nodes.length; i++) { - Node calldata node = _nodes[i]; - _tokenNodes[tokenId][node.id] = node; - } - } + return new GraphUpdate[](0); + } - function getNode(uint256 tokenId, string memory _nodeId) public view returns(Node memory) { - return _tokenNodes[tokenId][_nodeId]; - } + function _writeToIntOutput(uint16 _nodeId, uint8 _socketId, int256 val) private { + // get the edge to the next node + EdgeToNode memory edge = _getEdge(_nodeId, _socketId); - function executeAction(uint256 tokenId, string calldata _nodeId) public { - Node memory node = getNode(tokenId, _nodeId); + // if the edge exists + if (edge.set) { + // write the node value to the input socket + _setInputVal(edge.toNode, edge.toSocket, val); - _assertCanExecuteAction(node); - - uint256 actionCount = ++_tokenNodeEmitCount[tokenId][_nodeId]; - - emit ActionExecuted(msg.sender, tokenId, _nodeId, actionCount); + // if is an immediate node, exec it + _exec(edge.toNode); } + } - function getActionCount(uint256 tokenId, string calldata _nodeId) public view returns(uint256) { - // uint256 numberElems = _nodeIds.length; - return _tokenNodeEmitCount[tokenId][_nodeId]; + function _exec(uint16 _nodeId) private { + NodeType nodeType = _getNodeType(_nodeId); + if (nodeType == NodeType.Add) { + // get the value from input a and input b + (new Add(_socketIndecesByNodeType.add)).execute(this, _nodeId); } - - function getActionCounts(uint256 tokenId, string[] calldata _nodeIds) public view returns(uint256[] memory) { - // uint256 numberElems = _nodeIds.length; - uint256[] memory result = new uint256[](_nodeIds.length); - - for(uint256 i = 0; i < _nodeIds.length; i++) { - string memory _nodeId = _nodeIds[i]; - uint256 count = _tokenNodeEmitCount[tokenId][_nodeId]; - result[i] = count; - } - return result; + } + + function _isImmediateNode(uint16 _nodeId) private view returns (bool) { + NodeType nodeType = _getNodeType(_nodeId); + return nodeType == NodeType.Add; + } + + function _triggerNode(uint16 _nodeId, uint8 _triggeringSocketIndex) internal returns (GraphUpdate[] memory) { + // get the node type + NodeType nodeType = _getNodeType(_nodeId); + + ITriggerNode triggerNode; + + if (nodeType == NodeType.Counter) { + triggerNode = new Counter(_socketIndecesByNodeType.counter); + } else if (nodeType == NodeType.Gate) { + triggerNode = new Gate(_socketIndecesByNodeType.gate); + } else if (nodeType == NodeType.VariableSet) { + uint8 variableId = _nodeVariableIds[_nodeId]; + triggerNode = new VariableSet(_socketIndecesByNodeType.variableSet, variableId); + } else { + revert InvalidActionId(_nodeId); } - function _assertCanExecuteAction(Node memory node) private view { - if (!node.tokenGateRule.active) { - return; - } + return triggerNode.trigger(this, _nodeId, _triggeringSocketIndex); + } - ERC721 erc721Contract = ERC721(node.tokenGateRule.tokenContract); + function invoke(uint8 _invocationId) public returns (GraphUpdate[] memory) { + uint16 _nodeIndex = _invocationNodes[_invocationId]; + NodeType _nodeType = _getNodeType(_nodeIndex); - uint256 balance = erc721Contract.balanceOf(msg.sender); - - if (balance <= 0) { - revert MissingTokens(node.id, msg.sender); - } + // console.log("node id %s %i %i ",_nodeId, _nodeIndex, uint8(_nodeType)); + if (_nodeType != NodeType.ExternalInvoke) { + revert CannotTriggerExternally(_nodeIndex); } + + // todo: rethink + return (new ExternalInvoke(_socketIndecesByNodeType.externalInvoke)).trigger(this, _nodeIndex, 0); + } } diff --git a/contracts/BehaviorGraphToken.sol b/contracts/BehaviorGraphToken.sol new file mode 100755 index 0000000..579518c --- /dev/null +++ b/contracts/BehaviorGraphToken.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import 'hardhat/console.sol'; + +import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; +import '@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol'; +import '@openzeppelin/contracts/access/Ownable.sol'; +import '@openzeppelin/contracts/utils/Counters.sol'; + +import './BehaviorGraph.sol'; + +contract BehaviorGraphToken is ERC721, ERC721URIStorage, Ownable, SocketsIndexedByName { + using Counters for Counters.Counter; + + Counters.Counter private _tokenIdCounter; + + mapping(uint256 => BehaviorGraph) private _behaviorGraphs; + + event SafeMint(uint256 tokenId, address toNode, string uri, NodeDefinitionAndValues[] nodes, EdgeDefinition[] edges); + + constructor() ERC721('MyToken', 'MTK') {} + + function _baseURI() internal pure override returns (string memory) { + return 'ipfs://'; + } + + // The following functions are overrides required by Solidity. + function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { + super._burn(tokenId); + } + + function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { + return super.tokenURI(tokenId); + } + + function safeMint( + string memory sceneUri, + NodeDefinitionAndValues[] calldata _nodes, + EdgeDefinition[] calldata _edges + ) public returns (uint256) { + uint256 tokenId = _tokenIdCounter.current(); + _tokenIdCounter.increment(); + address to = msg.sender; + _safeMint(to, tokenId); + _setTokenURI(tokenId, sceneUri); + // todo - fix overflow with uint16 + BehaviorGraph _behaviorGraph = new BehaviorGraph(tokenId, _nodes, _edges, _socketIndecesByNodeType); + _behaviorGraphs[tokenId] = _behaviorGraph; + + emit SafeMint(tokenId, to, sceneUri, _nodes, _edges); + + return tokenId; + } + + event IntVariableUpdated(address executor, uint256 _tokenId, uint8 _variableId, int256 value); + event BoolVariableUpdated(address executor, uint256 _tokenId, uint8 _variableId, bool value); + + function invoke(uint256 tokenId, uint8 invocationName) public { + BehaviorGraph behaviorGraph = _behaviorGraphs[tokenId]; + GraphUpdate[] memory graphUpdates = behaviorGraph.invoke(invocationName); + + for (uint16 i = 0; i < graphUpdates.length; i++) { + GraphUpdate memory update = graphUpdates[i]; + if (update.updateType == UpdateType.IntVariableUpdated) { + emit IntVariableUpdated(msg.sender, tokenId, update.variableId, update.intValue); + } else if (update.updateType == UpdateType.BoolVariableUpdated) { + emit BoolVariableUpdated(msg.sender, tokenId, update.variableId, update.boolValue); + } + } + } +} diff --git a/contracts/Interfaces.sol b/contracts/Interfaces.sol new file mode 100755 index 0000000..010894c --- /dev/null +++ b/contracts/Interfaces.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +enum ValueType { + Int, + Bool, + NotAVariable +} + +enum UpdateType { + IntVariableUpdated, + BoolVariableUpdated +} + +struct GraphUpdate { + UpdateType updateType; + uint8 variableId; + int256 intValue; + bool boolValue; +} + +interface IBehaviorGraph { + function getNodeStateVal(uint16 _nodeId, string memory _stateVar) external view returns (int256); + + function setNodeIntStateVal(uint16 _nodeId, string memory _stateVar, int256 val) external; + + function writeToOutput(uint16 _nodeId, uint8 _socketId, int256 val) external; + + function triggerEdge(uint16 _nodeId, uint8 _socketIndex) external returns (GraphUpdate[] memory); + + function getBoolInputVal(uint16 _nodeId, uint8 _socketName) external view returns (bool); + + function getInputValueType(uint16 _nodeId) external view returns (ValueType); + + function getStringInputVal(uint16 _nodeId, uint8 _socketName) external view returns (string memory); + + function getIntInputVal(uint16 _nodeId, uint8 _socketName) external view returns (int256); + + // function setVariable(uint8 _variableId, int256 val) external; + + // function setVariable(uint8 _variableId, bool val) external; +} + +interface ITriggerNode { + function trigger( + IBehaviorGraph _behaviorGraph, + uint16 _nodeId, + uint8 _triggeringSocketIndex + ) external returns (GraphUpdate[] memory); +} + +interface IFunctionNode { + function execute(IBehaviorGraph behaviorGraph, uint16 _nodeId) external; +} diff --git a/contracts/NodeState.sol b/contracts/NodeState.sol new file mode 100755 index 0000000..fa93e17 --- /dev/null +++ b/contracts/NodeState.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +// import './IndexedNodes.sol'; + +struct BooleanValueAndLabel { + bool value; + uint8 socket; +} + +struct IntValueAndLabel { + int256 value; + uint8 socket; +} + +struct StringValueAndLabel { + string value; + uint8 socket; +} + +struct InitialValues { + BooleanValueAndLabel[] booleans; + IntValueAndLabel[] integers; + StringValueAndLabel[] strings; +} + +struct IntValue { + int256 value; + bool set; +} + +struct BoolValue { + bool value; + bool set; +} + +struct StringValue { + string value; + bool set; +} + +contract NodeState { + mapping(uint16 => mapping(uint8 => IntValue)) private _nodeInputIntVals; + mapping(uint16 => mapping(uint8 => StringValue)) private _nodeInputStringVals; + mapping(uint16 => mapping(uint8 => BoolValue)) private _nodeBoolInputVals; + + mapping(uint16 => mapping(string => IntValue)) private _nodeIntStateVals; + + constructor() {} + + function _getIntInputVal(uint16 _nodeId, uint8 _socketName) internal view returns (int256) { + // uint16 val = uint16(_nodeInputIntVals[_nodeId][_socketName]); + // console.log("get int input val %s %s: %i",_nodeId, _socketName, val); + + IntValue memory val = _nodeInputIntVals[_nodeId][_socketName]; + + if (val.set) return val.value; + return 0; + } + + function _setInputVal(uint16 _nodeId, uint8 _socketName, int256 val) internal { + // console.log("set int input val %s %s: %i", _nodeId, _socketName, uint16(val)); + _nodeInputIntVals[_nodeId][_socketName] = IntValue(val, true); + } + + function _getBoolInputVal(uint16 _nodeId, uint8 _socketName) internal view returns (bool) { + BoolValue memory val = _nodeBoolInputVals[_nodeId][_socketName]; + + if (val.set) return val.value; + return false; + } + + function _setInputVal(uint16 _nodeId, uint8 _socketName, bool val) internal { + _nodeBoolInputVals[_nodeId][_socketName] = BoolValue(val, true); + } + + function _getStringInputVal(uint16 _nodeId, uint8 _socketName) internal view returns (string memory) { + StringValue memory val = _nodeInputStringVals[_nodeId][_socketName]; + + if (val.set) return val.value; + + return ''; + } + + function _setInputVal(uint16 _nodeId, uint8 _socketName, string memory val) internal { + _nodeInputStringVals[_nodeId][_socketName] = StringValue(val, true); + } + + function _setNodeIntStateVal(uint16 _nodeId, string memory _stateVar, int256 val) internal { + _nodeIntStateVals[_nodeId][_stateVar] = IntValue(val, true); + } + + function _getNodeStateVal(uint16 _nodeId, string memory _stateVar) internal view returns (int256) { + IntValue memory val = _nodeIntStateVals[_nodeId][_stateVar]; + + if (val.set) return val.value; + return 0; + } + + function _setInitialValues(uint16 _nodeId, InitialValues memory _initialValues) internal { + // set initial boolean values + for (uint16 j = 0; j < _initialValues.booleans.length; j++) { + BooleanValueAndLabel memory boolVal = _initialValues.booleans[j]; + _setInputVal(_nodeId, boolVal.socket, boolVal.value); + } + + // set initial int values + for (uint16 j = 0; j < _initialValues.integers.length; j++) { + IntValueAndLabel memory intVal = _initialValues.integers[j]; + _setInputVal(_nodeId, intVal.socket, intVal.value); + } + + // set initial string values + for (uint16 j = 0; j < _initialValues.strings.length; j++) { + StringValueAndLabel memory stringVal = _initialValues.strings[j]; + _setInputVal(_nodeId, stringVal.socket, stringVal.value); + } + } +} + +struct VariableIdAndSet { + bool set; + uint8 id; +} + +contract HasVariables { + event IntVariableUpdated(address executor, uint8 _variableId, int256 value); + event BoolVariableUpdated(address executor, uint8 _variableId, bool value); + + constructor() {} + + function _setVariable(uint8 _variableId, int256 val) internal { + // uint8 _variableId = _getOrSetVariable(tokenId, _variableName); + // _intVarVals[_variableId] = val; + emit IntVariableUpdated(msg.sender, _variableId, val); + } + + function _setVariable(uint8 _variableId, bool val) internal { + // uint8 _variableId = _getOrSetVariable(tokenId, _variableName); + // _boolVarVals[_variableId] = val; + emit BoolVariableUpdated(msg.sender, _variableId, val); + } +} diff --git a/contracts/Nodes.sol b/contracts/Nodes.sol new file mode 100755 index 0000000..ed2da35 --- /dev/null +++ b/contracts/Nodes.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import './Interfaces.sol'; + +contract Node { + // IBehaviorGraph internal _behaviorGraph; + // constructor(IBehaviorGraph behaviorGraph) { + // _behaviorGraph = behaviorGraph; + // } +} + +contract Counter is Node, ITriggerNode { + uint256 public count; + + CounterSocketIndeces private _socketIndeces; + + constructor(CounterSocketIndeces memory socketIndeces) { + _socketIndeces = socketIndeces; + } + + function trigger( + IBehaviorGraph _behaviorGraph, + uint16 _nodeId, + uint8 _triggeringSocketIndex + ) external returns (GraphUpdate[] memory) { + // update state to increment counter + // this is internal, so we dont need to store it in constant + int256 newStateVal = _behaviorGraph.getNodeStateVal(_nodeId, 'count') + 1; + _behaviorGraph.setNodeIntStateVal(_nodeId, 'count', newStateVal); + + // write the count to the output + _behaviorGraph.writeToOutput(_nodeId, _socketIndeces.outputCount, newStateVal); + // trigger the flow edge + return _behaviorGraph.triggerEdge(_nodeId, _socketIndeces.outputFlow); + } +} + +contract Gate is Node, ITriggerNode { + GateSocketIndeces private _socketIndeces; + + constructor(GateSocketIndeces memory socketIndeces) { + _socketIndeces = socketIndeces; + } + + function trigger( + IBehaviorGraph _behaviorGraph, + uint16 _nodeId, + uint8 _triggeringSocketIndex + ) external returns (GraphUpdate[] memory) { + // get the socket to trigger + uint8 toTrigger = _behaviorGraph.getBoolInputVal(_nodeId, _triggeringSocketIndex) + ? _socketIndeces.outputGateTrue + : _socketIndeces.outputGateFalse; + // trigger the flow edge along that socket + return _behaviorGraph.triggerEdge(_nodeId, toTrigger); + } +} + +contract VariableSet is Node, ITriggerNode { + VariableSetIndeces private _socketIndeces; + + uint8 private _variableId; + + constructor(VariableSetIndeces memory socketIndeces, uint8 variableId) { + _socketIndeces = socketIndeces; + _variableId = variableId; + } + + function trigger( + IBehaviorGraph _behaviorGraph, + uint16 _nodeId, + uint8 _triggeringSocketIndex + ) external returns (GraphUpdate[] memory) { + // determine what type of value is stored + ValueType _inputValueType = _behaviorGraph.getInputValueType(_nodeId); + + GraphUpdate[] memory updates = new GraphUpdate[](1); + + updates[0].variableId = _variableId; + + // if it is an int variable + if (_inputValueType == ValueType.Int) { + updates[0].updateType = UpdateType.IntVariableUpdated; + updates[0].intValue = _behaviorGraph.getIntInputVal(_nodeId, _socketIndeces.inputVal); + } else { + updates[0].updateType = UpdateType.BoolVariableUpdated; + updates[0].boolValue = _behaviorGraph.getBoolInputVal(_nodeId, _socketIndeces.inputVal); + } + + return updates; + } +} + +contract ExternalInvoke is Node, ITriggerNode { + ExternalInvokeIndeces private _socketIndeces; + + constructor(ExternalInvokeIndeces memory socketIndeces) { + _socketIndeces = socketIndeces; + } + + function trigger( + IBehaviorGraph _behaviorGraph, + uint16 _nodeId, + uint8 _triggeringSocketIndex + ) external returns (GraphUpdate[] memory) { + return _behaviorGraph.triggerEdge(_nodeId, _socketIndeces.outputFlowSocket); + } +} + +contract Add is Node, IFunctionNode { + Int2Out1SocketIndeces private _socketIndeces; + + constructor(Int2Out1SocketIndeces memory socketIndeces) { + _socketIndeces = socketIndeces; + } + + function execute(IBehaviorGraph _behaviorGraph, uint16 _nodeId) external { + int256 val = _behaviorGraph.getIntInputVal(_nodeId, _socketIndeces.input1) + + _behaviorGraph.getIntInputVal(_nodeId, _socketIndeces.input2); + + _behaviorGraph.writeToOutput(_nodeId, _socketIndeces.result, val); + } +} + +struct ExternalInvokeIndeces { + uint8 outputFlowSocket; +} + +struct Int2Out1SocketIndeces { + uint8 input1; + uint8 input2; + uint8 result; +} + +struct CounterSocketIndeces { + uint8 inputFlow; + uint8 outputCount; + uint8 outputFlow; +} + +struct GateSocketIndeces { + uint8 inputFlow; + uint8 outputGateTrue; + uint8 outputGateFalse; +} + +struct VariableSetIndeces { + uint8 inputFlow; + uint8 inputVal; +} + +struct SocketIndecesByNodeType { + ExternalInvokeIndeces externalInvoke; + CounterSocketIndeces counter; + Int2Out1SocketIndeces add; + VariableSetIndeces variableSet; + GateSocketIndeces gate; +} + +contract SocketsIndexedByName { + SocketIndecesByNodeType internal _socketIndecesByNodeType; + + constructor() { + // initialize socket indeces by node type with incremeting + // and unique values + _socketIndecesByNodeType = SocketIndecesByNodeType({ + externalInvoke: ExternalInvokeIndeces({ outputFlowSocket: 0 }), + counter: CounterSocketIndeces({ inputFlow: 0, outputCount: 1, outputFlow: 2 }), + add: Int2Out1SocketIndeces({ input1: 0, input2: 1, result: 2 }), + variableSet: VariableSetIndeces({ inputFlow: 0, inputVal: 1 }), + gate: GateSocketIndeces({ inputFlow: 0, outputGateTrue: 1, outputGateFalse: 2 }) + }); + } + + function getSocketIndecesByNodeType() public view returns (SocketIndecesByNodeType memory) { + return _socketIndecesByNodeType; + } +} diff --git a/contracts/Token.sol b/contracts/Token.sol old mode 100644 new mode 100755 diff --git a/editor/.eslintignore b/editor/.eslintignore old mode 100644 new mode 100755 diff --git a/editor/.eslintrc.json b/editor/.eslintrc.json old mode 100644 new mode 100755 diff --git a/editor/.gitignore b/editor/.gitignore old mode 100644 new mode 100755 diff --git a/editor/index.html b/editor/index.html old mode 100644 new mode 100755 diff --git a/editor/package.json b/editor/package.json old mode 100644 new mode 100755 index a472227..f1e9942 --- a/editor/package.json +++ b/editor/package.json @@ -1,5 +1,5 @@ { - "name": "@behavior-graph/editor", + "name": "@blocktopia/editor", "private": true, "version": "0.0.1", "type": "module", @@ -9,18 +9,21 @@ "preview": "vite preview" }, "dependencies": { - "@behave-graph/core": "^0.9.11", + "@blocktopia/core": "*", "@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^1.7.4", "@heroicons/react": "^2.0.13", - "@rainbow-me/rainbowkit": "^0.7.1", + "@behave-graph/core": "^0.0.1", + "@behave-graph/flow": "^1.5.0", + "@rainbow-me/rainbowkit": "^0.8.1", "@react-three/drei": "^9.40.0", "@react-three/fiber": "^8.9.1", - "behave-graph": "^0.9.10", + "abitype": "^0.2.5", "classnames": "^2.3.2", "clsx": "^1.2.1", + "ethers": "^5.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", @@ -32,7 +35,7 @@ "three": "^0.146.0", "use-why-did-you-update": "^0.1.0", "uuid": "^9.0.0", - "wagmi": "^0.7.12", + "wagmi": "^0.8.10", "web3.storage": "^4.4.0" }, "devDependencies": { diff --git a/editor/postcss.config.cjs b/editor/postcss.config.cjs old mode 100644 new mode 100755 diff --git a/editor/public/examples/graphs/ClickButtonToAnimate.json b/editor/public/examples/graphs/ClickButtonToAnimate.json old mode 100644 new mode 100755 diff --git a/editor/public/examples/graphs/ClickToAnimate.json b/editor/public/examples/graphs/ClickToAnimate.json old mode 100644 new mode 100755 diff --git a/editor/public/examples/graphs/FlashSuzanne.json b/editor/public/examples/graphs/FlashSuzanne.json old mode 100644 new mode 100755 diff --git a/editor/public/examples/graphs/OnChainClickButtonToAnimate.json b/editor/public/examples/graphs/OnChainClickButtonToAnimate.json new file mode 100755 index 0000000..0de32f5 --- /dev/null +++ b/editor/public/examples/graphs/OnChainClickButtonToAnimate.json @@ -0,0 +1,178 @@ +{ + "nodes": [ + { + "id": "95fbb61d-5331-4c4e-935c-3b69f7b8bc3d", + "type": "chain/intVariableGet", + "metadata": { + "positionX": "33.885925349863555", + "positionY": "-577.5140747355053" + } + }, + { + "id": "85c96bd9-12ab-4359-88f1-3554fffcfe91", + "type": "chain/invokeAction", + "metadata": { + "positionX": "-616.5075071386531", + "positionY": "-570.9137043599102" + }, + "flows": { + "flow": { + "nodeId": "8e7f6b70-8d0c-45be-81dd-ad0014209dbc", + "socket": "flow" + } + } + }, + { + "id": "85c96bd9-12ab-4359-88f1-3554fffcfe90", + "type": "chain/intVariableSet", + "metadata": { + "positionX": "-163.15091375180901", + "positionY": "-585.4948388660762" + }, + "parameters": { + "value": { + "link": { + "nodeId": "8e7f6b70-8d0c-45be-81dd-ad0014209dbc", + "socket": "count" + } + } + } + }, + { + "id": "8e7f6b70-8d0c-45be-81dd-ad0014209dbc", + "type": "chain/counter", + "metadata": { + "positionX": "-390.5852030032349", + "positionY": "-583.9195282889922" + }, + "flows": { + "flow": { + "nodeId": "85c96bd9-12ab-4359-88f1-3554fffcfe90", + "socket": "flow" + } + } + }, + { + "id": "d71ccb9c-0fea-4ce9-bf92-a31485e9b5a6", + "type": "scene/set/color", + "metadata": { + "positionX": "184.79620709295074", + "positionY": "18.913444261875217" + }, + "parameters": { + "jsonPath": { + "value": "materials/2/color" + }, + "value": { + "link": { + "nodeId": "5394babc-dcc0-4735-9daf-465c73a66e80", + "socket": "result" + } + } + } + }, + { + "id": "5394babc-dcc0-4735-9daf-465c73a66e80", + "type": "math/toColor/rgb", + "metadata": { + "positionX": "-145.20682021207682", + "positionY": "90.16020188760152" + }, + "parameters": { + "g": { + "value": ".9" + } + } + }, + { + "id": "267d676e-79d4-49e5-9be8-c7dd8b157f00", + "type": "math/toColor/rgb", + "metadata": { + "positionX": "-140.0557507122972", + "positionY": "344.03584956239894" + }, + "parameters": { + "r": { + "value": ".9" + } + } + }, + { + "id": "98da1b8b-b49e-489d-80ff-24ce601ac0c0", + "type": "scene/set/color", + "metadata": { + "positionX": "181.70354627203167", + "positionY": "272.5116315772409" + }, + "parameters": { + "jsonPath": { + "value": "materials/2/color" + }, + "value": { + "link": { + "nodeId": "267d676e-79d4-49e5-9be8-c7dd8b157f00", + "socket": "result" + } + } + } + }, + { + "id": "d326d57f-198b-41e8-9d2b-182130f1398f", + "type": "flow/flipFlop", + "metadata": { + "positionX": "-160.18432779493156", + "positionY": "-340.6752730499105" + }, + "flows": { + "on": { + "nodeId": "d71ccb9c-0fea-4ce9-bf92-a31485e9b5a6", + "socket": "flow" + }, + "off": { + "nodeId": "98da1b8b-b49e-489d-80ff-24ce601ac0c0", + "socket": "flow" + } + } + }, + { + "id": "85179702-7634-4cf5-95ca-5908224cd4f0", + "type": "scene/nodeClick", + "metadata": { + "positionX": "-805.9050665431862", + "positionY": "-239.26637596237106" + }, + "parameters": { + "jsonPath": { + "value": "nodes/2" + } + }, + "flows": { + "flow": { + "nodeId": "85c96bd9-12ab-4359-88f1-3554fffcfe91", + "socket": "flow" + } + } + }, + { + "id": "fcb81837-650c-42fb-a708-236f2ee64abc", + "type": "scene/set/boolean", + "metadata": { + "positionX": "174.3147945277493", + "positionY": "-187.0779606350799" + }, + "parameters": { + "jsonPath": { + "value": "animations/0/playing" + }, + "value": { + "link": { + "nodeId": "d326d57f-198b-41e8-9d2b-182130f1398f", + "socket": "isOn" + } + } + } + } + ], + "variables": [], + "customEvents": [] +} diff --git a/editor/public/examples/graphs/SpinningSuzanne.json b/editor/public/examples/graphs/SpinningSuzanne.json old mode 100644 new mode 100755 diff --git a/editor/public/examples/graphs/TokenGatedClick.json b/editor/public/examples/graphs/TokenGatedClick.json old mode 100644 new mode 100755 diff --git a/editor/public/examples/graphs/animatedBuildingColor.json b/editor/public/examples/graphs/animatedBuildingColor.json old mode 100644 new mode 100755 diff --git a/editor/public/examples/graphs/defaultGraph.json b/editor/public/examples/graphs/defaultGraph.json old mode 100644 new mode 100755 diff --git a/editor/public/examples/models/CourtYard.glb b/editor/public/examples/models/CourtYard.glb old mode 100644 new mode 100755 diff --git a/editor/public/examples/models/FlashSuzanne.gltf b/editor/public/examples/models/FlashSuzanne.gltf old mode 100644 new mode 100755 diff --git a/editor/public/examples/models/Hierarchy.gltf b/editor/public/examples/models/Hierarchy.gltf old mode 100644 new mode 100755 diff --git a/editor/public/examples/models/PressButtonToStartElevator.gltf b/editor/public/examples/models/PressButtonToStartElevator.gltf old mode 100644 new mode 100755 diff --git a/editor/public/examples/models/SpinningSuzanne.gltf b/editor/public/examples/models/SpinningSuzanne.gltf old mode 100644 new mode 100755 diff --git a/editor/public/interx.jpg b/editor/public/interx.jpg old mode 100644 new mode 100755 diff --git a/editor/public/interx.png b/editor/public/interx.png old mode 100644 new mode 100755 diff --git a/editor/public/vite.svg b/editor/public/vite.svg old mode 100644 new mode 100755 diff --git a/editor/src/App.tsx b/editor/src/App.tsx old mode 100644 new mode 100755 diff --git a/editor/src/EditorAndScene.tsx b/editor/src/EditorAndScene.tsx old mode 100644 new mode 100755 index 05c52d1..2488ef1 --- a/editor/src/EditorAndScene.tsx +++ b/editor/src/EditorAndScene.tsx @@ -1,63 +1,71 @@ -import { Suspense, useCallback } from 'react'; +import { Suspense } from 'react'; import Scene from './scene/Scene'; import '@rainbow-me/rainbowkit/styles.css'; -import useMockSmartContractActions from './onChainWorld/useMockSmartContractActions'; import './styles/resizer.css'; import Controls from './flowEditor/components/Controls'; import Nav from './nav/Nav'; import PublishingControls from './web3/PublishingControls'; -import useNodeSpecJson from './hooks/useNodeSpecJson'; -import useRegistry from './hooks/useRegistry'; import useSetAndLoadModelFile, { exampleModelFileUrl } from './hooks/useSetAndLoadModelFile'; -import useBehaveGraphFlow, { exampleBehaveGraphFileUrl } from './hooks/useBehaveGraphFlow'; -import useEngine from './hooks/useEngine'; -import useSceneModifier from './scene/useSceneModifier'; import Flow from './flowEditor/FlowEditorApp'; import SplitEditor from './SplitEditor'; import { examplePairs } from './flowEditor/components/LoadModal'; -import { Registry } from '@behave-graph/core'; -import useRegisterSmartContractActions from './onChainWorld/useRegisterSmartContractActions'; +import { registerSceneProfile, registerSceneDependency, IRegistry } from '@behave-graph/core'; +import { useScene } from './scene/useSceneModifier'; +import { registerChainGraphDepenency, registerChainGraphProfile, useMockSmartContractActions } from '@blocktopia/core'; +import { useRegisterDependency } from './hooks/useRegisterDependency'; +import { + useRegisterCoreProfileAndOthers, + useBehaveGraphFlow, + useGraphRunner, + useNodeSpecJson, +} from '@behave-graph/flow'; +import { suspend } from 'suspend-react'; +import { exampleBehaveGraphFileUrl, fetchBehaviorGraphJson } from './hooks/useSaveAndLoad'; +import { ReactFlowProvider } from 'reactflow'; const [initialModelFile, initialBehaviorGraph] = examplePairs[0]; const initialModelFileUrl = exampleModelFileUrl(initialModelFile); const initialBehaviorGraphUrl = exampleBehaveGraphFileUrl(initialBehaviorGraph); -function EditorAndScene({ web3Enabled }: { web3Enabled?: boolean }) { - const smartContractActions = useMockSmartContractActions(); - const registerSmartContractActions = useRegisterSmartContractActions(smartContractActions); +export const registerChainGraphProfiles: ((registry: Pick) => void)[] = [ + registerChainGraphProfile, + registerSceneProfile, +]; +function EditorAndScene({ web3Enabled }: { web3Enabled?: boolean }) { const { modelFile, setModelFile, gltf } = useSetAndLoadModelFile({ initialFileUrl: initialModelFileUrl, }); - const { scene, animations, sceneOnClickListeners, registerSceneProfile } = useSceneModifier(gltf); - - const registerProfiles = useCallback( - (registry: Registry) => { - registerSmartContractActions(registry); - registerSceneProfile(registry); - }, - [registerSceneProfile, smartContractActions] - ); - - const { registry, lifecyleEmitter } = useRegistry({ - registerProfiles, + const { registry, lifecyleEmitter } = useRegisterCoreProfileAndOthers({ + otherRegisters: registerChainGraphProfiles, }); + const { scene, animations, sceneOnClickListeners } = useScene(gltf); + useRegisterDependency(registry?.dependencies, scene, registerSceneDependency); + const specJson = useNodeSpecJson(registry); + const initialGraphJson = suspend(async () => { + return await fetchBehaviorGraphJson(initialBehaviorGraphUrl); + }, []); + const { nodes, edges, onNodesChange, onEdgesChange, graphJson, setGraphJson } = useBehaveGraphFlow({ - initialGraphJsonUrl: initialBehaviorGraphUrl, + initialGraphJson, specJson, }); - const { togglePlay, playing } = useEngine({ + const { togglePlay, playing } = useGraphRunner({ graphJson, registry, eventEmitter: lifecyleEmitter, }); + const chainGraph = useMockSmartContractActions(); + + useRegisterDependency(registry?.dependencies, chainGraph, registerChainGraphDepenency); + const web3Controls = web3Enabled ? : null; const controls = specJson && ( @@ -74,6 +82,7 @@ function EditorAndScene({ web3Enabled }: { web3Enabled?: boolean }) { const flowEditor = specJson && ( - + + + ); } diff --git a/editor/src/SplitEditor.tsx b/editor/src/SplitEditor.tsx old mode 100644 new mode 100755 diff --git a/editor/src/abstractions.ts b/editor/src/abstractions.ts deleted file mode 100644 index b7329a9..0000000 --- a/editor/src/abstractions.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IScene } from '@behave-graph/core'; - -export interface ISmartContractActions { - invoke: (id: string) => void; - registerTriggerHandler: (id: string, cb: (count: bigint) => void) => void; - unRegisterTriggerHandler: (id: string, cb: (count: bigint) => void) => void; -} - -export type ResourceOption = { - name: string; - index: number; -}; - -export type ResourceProperties = { - options: ResourceOption[]; - properties: string[]; -}; - -export type ResourceTypes = 'nodes' | 'materials' | 'animations'; - -export type Properties = { - nodes?: ResourceProperties; - materials?: ResourceProperties; - animations?: ResourceProperties; -}; - -export interface ISceneWithQueries extends IScene { - getProperties: () => Properties; - removeOnClickedListener(jsonPath: string, callback: (jsonPath: string) => void): void; -} diff --git a/editor/src/contracts/abi.ts b/editor/src/contracts/abi.ts deleted file mode 100644 index ef2be85..0000000 --- a/editor/src/contracts/abi.ts +++ /dev/null @@ -1,679 +0,0 @@ -export const abi = [ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "nodeId", - "type": "string" - } - ], - "name": "InvalidActionId", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "nodeId", - "type": "string" - }, - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - } - ], - "name": "MissingTokens", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "executor", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "string", - "name": "actionId", - "type": "string" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "count", - "type": "uint256" - } - ], - "name": "ActionExecuted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "uri", - "type": "string" - }, - { - "components": [ - { - "internalType": "string", - "name": "id", - "type": "string" - }, - { - "internalType": "enum NodeType", - "name": "nodeType", - "type": "uint8" - }, - { - "components": [ - { - "internalType": "bool", - "name": "active", - "type": "bool" - }, - { - "internalType": "address", - "name": "tokenContract", - "type": "address" - } - ], - "internalType": "struct TokenGateRule", - "name": "tokenGateRule", - "type": "tuple" - } - ], - "indexed": false, - "internalType": "struct Node[]", - "name": "nodes", - "type": "tuple[]" - } - ], - "name": "SafeMint", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "string", - "name": "_nodeId", - "type": "string" - } - ], - "name": "executeAction", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "string", - "name": "_nodeId", - "type": "string" - } - ], - "name": "getActionCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "string[]", - "name": "_nodeIds", - "type": "string[]" - } - ], - "name": "getActionCounts", - "outputs": [ - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "getApproved", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "string", - "name": "_nodeId", - "type": "string" - } - ], - "name": "getNode", - "outputs": [ - { - "components": [ - { - "internalType": "string", - "name": "id", - "type": "string" - }, - { - "internalType": "enum NodeType", - "name": "nodeType", - "type": "uint8" - }, - { - "components": [ - { - "internalType": "bool", - "name": "active", - "type": "bool" - }, - { - "internalType": "address", - "name": "tokenContract", - "type": "address" - } - ], - "internalType": "struct TokenGateRule", - "name": "tokenGateRule", - "type": "tuple" - } - ], - "internalType": "struct Node", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "sceneUri", - "type": "string" - }, - { - "components": [ - { - "internalType": "string", - "name": "id", - "type": "string" - }, - { - "internalType": "enum NodeType", - "name": "nodeType", - "type": "uint8" - }, - { - "components": [ - { - "internalType": "bool", - "name": "active", - "type": "bool" - }, - { - "internalType": "address", - "name": "tokenContract", - "type": "address" - } - ], - "internalType": "struct TokenGateRule", - "name": "tokenGateRule", - "type": "tuple" - } - ], - "internalType": "struct Node[]", - "name": "_nodes", - "type": "tuple[]" - } - ], - "name": "safeMint", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "tokenURI", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] as const \ No newline at end of file diff --git a/editor/src/contracts/addresses.json b/editor/src/contracts/addresses.json deleted file mode 100644 index eabc0fb..0000000 --- a/editor/src/contracts/addresses.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "localhost": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - "mumbai": "0xbefC97Db79157D8AaAedD5B1E6759DCA5B68dF50" -} \ No newline at end of file diff --git a/editor/src/flowEditor/FlowEditorApp.tsx b/editor/src/flowEditor/FlowEditorApp.tsx old mode 100644 new mode 100755 index cd4f483..91b64a5 --- a/editor/src/flowEditor/FlowEditorApp.tsx +++ b/editor/src/flowEditor/FlowEditorApp.tsx @@ -1,23 +1,13 @@ -import { MouseEvent as ReactMouseEvent, useCallback, useState } from 'react'; -import ReactFlow, { - Background, - BackgroundVariant, - Connection, - OnConnectStartParams, - OnNodesChange, - OnEdgesChange, - XYPosition, - Node, - Edge, -} from 'reactflow'; -import { v4 as uuidv4 } from 'uuid'; +import ReactFlow, { Background, BackgroundVariant, OnNodesChange, OnEdgesChange, Node, Edge } from 'reactflow'; import NodePicker from './components/NodePicker'; -import { calculateNewEdge } from './util/calculateNewEdge'; import { NodeSpecJSON } from '@behave-graph/core'; import 'reactflow/dist/style.css'; import './flowEditor.css'; -import useFlowConfigFromRegistry from './hooks/useFlowConfigFromRegistry'; -import { ISceneWithQueries } from '../abstractions'; +import { ISceneWithQueries } from '@blocktopia/core'; +import { useFlowHandlers } from '@behave-graph/flow'; +import { useCustomNodeTypes, useBehaveGraphFlow } from '@behave-graph/flow'; + +type BehaveGraphFlow = ReturnType; function Flow({ nodes, @@ -29,103 +19,33 @@ function Flow({ controls, }: { nodes: Node[]; - onNodesChange: OnNodesChange; edges: Edge[]; - onEdgesChange: OnEdgesChange; specJson: NodeSpecJSON[]; scene: ISceneWithQueries | undefined; controls: JSX.Element | undefined; -}) { - const [nodePickerVisibility, setNodePickerVisibility] = useState(); - const [lastConnectStart, setLastConnectStart] = useState(); - - const { filters, customNodeTypes } = useFlowConfigFromRegistry({ +} & Pick) { + const { + onConnect, + handleStartConnect, + handleStopConnect, + handlePaneClick, + handlePaneContextMenu, + nodePickerVisibility, + handleAddNode, + closeNodePicker, + nodePickFilters, + } = useFlowHandlers({ nodes, - lastConnectStart, - specJson, - scene, + onEdgesChange, + onNodesChange, + specJSON: specJson, }); - const onConnect = useCallback( - (connection: Connection) => { - if (connection.source === null) return; - if (connection.target === null) return; - - const newEdge = { - id: uuidv4(), - source: connection.source, - target: connection.target, - sourceHandle: connection.sourceHandle, - targetHandle: connection.targetHandle, - }; - onEdgesChange([ - { - type: 'add', - item: newEdge, - }, - ]); - }, - [onEdgesChange] - ); - - const handleAddNode = useCallback( - (nodeType: string, position: XYPosition) => { - if (!specJson) return; - closeNodePicker(); - const newNode = { - id: uuidv4(), - type: nodeType, - position, - data: {}, - }; - onNodesChange([ - { - type: 'add', - item: newNode, - }, - ]); - - if (lastConnectStart === undefined) return; - - // add an edge if we started on a socket - const originNode = nodes.find((node) => node.id === lastConnectStart.nodeId); - if (originNode === undefined) return; - onEdgesChange([ - { - type: 'add', - item: calculateNewEdge(originNode, nodeType, newNode.id, lastConnectStart, specJson), - }, - ]); - }, - [lastConnectStart, nodes, onEdgesChange, onNodesChange, specJson] - ); - - const handleStartConnect = (e: ReactMouseEvent, params: OnConnectStartParams) => { - setLastConnectStart(params); - }; - - const handleStopConnect = (e: MouseEvent) => { - const element = e.target as HTMLElement; - if (element.classList.contains('react-flow__pane')) { - setNodePickerVisibility({ x: e.clientX, y: e.clientY }); - } else { - setLastConnectStart(undefined); - } - }; - - const closeNodePicker = () => { - setLastConnectStart(undefined); - setNodePickerVisibility(undefined); - }; - - const handlePaneClick = () => closeNodePicker(); - - const handlePaneContextMenu = (e: ReactMouseEvent) => { - e.preventDefault(); - setNodePickerVisibility({ x: e.clientX, y: e.clientY }); - }; + const customNodeTypes = useCustomNodeTypes({ + specJson, + }); - if (!customNodeTypes || !specJson) return null; + if (!customNodeTypes) return null; return ( <> @@ -148,7 +68,7 @@ function Flow({ {nodePickerVisibility && ( ; + Pick; export default function InputSocket({ connected, diff --git a/editor/src/flowEditor/components/LoadModal.tsx b/editor/src/flowEditor/components/LoadModal.tsx old mode 100644 new mode 100755 index 4897214..c7cfddf --- a/editor/src/flowEditor/components/LoadModal.tsx +++ b/editor/src/flowEditor/components/LoadModal.tsx @@ -6,7 +6,7 @@ import { useDropzone } from 'react-dropzone'; import { fetchModelFile } from '../../hooks/useSaveAndLoad'; import ModelPreview from '../../scene/ModelPreview'; -import { exampleBehaveGraphFileUrl, fetchBehaviorGraphJson } from '../../hooks/useBehaveGraphFlow'; +import { exampleBehaveGraphFileUrl, fetchBehaviorGraphJson } from '../../hooks/useSaveAndLoad'; import { exampleModelFileUrl } from '../../hooks/useSetAndLoadModelFile'; const modelFiles = { @@ -17,6 +17,7 @@ const modelFiles = { const graphFiles = { clickButtonToAnimate: 'ClickButtonToAnimate.json', + onChainClickButtonToAnimate: 'OnChainClickButtonToAnimate.json', spinningSuzanne: 'SpinningSuzanne.json', tokenGatedClick: 'TokenGatedClick.json', delay: 'Delay.json', @@ -26,7 +27,7 @@ const graphFiles = { }; export const examplePairs: [string, string][] = [ - [modelFiles.pressButtonToStartElevator, graphFiles.clickButtonToAnimate], + [modelFiles.pressButtonToStartElevator, graphFiles.onChainClickButtonToAnimate], [modelFiles.suzanne, graphFiles.spinningSuzanne], [modelFiles.suzanne, graphFiles.tokenGatedClick], // [graphFiles.helloWorld], diff --git a/editor/src/flowEditor/components/Modal.tsx b/editor/src/flowEditor/components/Modal.tsx old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/components/Node.tsx b/editor/src/flowEditor/components/Node.tsx old mode 100644 new mode 100755 index 558bbb7..fead0f9 --- a/editor/src/flowEditor/components/Node.tsx +++ b/editor/src/flowEditor/components/Node.tsx @@ -5,12 +5,12 @@ import NodeContainer from './NodeContainer'; import OutputSocket from './OutputSocket'; import { useChangeNodeData } from '../hooks/useChangeNodeData'; import { isHandleConnected } from '../util/isHandleConnected'; -import { IScene } from '../../abstractions'; +import { ISceneWithQueries } from '@blocktopia/core'; type NodeProps = FlowNodeProps & { spec: NodeSpecJSON; allSpecs: NodeSpecJSON[]; -} & Pick; +} & Pick; const getTitle = (type: string) => { const tokens = type.split('/'); diff --git a/editor/src/flowEditor/components/NodeContainer.tsx b/editor/src/flowEditor/components/NodeContainer.tsx old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/components/NodePicker.tsx b/editor/src/flowEditor/components/NodePicker.tsx old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/components/OutputSocket.tsx b/editor/src/flowEditor/components/OutputSocket.tsx old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/components/PathSelect.tsx b/editor/src/flowEditor/components/PathSelect.tsx old mode 100644 new mode 100755 index b5b83de..014787c --- a/editor/src/flowEditor/components/PathSelect.tsx +++ b/editor/src/flowEditor/components/PathSelect.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { parseJsonPath, Path, toJsonPathString } from '../../scene/useSceneModifier'; -import { ISceneWithQueries, ResourceTypes } from '../../abstractions'; +import { ISceneWithQueries, ResourceTypes } from '@blocktopia/core'; const PathSelect = ({ value, diff --git a/editor/src/flowEditor/components/SaveModal.tsx b/editor/src/flowEditor/components/SaveModal.tsx old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/flowEditor.css b/editor/src/flowEditor/flowEditor.css old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/hooks/useChangeNodeData.ts b/editor/src/flowEditor/hooks/useChangeNodeData.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/hooks/useFlowConfigFromRegistry.ts b/editor/src/flowEditor/hooks/useFlowConfigFromRegistry.ts old mode 100644 new mode 100755 index 335e2ed..9cb73be --- a/editor/src/flowEditor/hooks/useFlowConfigFromRegistry.ts +++ b/editor/src/flowEditor/hooks/useFlowConfigFromRegistry.ts @@ -1,7 +1,7 @@ import { NodeSpecJSON } from '@behave-graph/core'; import { useEffect, useState } from 'react'; import { NodeTypes, Node, OnConnectStartParams } from 'reactflow'; -import { ISceneWithQueries } from '../../abstractions'; +import { ISceneWithQueries } from '../../../../packages/core/src/abstractions'; import { NodePickerFilters } from '../components/NodePicker'; import getCustomNodeTypes from '../util/getCustomNodeTypes'; import { getNodePickerFilters } from '../util/getPickerFilters'; diff --git a/editor/src/flowEditor/hooks/useOnPressKey.ts b/editor/src/flowEditor/hooks/useOnPressKey.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/transformers/behaveToFlow.ts b/editor/src/flowEditor/transformers/behaveToFlow.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/transformers/flowToBehave.ts b/editor/src/flowEditor/transformers/flowToBehave.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/autoLayout.ts b/editor/src/flowEditor/util/autoLayout.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/calculateNewEdge.ts b/editor/src/flowEditor/util/calculateNewEdge.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/colors.ts b/editor/src/flowEditor/util/colors.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/getCustomNodeTypes.tsx b/editor/src/flowEditor/util/getCustomNodeTypes.tsx old mode 100644 new mode 100755 index 899dc5e..a093845 --- a/editor/src/flowEditor/util/getCustomNodeTypes.tsx +++ b/editor/src/flowEditor/util/getCustomNodeTypes.tsx @@ -1,9 +1,9 @@ import { NodeSpecJSON } from '@behave-graph/core'; import { NodeTypes } from 'reactflow'; -import { IScene } from '../../abstractions'; import { Node } from '../components/Node'; +import { ISceneWithQueries } from '@blocktopia/core'; -const getCustomNodeTypes = (allSpecs: NodeSpecJSON[], scene: IScene) => { +const getCustomNodeTypes = (allSpecs: NodeSpecJSON[], scene: ISceneWithQueries) => { return allSpecs.reduce((nodes, node) => { nodes[node.type] = (props) => ( diff --git a/editor/src/flowEditor/util/getNodeSpecJSON.ts b/editor/src/flowEditor/util/getNodeSpecJSON.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/getPickerFilters.ts b/editor/src/flowEditor/util/getPickerFilters.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/getSocketsByNodeTypeAndHandleType.ts b/editor/src/flowEditor/util/getSocketsByNodeTypeAndHandleType.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/hasPositionMetaData.ts b/editor/src/flowEditor/util/hasPositionMetaData.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/isHandleConnected.ts b/editor/src/flowEditor/util/isHandleConnected.ts old mode 100644 new mode 100755 diff --git a/editor/src/flowEditor/util/isValidConnection.ts b/editor/src/flowEditor/util/isValidConnection.ts old mode 100644 new mode 100755 diff --git a/editor/src/hooks/behaviorFlow.ts b/editor/src/hooks/behaviorFlow.ts deleted file mode 100644 index f74285d..0000000 --- a/editor/src/hooks/behaviorFlow.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - DefaultLogger, - Engine, - Graph, - GraphJSON, - ILifecycleEventEmitter, - ILogger, - ManualLifecycleEventEmitter, - NodeSpecJSON, - readGraphFromJSON, - registerCoreProfile, - Registry, -} from '@behave-graph/core'; -import { useEffect, useState } from 'react'; -import { getNodeSpecJSON } from '../flowEditor/util/getNodeSpecJSON'; -import { IScene, ISmartContractActions } from '../abstractions'; -import { registerSharedSceneProfiles, registerSmartContractActions, registerSpecificSceneProfiles } from './profiles'; - -export const useRegistry = ({ - scene, - smartContractActions, -}: { - scene: IScene | undefined; - smartContractActions: ISmartContractActions; -}) => { - const [registry, setRegistry] = useState(); - - const [lifecyleEmitter, setLifecycleEmitter] = useState(new ManualLifecycleEventEmitter()); - const [logger] = useState(new DefaultLogger()); - - const [specJson, setSpecJson] = useState(); - - useEffect(() => { - if (!scene) return; - const registry = new Registry(); - const lifecyleEmitter = new ManualLifecycleEventEmitter(); - registerCoreProfile(registry, logger, lifecyleEmitter); - registerSharedSceneProfiles(registry, scene); - registerSpecificSceneProfiles(registry, scene); - registerSmartContractActions(registry, smartContractActions); - const specJson = getNodeSpecJSON(registry); - - setRegistry(registry); - setSpecJson(specJson); - setLifecycleEmitter(lifecyleEmitter); - }, [scene, smartContractActions, logger]); - - return { registry, specJson, lifecyleEmitter, logger }; -}; - -export const useGraph = (graphJson: GraphJSON | undefined, registry: Registry | undefined) => { - const [graph, setGraph] = useState(); - - useEffect(() => { - if (!graphJson || !registry) { - setGraph(undefined); - return; - } - - setGraph(readGraphFromJSON(graphJson, registry)); - }, [graphJson, registry]); - - return graph; -}; - -export const useSceneModificationEngine = ({ - graphJson, - registry, - eventEmitter, - run, -}: { - graphJson: GraphJSON | undefined; - registry: Registry | undefined; - eventEmitter: ILifecycleEventEmitter; - run: boolean; -}) => { - const [engine, setEngine] = useState(); - - useEffect(() => { - if (!graphJson || !registry || !run) return; - - const graph = readGraphFromJSON(graphJson, registry); - const engine = new Engine(graph); - - setEngine(engine); - - return () => { - engine.dispose(); - setEngine(undefined); - }; - }, [graphJson, registry, run]); - - useEffect(() => { - if (!engine || !run) return; - - engine.executeAllSync(); - - let timeout: number; - - const onTick = async () => { - eventEmitter.tickEvent.emit(); - - // eslint-disable-next-line no-await-in-loop - await engine.executeAllAsync(500); - - timeout = window.setTimeout(onTick, 50); - }; - - (async () => { - if (eventEmitter.startEvent.listenerCount > 0) { - eventEmitter.startEvent.emit(); - - await engine.executeAllAsync(5); - } else { - console.log('has no listener count'); - } - onTick(); - })(); - - return () => { - console.log('clear timeout'); - window.clearTimeout(timeout); - }; - }, [eventEmitter, engine, run]); - - return engine; -}; diff --git a/editor/src/hooks/index.ts b/editor/src/hooks/index.ts deleted file mode 100644 index 9aac731..0000000 --- a/editor/src/hooks/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as useBehaveGraphFlow } from './useBehaveGraphFlow'; -export { default as useEngine } from './useEngine'; -export { default as useNodeSpecJson } from './useNodeSpecJson'; -export { default as useRegistry } from './useRegistry'; diff --git a/editor/src/hooks/profiles.ts b/editor/src/hooks/profiles.ts deleted file mode 100644 index 91703a6..0000000 --- a/editor/src/hooks/profiles.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - Registry, - Vec2Value, - Vec3Value, - Vec4Value, - ColorValue, - EulerValue, - QuatValue, - getNodeDescriptions, - Vec2Nodes, - Vec3Nodes, - Vec4Nodes, - ColorNodes, - EulerNodes, - QuatNodes, - SetSceneProperty, - GetSceneProperty, - IScene, -} from '@behave-graph/core'; -import { registerSerializersForValueType } from '@behave-graph/core/src/Profiles/Core/registerSerializersForValueType'; -import { ISceneWithQueries, ISmartContractActions } from '../abstractions'; -import { OnSceneNodeClick } from '../nodes/scene/OnSceneNodeClick'; -import { OnSmartActionInvoked } from '../nodes/smartContracts/OnSmartActionInvoked'; -import { TokenGatedActionInvoker } from '../nodes/smartContracts/TokenGatedActionInvoker'; - -export function registerSharedSceneProfiles(registry: Registry, scene: IScene) { - const { values, nodes } = registry; - - // pull in value type nodes - values.register(Vec2Value); - values.register(Vec3Value); - values.register(Vec4Value); - values.register(ColorValue); - values.register(EulerValue); - values.register(QuatValue); - - // pull in value type nodes - nodes.register(...getNodeDescriptions(Vec2Nodes)); - nodes.register(...getNodeDescriptions(Vec3Nodes)); - nodes.register(...getNodeDescriptions(Vec4Nodes)); - nodes.register(...getNodeDescriptions(ColorNodes)); - nodes.register(...getNodeDescriptions(EulerNodes)); - nodes.register(...getNodeDescriptions(QuatNodes)); - - // // actions - const allValueTypeNames = values.getAllNames(); - nodes.register(...SetSceneProperty.GetDescriptions(scene, ...allValueTypeNames)); - nodes.register(...GetSceneProperty.GetDescriptions(scene, ...allValueTypeNames)); - - const newValueTypeNames = ['vec2', 'vec3', 'vec4', 'quat', 'euler', 'color']; - - // variables - - newValueTypeNames.forEach((valueTypeName) => { - registerSerializersForValueType( - // @ts-ignore - registry, - valueTypeName - ); - }); -} - -export function registerSpecificSceneProfiles(registry: Registry, scene: ISceneWithQueries) { - const { nodes } = registry; - - // TODO: register scene node types with IScene. - - nodes.register(OnSceneNodeClick.Description(scene)); -} - -export function registerSmartContractActions(registry: Registry, actions: ISmartContractActions) { - const { nodes } = registry; - - // TODO: register scene node types with IScene. - - nodes.register(TokenGatedActionInvoker.Description(actions)); - nodes.register(OnSmartActionInvoked.Description(actions)); -} diff --git a/editor/src/hooks/useBehaveGraphFlow.ts b/editor/src/hooks/useBehaveGraphFlow.ts deleted file mode 100644 index bb67b43..0000000 --- a/editor/src/hooks/useBehaveGraphFlow.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { GraphJSON, NodeSpecJSON } from '@behave-graph/core'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useEdgesState, useNodesState } from 'reactflow'; -import { suspend } from 'suspend-react'; -import { behaveToFlow } from '../flowEditor/transformers/behaveToFlow'; -import { flowToBehave } from '../flowEditor/transformers/flowToBehave'; -import { autoLayout } from '../flowEditor/util/autoLayout'; -import { hasPositionMetaData } from '../flowEditor/util/hasPositionMetaData'; -import { publicUrl } from './useSaveAndLoad'; - -export const exampleBehaveGraphFileUrl = (fileName: string) => publicUrl(`/examples/graphs/${fileName}`); -export const fetchBehaviorGraphJson = async (url: string) => (await (await fetch(url)).json()) as GraphJSON; - -const useBehaveGraphFlow = ({ - initialGraphJsonUrl, - specJson, -}: { - initialGraphJsonUrl: string | undefined; - specJson: NodeSpecJSON[] | undefined; -}) => { - const initialGraphJson = suspend(async () => { - if (!initialGraphJsonUrl) return; - return await fetchBehaviorGraphJson(initialGraphJsonUrl); - }, [initialGraphJsonUrl]); - - const [graphJson, setStoredGraphJson] = useState(initialGraphJson); - const [nodes, setNodes, onNodesChange] = useNodesState([]); - const [edges, setEdges, onEdgesChange] = useEdgesState([]); - - const setGraphJson = useCallback((graphJson: GraphJSON) => { - if (!graphJson) return; - - const [nodes, edges] = behaveToFlow(graphJson); - - if (hasPositionMetaData(graphJson) === false) { - autoLayout(nodes, edges); - } - - setNodes(nodes); - setEdges(edges); - setStoredGraphJson(graphJson); - }, []); - - useEffect(() => { - if (!specJson) return; - // when nodes and edges are updated, update the graph json with the flow to behave behavior - const graphJson = flowToBehave({ nodes, edges, nodeSpecJSON: specJson }); - setStoredGraphJson(graphJson); - }, [nodes, edges, specJson]); - - return { - nodes, - edges, - onEdgesChange, - onNodesChange, - setGraphJson, - graphJson, - }; -}; - -export default useBehaveGraphFlow; diff --git a/editor/src/hooks/useChangeNodeData.ts b/editor/src/hooks/useChangeNodeData.ts old mode 100644 new mode 100755 diff --git a/editor/src/hooks/useEngine.ts b/editor/src/hooks/useEngine.ts deleted file mode 100644 index 3bcc5a3..0000000 --- a/editor/src/hooks/useEngine.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - Engine, - GraphJSON, - ILifecycleEventEmitter, - readGraphFromJSON, - Registry -} from '@behave-graph/core'; -import { useCallback, useEffect, useState } from 'react'; - -const useEngine = ({ - graphJson, - registry, - eventEmitter, - autoRun = false -}: { - graphJson: GraphJSON | undefined; - registry: Registry | undefined; - eventEmitter: ILifecycleEventEmitter; - autoRun?: boolean; -}) => { - const [engine, setEngine] = useState(); - - const [run, setRun] = useState(autoRun); - - const play = useCallback(() => { - setRun(true); - }, []); - - const pause = useCallback(() => { - setRun(false); - }, []); - - const togglePlay = useCallback(() => { - setRun((existing) => !existing); - }, []); - - useEffect(() => { - if (!graphJson || !registry || !run) return; - - const graph = readGraphFromJSON(graphJson, registry); - const engine = new Engine(graph); - - setEngine(engine); - - return () => { - engine.dispose(); - setEngine(undefined); - }; - }, [graphJson, registry, run]); - - useEffect(() => { - if (!engine || !run) return; - - engine.executeAllSync(); - - let timeout: number; - - const onTick = async () => { - eventEmitter.tickEvent.emit(); - - // eslint-disable-next-line no-await-in-loop - await engine.executeAllAsync(500); - - timeout = window.setTimeout(onTick, 50); - }; - - (async () => { - if (eventEmitter.startEvent.listenerCount > 0) { - eventEmitter.startEvent.emit(); - - await engine.executeAllAsync(5); - } else { - console.log('has no listener count'); - } - onTick(); - })(); - - return () => { - window.clearTimeout(timeout); - }; - }, [eventEmitter, engine, run]); - - return { - engine, - playing: run, - play, - togglePlay, - pause - }; -}; - -export default useEngine; diff --git a/editor/src/hooks/useFlowConfigFromRegistry.ts b/editor/src/hooks/useFlowConfigFromRegistry.ts deleted file mode 100644 index 3e0d029..0000000 --- a/editor/src/hooks/useFlowConfigFromRegistry.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { NodeSpecJSON } from '@behave-graph/core'; -import { useEffect, useState } from 'react'; -import { Node, NodeTypes, OnConnectStartParams } from 'reactflow'; - -import { NodePickerFilters } from '../components/NodePicker'; -import getCustomNodeTypes from '../util/getCustomNodeTypes'; -import { getNodePickerFilters } from '../util/getPickerFilters'; - -const useFlowConfigFromRegistry = ({ - nodes, - lastConnectStart, - specJson -}: { - nodes: Node[]; - lastConnectStart: OnConnectStartParams | undefined; - specJson: NodeSpecJSON[]; -}) => { - const [filters, setFilters] = useState(); - - const [customNodeTypes, setCustomNodeTypes] = useState(); - - useEffect(() => { - if (!specJson) return; - const filters = getNodePickerFilters(nodes, lastConnectStart, specJson); - - setFilters(filters); - }, [lastConnectStart, nodes, specJson]); - - useEffect(() => { - if (!specJson) return; - const customNodeTypes = getCustomNodeTypes(specJson); - - setCustomNodeTypes(customNodeTypes); - }, [specJson]); - - return { filters, customNodeTypes, specJson }; -}; - -export default useFlowConfigFromRegistry; diff --git a/editor/src/hooks/useLoadSceneAndRegistry.ts b/editor/src/hooks/useLoadSceneAndRegistry.ts deleted file mode 100644 index 400ef8b..0000000 --- a/editor/src/hooks/useLoadSceneAndRegistry.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ObjectMap } from '@react-three/fiber'; -import { GLTF } from 'three-stdlib'; -import useSceneModifier from '../scene/useSceneModifier'; -import { useRegistry } from './behaviorFlow'; -import { ISmartContractActions } from '../abstractions'; - -const useLoadSceneAndRegistry = ({ - smartContractActions, - gltf, -}: { - smartContractActions: ISmartContractActions; - gltf?: GLTF & ObjectMap; -}) => { - const { scene, animations, sceneOnClickListeners } = useSceneModifier(gltf); - - const { registry, specJson, lifecyleEmitter } = useRegistry({ scene, smartContractActions }); - - return { - scene, - animations, - sceneOnClickListeners, - registry, - specJson, - lifecyleEmitter, - }; -}; - -export default useLoadSceneAndRegistry; diff --git a/editor/src/hooks/useMintWorld.ts b/editor/src/hooks/useMintWorld.ts deleted file mode 100644 index 3d4efd5..0000000 --- a/editor/src/hooks/useMintWorld.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { GraphJSON, NodeParametersJSON, NodeParameterValueJSON } from '@behave-graph/core'; -import { useEffect, useState } from 'react'; -import { usePrepareContractWrite, useContractWrite, useContractEvent } from 'wagmi'; -import { abi } from '../contracts/abi'; -import { - actionNameParamName, - smartContractInvokedActionName, - togenGatedAddressParamName, - tokenGatedParamName, -} from '../nodes/smartContracts/TokenGatedActionInvoker'; - -type TokenizedAction = { - nodeType: number; - id: string; - tokenGateRule: { - active: boolean; - tokenContract: `0x${string}`; - }; -}; - -export const tokenizableActionTypes: string[] = [smartContractInvokedActionName]; - -const getParam = (x: NodeParametersJSON | undefined, paramName: string) => { - if (!x) return undefined; - - const paramAndValue = x[paramName] as NodeParameterValueJSON | undefined; - - return paramAndValue?.value; -}; - -const actionsToSmartContractActions = (behaviorGraph: GraphJSON, contractAddress: string): TokenizedAction[] => { - const validNodes = behaviorGraph.nodes?.filter((x) => tokenizableActionTypes.includes(x.type)); - - if (!validNodes) return []; - - const result = validNodes.map((x): TokenizedAction => { - const parameters = x.parameters; - const actionName = getParam(parameters, actionNameParamName) as string | undefined; - const active = !!getParam(parameters, tokenGatedParamName); - const address = getParam(parameters, togenGatedAddressParamName) as `0x${string}` | undefined; - - if (!actionName) throw new Error(`actionName: ${actionName} must not be null`); - - const inner: TokenizedAction = { - id: actionName, - nodeType: 0, - tokenGateRule: { - active, - tokenContract: address || (contractAddress as `0x${string}`), - }, - }; - - return inner; - }) || [contractAddress]; - - return result; -}; - -const toMintArgs = (cid: string, behaviorGraph: GraphJSON, contractAddress: string): [string, TokenizedAction[]] => [ - cid, - actionsToSmartContractActions(behaviorGraph, contractAddress), -]; - -const useWaitForMintedTokenWithContentUri = ({ contractAddress, cid }: { contractAddress: string; cid: string }) => { - const [mintedTokenId, setMintedTokenId] = useState(null); - - useContractEvent({ - address: contractAddress, - abi: abi, - eventName: 'SafeMint', - listener(tokenId, to, uri, nodes) { - // hack - if this was minted with the proper cid, we can assume this was the token. - if (uri === cid) { - setMintedTokenId(tokenId.toNumber()); - } - }, - }); - - return mintedTokenId; -}; - -const useMintWorld = ({ - worldCid, - contractAddress, - behaviorGraph, -}: { - contractAddress: string; - worldCid: string; - behaviorGraph: GraphJSON; -}) => { - const [args, setArgs] = useState(() => toMintArgs(worldCid, behaviorGraph, contractAddress)); - - useEffect(() => { - const args = toMintArgs(worldCid, behaviorGraph, contractAddress); - setArgs(args); - }, [worldCid, behaviorGraph, contractAddress]); - - const { config, error, isError } = usePrepareContractWrite({ - address: contractAddress, - abi, - functionName: 'safeMint', - args, - }); - - const { data, isLoading, isSuccess, write } = useContractWrite({ - ...config, - }); - - const mintedTokenId = useWaitForMintedTokenWithContentUri({ - contractAddress, - cid: worldCid, - }); - - return { mint: write, isSuccess, isLoading, isError, error, mintedTokenId }; -}; - -export type MintWorldReturn = ReturnType; - -export default useMintWorld; diff --git a/editor/src/hooks/useNodeSpecJson.ts b/editor/src/hooks/useNodeSpecJson.ts deleted file mode 100644 index 20a5bdd..0000000 --- a/editor/src/hooks/useNodeSpecJson.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - NodeSpecJSON, - Registry, - writeNodeSpecsToJSON -} from '@behave-graph/core'; -import { useEffect, useState } from 'react'; - -const useNodeSpecJson = (registry: Registry | undefined) => { - const [specJson, setSpecJson] = useState(); - - useEffect(() => { - if (!registry) return; - const specJson = writeNodeSpecsToJSON(registry); - setSpecJson(specJson); - }, [registry]); - - return specJson; -}; - -export default useNodeSpecJson; diff --git a/editor/src/hooks/useOnPressKey.ts b/editor/src/hooks/useOnPressKey.ts old mode 100644 new mode 100755 diff --git a/editor/src/hooks/useRegisterDependency.ts b/editor/src/hooks/useRegisterDependency.ts new file mode 100755 index 0000000..cf9c27a --- /dev/null +++ b/editor/src/hooks/useRegisterDependency.ts @@ -0,0 +1,12 @@ +import { IRegistry } from '@behave-graph/core'; +import { useEffect } from 'react'; + +export const useRegisterDependency = ( + dependencies: IRegistry['dependencies'] | undefined, + dependency: any, + register: (registry: IRegistry['dependencies'], dependency: any) => void +) => { + useEffect(() => { + if (dependencies) register(dependencies, dependency); + }, [dependencies, dependency]); +}; diff --git a/editor/src/hooks/useRegistry.ts b/editor/src/hooks/useRegistry.ts deleted file mode 100644 index 9dc3e12..0000000 --- a/editor/src/hooks/useRegistry.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - DefaultLogger, - ILifecycleEventEmitter, - ILogger, - ManualLifecycleEventEmitter, - registerCoreProfile, - Registry -} from '@behave-graph/core'; -import { useEffect, useState } from 'react'; - -const useRegistry = ({ - registerProfiles -}: { - registerProfiles: (registry: Registry) => void; -}) => { - const [registry, setRegistry] = useState(); - - const [lifecyleEmitter, setLifecycleEmitter] = - useState(new ManualLifecycleEventEmitter()); - const [logger] = useState(new DefaultLogger()); - - useEffect(() => { - const registry = new Registry(); - const lifecyleEmitter = new ManualLifecycleEventEmitter(); - registerCoreProfile(registry, logger, lifecyleEmitter); - registerProfiles(registry); - - setRegistry(registry); - setLifecycleEmitter(lifecyleEmitter); - }, [registerProfiles, logger]); - - return { registry, lifecyleEmitter, logger }; -}; - -export default useRegistry; diff --git a/editor/src/hooks/useSaveAndLoad.ts b/editor/src/hooks/useSaveAndLoad.ts old mode 100644 new mode 100755 index 3274e01..5d3d486 --- a/editor/src/hooks/useSaveAndLoad.ts +++ b/editor/src/hooks/useSaveAndLoad.ts @@ -1,5 +1,8 @@ import { GraphJSON } from '@behave-graph/core'; +export const exampleBehaveGraphFileUrl = (fileName: string) => publicUrl(`/examples/graphs/${fileName}`); +export const fetchBehaviorGraphJson = async (url: string) => (await (await fetch(url)).json()) as GraphJSON; + function readFileContents(file: File) { return new Promise((resolve, reject) => { const reader = new FileReader(); diff --git a/editor/src/hooks/useSetAndLoadModelFile.ts b/editor/src/hooks/useSetAndLoadModelFile.ts old mode 100644 new mode 100755 diff --git a/editor/src/index.css b/editor/src/index.css old mode 100644 new mode 100755 diff --git a/editor/src/main.tsx b/editor/src/main.tsx old mode 100644 new mode 100755 diff --git a/editor/src/nav/Nav.tsx b/editor/src/nav/Nav.tsx old mode 100644 new mode 100755 index af7f059..3a900d0 --- a/editor/src/nav/Nav.tsx +++ b/editor/src/nav/Nav.tsx @@ -60,7 +60,10 @@ const Nav = ({ isWeb3Enabled }: { isWeb3Enabled?: boolean }) => {
{isWeb3Enabled && } - +
diff --git a/editor/src/nodes/scene/OnSceneNodeClick.ts b/editor/src/nodes/scene/OnSceneNodeClick.ts deleted file mode 100644 index 5ed2e29..0000000 --- a/editor/src/nodes/scene/OnSceneNodeClick.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Graph, Socket, Engine, Assert } from '@behave-graph/core'; -import { EventNode, NodeDescription } from '@behave-graph/core'; -import { ISceneWithQueries } from '../../abstractions'; - -// very 3D specific. -export class OnSceneNodeClick extends EventNode { - public static Description = (scene: ISceneWithQueries) => - new NodeDescription( - 'scene/nodeClick', - 'Event', - 'On Node Click', - (description, graph) => new OnSceneNodeClick(description, graph, scene) - ); - - constructor(description: NodeDescription, graph: Graph, private readonly scene: ISceneWithQueries) { - super( - description, - graph, - [new Socket('string', 'jsonPath')], - [new Socket('flow', 'flow'), new Socket('flow', 'secondFlow')] - ); - } - - private jsonPath: string | undefined; - - private sendNodeClickedData = (engine: Engine) => { - engine.commitToNewFiber(this, 'flow'); - engine.commitToNewFiber(this, 'secondFlow'); - }; - - private handleNodeClick: (() => void) | undefined = undefined; - - init(engine: Engine) { - const jsonPath = this.readInput('jsonPath') as string; - console.log({ jsonPath }); - if (!jsonPath) return; - Assert.mustBeTrue(this.handleNodeClick === undefined); - - this.jsonPath = jsonPath; - - this.handleNodeClick = () => { - this.sendNodeClickedData(engine); - }; - - const scene = this.scene; - scene.addOnClickedListener(jsonPath, this.handleNodeClick); - } - - dispose(engine: Engine) { - Assert.mustBeTrue(this.handleNodeClick !== undefined); - Assert.mustBeTrue(this.jsonPath !== undefined); - - if (!this.jsonPath || !this.handleNodeClick) return; - this.scene.removeOnClickedListener(this.jsonPath, this.handleNodeClick); - } -} diff --git a/editor/src/nodes/smartContracts/OnSmartActionInvoked.ts b/editor/src/nodes/smartContracts/OnSmartActionInvoked.ts deleted file mode 100644 index 3faebb7..0000000 --- a/editor/src/nodes/smartContracts/OnSmartActionInvoked.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Graph, Socket, Engine, Assert } from '@behave-graph/core'; -import { EventNode, NodeDescription } from '@behave-graph/core'; -import { ISmartContractActions } from '../../abstractions'; -import { actionNameParamName } from './TokenGatedActionInvoker'; - -const smartActionInvokedTypeName = 'smartContract/actionInvoked'; - -export class OnSmartActionInvoked extends EventNode { - public static Description = (smartContractActions: ISmartContractActions) => - new NodeDescription( - smartActionInvokedTypeName, - 'Event', - 'On Smart Contract Action Invoked', - (description, graph) => new OnSmartActionInvoked(description, graph, smartContractActions) - ); - - constructor( - description: NodeDescription, - graph: Graph, - private readonly smartContractActions: ISmartContractActions - ) { - super( - description, - graph, - [new Socket('string', actionNameParamName)], - [new Socket('flow', 'flow'), new Socket('integer', 'count')] - ); - } - - private jsonPath: string | undefined; - - private sendData = (engine: Engine, count: bigint) => { - engine.commitToNewFiber(this, 'flow'); - this.writeOutput('count', count); - }; - - private handleActionInvoked: ((count: bigint) => void) | undefined = undefined; - - private getTriggeredActionName() { - const triggerdActionName = this.readInput(actionNameParamName) as string; - Assert.mustBeTrue(triggerdActionName !== undefined); - - return triggerdActionName; - } - - init(engine: Engine) { - const triggeredNodeId = this.getTriggeredActionName(); - console.log({ nodeId: triggeredNodeId }); - if (!triggeredNodeId) return; - Assert.mustBeTrue(this.handleActionInvoked === undefined); - - this.jsonPath = triggeredNodeId; - - this.handleActionInvoked = (count: bigint) => { - this.sendData(engine, count); - }; - - const smartContractActions = this.smartContractActions; - smartContractActions.registerTriggerHandler(triggeredNodeId, this.handleActionInvoked); - } - - dispose(engine: Engine) { - Assert.mustBeTrue(this.handleActionInvoked !== undefined); - Assert.mustBeTrue(this.jsonPath !== undefined); - - if (!this.jsonPath || !this.handleActionInvoked) return; - - const triggeredNodeId = this.getTriggeredActionName(); - this.smartContractActions.unRegisterTriggerHandler(triggeredNodeId, this.handleActionInvoked); - } -} diff --git a/editor/src/nodes/smartContracts/TokenGatedActionInvoker.ts b/editor/src/nodes/smartContracts/TokenGatedActionInvoker.ts deleted file mode 100644 index 9fb6c33..0000000 --- a/editor/src/nodes/smartContracts/TokenGatedActionInvoker.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { FlowNode, NodeDescription, Socket, Graph, Engine, Assert } from '@behave-graph/core'; -import { Fiber } from '@behave-graph/core/dist/lib/Execution/Fiber'; -import { ISmartContractActions } from '../../abstractions'; - -export const smartContractInvokedActionName = 'smartContract/invokeTokenGatedAction'; -export const actionNameParamName = 'actionName'; -export const tokenGatedParamName = 'tokenGated'; -export const togenGatedAddressParamName = 'tokenGatedAddress'; - -export class TokenGatedActionInvoker extends FlowNode { - public static Description = (smartContractActions: ISmartContractActions) => - new NodeDescription( - smartContractInvokedActionName, - 'Flow', - 'Invoke Smart Contract Action', - (description, graph) => new TokenGatedActionInvoker(description, graph, smartContractActions) - ); - - constructor(description: NodeDescription, graph: Graph, private smartContractActions: ISmartContractActions) { - super( - description, - graph, - [ - new Socket('flow', 'flow'), - new Socket('string', actionNameParamName), - new Socket('boolean', tokenGatedParamName), - new Socket('string', togenGatedAddressParamName), - ], - [] - ); - } - - private isInitialized = false; - - triggered(fiber: Fiber) { - if (!this.isInitialized) { - this.isInitialized = true; - } - - const smartContractAction = this.smartContractActions; - - if (smartContractAction) { - const actionName = this.readInput(actionNameParamName) as string; - - Assert.mustBeTrue(actionName !== undefined); - smartContractAction.invoke(actionName); - } - } -} diff --git a/editor/src/onChainWorld/OnChainWorld.tsx b/editor/src/onChainWorld/OnChainWorld.tsx old mode 100644 new mode 100755 index 77843ee..9715d86 --- a/editor/src/onChainWorld/OnChainWorld.tsx +++ b/editor/src/onChainWorld/OnChainWorld.tsx @@ -1,38 +1,67 @@ -import { GraphJSON } from '@behave-graph/core'; +import { registerSceneDependency, IRegistry, registerSceneProfile } from '@behave-graph/core'; + import { useParams } from 'react-router-dom'; -import { useSceneModificationEngine } from '../hooks/behaviorFlow'; -import useLoadOnChainWorld from '../hooks/useLoadOnChainWorld'; -import useLoadSceneAndRegistry from '../hooks/useLoadSceneAndRegistry'; import Web3Login from '../web3/Web3Login'; import Scene from '../scene/Scene'; import useTokenContractAddress from '../web3/useTokenContractAddress'; -import useSmartContractActions from './useSmartContractActions'; -import { ISmartContractActions } from '../abstractions'; +import useChainGraph from '@blocktopia/core/src/hooks/useChainGraph'; import { useGLTF } from '@react-three/drei'; +import { useMemo } from 'react'; +import { registerChainGraphDepenency, registerChainGraphProfile } from '@blocktopia/core'; +import { useScene } from '../scene/useSceneModifier'; +import { useRegisterDependency } from '../hooks/useRegisterDependency'; +import { useGraphRunner, useRegisterCoreProfileAndOthers } from '@behave-graph/flow'; +import { useLoadOnChainWorld } from '@blocktopia/core'; + +const LoadAndIntegrateOnChainWorldInner = ({ + tokenId, + dependencies, + contractAddress, +}: { + tokenId: number; + dependencies: IRegistry['dependencies']; + contractAddress: `0x${string}`; +}) => { + const smartContractActions = useChainGraph(contractAddress, tokenId); + + useRegisterDependency(dependencies, smartContractActions, registerChainGraphDepenency); + + return null; +}; + +const LoadAndIntegrateOnChainWorld = ({ + tokenId, + dependencies, +}: { + tokenId: number; + dependencies: IRegistry['dependencies'] | undefined; +}) => { + const contractAddress = useTokenContractAddress(); + + if (!contractAddress || !dependencies) return null; + return ( + + ); +}; const OnChainWorld = ({ - graphJson, + dependencies, sceneFileUrl, - smartContractActions, tokenId, }: { - graphJson: GraphJSON; + dependencies: IRegistry['dependencies'] | undefined; sceneFileUrl: string; - smartContractActions: ISmartContractActions; tokenId: number; }) => { const gltf = useGLTF(sceneFileUrl); - const { sceneOnClickListeners, registry, lifecyleEmitter, animations } = useLoadSceneAndRegistry({ - smartContractActions, - gltf, - }); - useSceneModificationEngine({ - graphJson, - eventEmitter: lifecyleEmitter, - registry, - run: true, - }); + const { scene, animations, sceneOnClickListeners } = useScene(gltf); + + useRegisterDependency(dependencies, scene, registerSceneDependency); return ( <> @@ -59,28 +88,38 @@ const OnChainWorld = ({ ); }; -const OnChainWorldLoader = ({ tokenId, contractAddress }: { tokenId: number; contractAddress: string }) => { +const OnChainWorldLoader = ({ tokenId, contractAddress }: { tokenId: number; contractAddress: `0x${string}` }) => { const { graphJson, sceneFileUrl } = useLoadOnChainWorld(tokenId, contractAddress); - const smartContractActions = useSmartContractActions(contractAddress, tokenId); + const registerProfiles = useMemo(() => [registerChainGraphProfile, registerSceneProfile], []); + + const { registry, lifecyleEmitter } = useRegisterCoreProfileAndOthers({ + otherRegisters: registerProfiles, + }); + + useGraphRunner({ + graphJson, + registry, + eventEmitter: lifecyleEmitter, + autoRun: true, + }); + + if (!sceneFileUrl || !graphJson) return null; - if (!sceneFileUrl || !graphJson || !smartContractActions) return null; + const dependencies = registry?.dependencies; return ( - + <> + + + ); }; const OnChainWorldWrapper = () => { const { tokenId } = useParams<{ tokenId: string }>(); - const contractAddress = useTokenContractAddress(); - if (!contractAddress || !tokenId) return null; + if (!tokenId || !contractAddress) return null; return ; }; diff --git a/editor/src/onChainWorld/useMockSmartContractActions.ts b/editor/src/onChainWorld/useMockSmartContractActions.ts deleted file mode 100644 index d6c331b..0000000 --- a/editor/src/onChainWorld/useMockSmartContractActions.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useCallback, useMemo, useRef } from 'react'; -import { ISmartContractActions } from '../abstractions'; - -type hn = { [id: string]: (count: bigint) => void }; - -const useMockSmartContractActions = () => { - const actionExecutedHandlers = useRef({}); - - const mockCounts = useRef<{ [id: string]: number }>({}); - - const registerTriggerHandler = useCallback((id: string, cb: (count: bigint) => void) => { - actionExecutedHandlers.current[id] = cb; - }, []); - - const unRegisterTriggerHandler = useCallback((id: string, cb: (count: bigint) => void) => { - delete actionExecutedHandlers.current[id]; - }, []); - - const invoke = useCallback(async (actionId: string) => { - const newCount = (mockCounts.current[actionId] || 0) + 1; - mockCounts.current[actionId] = newCount; - - const handler = actionExecutedHandlers.current[actionId]; - if (handler) { - handler(BigInt(newCount)); - } - }, []); - - const smartContractAction = useMemo(() => { - const result: ISmartContractActions = { - invoke, - registerTriggerHandler, - unRegisterTriggerHandler, - }; - - return result; - }, [invoke, registerTriggerHandler, unRegisterTriggerHandler]); - - return smartContractAction; -}; - -export default useMockSmartContractActions; diff --git a/editor/src/onChainWorld/useRegisterSmartContractActions.ts b/editor/src/onChainWorld/useRegisterSmartContractActions.ts deleted file mode 100644 index c41abcc..0000000 --- a/editor/src/onChainWorld/useRegisterSmartContractActions.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Registry } from '@behave-graph/core'; -import { useCallback } from 'react'; -import { ISmartContractActions } from '../abstractions'; -import { registerSmartContractActions } from '../hooks/profiles'; - -const useRegisterSmartContractActions = (actions: ISmartContractActions) => { - const register = useCallback( - (registry: Registry) => { - registerSmartContractActions(registry, actions); - }, - [actions] - ); - - return register; -}; - -export default useRegisterSmartContractActions; diff --git a/editor/src/scene/GLTFJson.ts b/editor/src/scene/GLTFJson.ts old mode 100644 new mode 100755 diff --git a/editor/src/scene/InteractiveModelPreview.tsx b/editor/src/scene/InteractiveModelPreview.tsx old mode 100644 new mode 100755 index d0696f8..9002d70 --- a/editor/src/scene/InteractiveModelPreview.tsx +++ b/editor/src/scene/InteractiveModelPreview.tsx @@ -1,26 +1,32 @@ import { useEffect, useState } from 'react'; -import { GraphJSON } from '@behave-graph/core'; +import { GraphJSON, registerSceneDependency } from '@behave-graph/core'; import { useGLTF } from '@react-three/drei'; -import useLoadSceneAndRegistry from '../hooks/useLoadSceneAndRegistry'; -import useMockSmartContractActions from '../onChainWorld/useMockSmartContractActions'; import Scene from './Scene'; import { dataUrlFromFile } from '../hooks/useSaveAndLoad'; -import { useSceneModificationEngine } from '../hooks/behaviorFlow'; +import { registerChainGraphDepenency, useMockSmartContractActions } from '@blocktopia/core'; +import { useScene } from './useSceneModifier'; +import { useRegisterDependency } from '../hooks/useRegisterDependency'; +import { registerChainGraphProfiles } from '../EditorAndScene'; +import { useGraphRunner, useRegisterCoreProfileAndOthers } from '@behave-graph/flow'; const Inner = ({ fileDataUrl, graphJson }: { fileDataUrl: string; graphJson: GraphJSON }) => { const gltf = useGLTF(fileDataUrl); const smartContractActions = useMockSmartContractActions(); - const { animations, sceneOnClickListeners, lifecyleEmitter, registry } = useLoadSceneAndRegistry({ - gltf, - smartContractActions, + const { scene, animations, sceneOnClickListeners } = useScene(gltf); + + const { registry, lifecyleEmitter } = useRegisterCoreProfileAndOthers({ + otherRegisters: registerChainGraphProfiles, }); - useSceneModificationEngine({ + useRegisterDependency(registry?.dependencies, smartContractActions, registerChainGraphDepenency); + useRegisterDependency(registry?.dependencies, scene, registerSceneDependency); + + useGraphRunner({ graphJson, registry, eventEmitter: lifecyleEmitter, - run: true, + autoRun: true, }); return ; diff --git a/editor/src/scene/ModelPreview.tsx b/editor/src/scene/ModelPreview.tsx old mode 100644 new mode 100755 diff --git a/editor/src/scene/Scene.tsx b/editor/src/scene/Scene.tsx old mode 100644 new mode 100755 diff --git a/editor/src/scene/ToggleAnimations.tsx b/editor/src/scene/ToggleAnimations.tsx old mode 100644 new mode 100755 diff --git a/editor/src/scene/useSceneModifier.ts b/editor/src/scene/useSceneModifier.ts old mode 100644 new mode 100755 index 094a8d7..d763e8b --- a/editor/src/scene/useSceneModifier.ts +++ b/editor/src/scene/useSceneModifier.ts @@ -1,11 +1,9 @@ -import { Registry, Vec3, Vec4 } from '@behave-graph/core'; +import { Choices, IScene, Vec3, Vec4 } from '@behave-graph/core'; import { ObjectMap } from '@react-three/fiber'; import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react'; import { Event, Material, MeshBasicMaterial, Object3D, Quaternion, Vector3, Vector4 } from 'three'; import { GLTF } from 'three-stdlib'; -import { ISceneWithQueries, Properties, ResourceTypes } from '../abstractions'; -import { registerSharedSceneProfiles, registerSpecificSceneProfiles } from '../hooks/profiles'; import { GLTFJson } from './GLTFJson'; function toVec3(value: Vector3): Vec3 { @@ -51,6 +49,12 @@ export function parseJsonPath(jsonPath: string, short = false): Path { }; } +const resources = { + nodes: 'nodes', + materials: 'materials', + animations: 'animations', +}; + function applyPropertyToModel( { resource, index, property }: Path, gltf: GLTF & ObjectMap, @@ -60,7 +64,7 @@ function applyPropertyToModel( ) { const nodeName = getResourceName({ resource, index }, properties); if (!nodeName) throw new Error(`could not get node at index ${index}`); - if (resource === 'nodes') { + if (resource === resources.nodes) { const node = gltf.nodes[nodeName] as unknown as Object3D | undefined; if (!node) { @@ -72,7 +76,7 @@ function applyPropertyToModel( return; } - if (resource === 'materials') { + if (resource === resources.materials) { const node = gltf.materials[nodeName] as unknown as Material | undefined; if (!node) { @@ -85,7 +89,7 @@ function applyPropertyToModel( return; } - if (resource === 'animations') { + if (resource === resources.animations) { setActiveAnimations(nodeName, value as boolean); return; } @@ -98,7 +102,7 @@ const getResourceName = ({ resource, index }: Pick, }; const getPropertyFromModel = ({ resource, index, property }: Path, gltf: GLTF & ObjectMap, properties: Properties) => { - if (resource === 'nodes') { + if (resource === resources.nodes) { const nodeName = getResourceName({ resource, index }, properties); if (!nodeName) throw new Error(`could not get node at index ${index}`); const node = gltf.nodes[nodeName] as unknown as Object3D | undefined; @@ -172,6 +176,24 @@ function getPropertyValue(property: string, objectRef: Object3D) { } } +export type ResourceOption = { + name: string; + index: number; +}; + +export type ResourceProperties = { + options: ResourceOption[]; + properties: string[]; +}; + +export type ResourceTypes = 'nodes' | 'materials' | 'animations'; + +type Properties = { + nodes?: ResourceProperties; + materials?: ResourceProperties; + animations?: ResourceProperties; +}; + const extractProperties = (gltf: GLTF): Properties => { const nodeProperties = ['visible', 'translation', 'scale', 'rotation', 'color']; const animationProperties = ['playing']; @@ -194,9 +216,7 @@ const extractProperties = (gltf: GLTF): Properties => { const properties: Properties = {}; - if (nodeOptions) { - properties.nodes = { options: nodeOptions, properties: nodeProperties }; - } + properties.nodes = { options: nodeOptions, properties: nodeProperties }; if (materialOptions) { properties.materials = { @@ -215,6 +235,56 @@ const extractProperties = (gltf: GLTF): Properties => { return properties; }; +function createPropertyChoice( + resource: string, + name: string, + property: string, + index: number +): { text: string; value: any } { + return { + text: `${resource}/${name}/${property}`, + value: `${resource}/${index}/${property}`, + }; +} + +function generateChoicesForProperty(property: ResourceProperties | undefined, resource: keyof typeof resources) { + if (!property) return []; + const choices: { text: string; value: any }[] = []; + + property.options.forEach(({ index, name }) => { + property.properties.forEach((property) => { + choices.push(createPropertyChoice(resource, name, property, index)); + }); + }); + + return choices; +} + +function generateSettableChoices(properties: Properties): Choices { + const choices: { text: string; value: any }[] = []; + + choices.push(...generateChoicesForProperty(properties.nodes, 'nodes')); + + choices.push(...generateChoicesForProperty(properties.materials, 'materials')); + + choices.push(...generateChoicesForProperty(properties.animations, 'animations')); + + return choices; +} + +function generateRaycastableChoices(properties: Properties): Choices { + const choices: { text: string; value: any }[] = []; + + properties.nodes?.options.forEach(({ index, name }) => { + choices.push({ + text: `nodes/${name}`, + value: `nodes/${index}`, + }); + }); + + return choices; +} + export type OnClickCallback = (jsonPath: string) => void; export type OnClickListener = { @@ -300,12 +370,15 @@ const buildSceneModifier = ( applyPropertyToModel(path, gltf, value, properties, setActiveAnimations); }; - const getProperties = () => properties; + const settableChoices = generateSettableChoices(properties); + + const raycastableChoices = generateRaycastableChoices(properties); - const scene: ISceneWithQueries = { + const scene: IScene = { getProperty, setProperty, - getProperties, + getSettableProperties: () => settableChoices, + getRaycastableProperties: () => raycastableChoices, addOnClickedListener, removeOnClickedListener, }; @@ -315,8 +388,8 @@ const buildSceneModifier = ( export type AnimationsState = { [key: string]: boolean }; -const useSceneModifier = (gltf: (GLTF & ObjectMap) | undefined) => { - const [scene, setScene] = useState(); +export const useScene = (gltf: (GLTF & ObjectMap) | undefined) => { + const [scene, setScene] = useState(); const [activeAnimations, setActiveAnimations] = useState({}); const [sceneOnClickListeners, setSceneOnClickListeners] = useState({}); @@ -345,21 +418,9 @@ const useSceneModifier = (gltf: (GLTF & ObjectMap) | undefined) => { } }, [gltf, setSceneOnClickListeners, setAnimationActive]); - const registerProfile = useCallback( - (registry: Registry) => { - if (!scene) return; - registerSharedSceneProfiles(registry, scene); - registerSpecificSceneProfiles(registry, scene); - }, - [scene] - ); - return { scene, animations: activeAnimations, sceneOnClickListeners, - registerSceneProfile: registerProfile, }; }; - -export default useSceneModifier; diff --git a/editor/src/styles/resizer.css b/editor/src/styles/resizer.css old mode 100644 new mode 100755 diff --git a/editor/src/web3/MintWorld.tsx b/editor/src/web3/MintWorld.tsx old mode 100644 new mode 100755 index 1d3d7c6..00562f1 --- a/editor/src/web3/MintWorld.tsx +++ b/editor/src/web3/MintWorld.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { GraphJSON } from '@behave-graph/core'; -import useMintWorld, { MintWorldReturn } from '../hooks/useMintWorld'; +import { useMintWorld, MintWorldReturn } from '@blocktopia/core'; // this allows us to conditionally call useMintWorld with all required arguments passed const MintWorld = ({ @@ -11,7 +11,7 @@ const MintWorld = ({ }: { cid: string; behaviorGraph: GraphJSON; - contractAddress: string; + contractAddress: `0x${string}`; setMintWorld: (mintWorld: MintWorldReturn | null) => void; }) => { const { error, isError, isLoading, isSuccess, mint, mintedTokenId } = useMintWorld({ diff --git a/editor/src/web3/PublishModal.tsx b/editor/src/web3/PublishModal.tsx old mode 100644 new mode 100755 index 35df6f0..c468252 --- a/editor/src/web3/PublishModal.tsx +++ b/editor/src/web3/PublishModal.tsx @@ -2,32 +2,24 @@ import { GraphJSON } from '@behave-graph/core'; import { FC, useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import InteractiveModelPreview from '../scene/InteractiveModelPreview'; -import { useSaveSceneToIpfs } from '../hooks/useSaveSceneToIpfs'; +import { useSaveSceneToIpfs } from '@blocktopia/core'; import useTokenContractAddress from './useTokenContractAddress'; import { MintWorldReturn } from '../hooks/useMintWorld'; import { Modal } from '../flowEditor/components/Modal'; import MintWorld from './MintWorld'; -import { convertURIToHTTPS } from '../hooks/ipfs/ipfsUrlUtils'; +import { convertURIToHTTPS } from '@blocktopia/core/src/hooks/ipfs/ipfsUrlUtils'; import { useNetwork } from 'wagmi'; -import { publicUrl } from '../hooks/useSaveAndLoad'; export type LoadModalProps = { open?: boolean; onClose: () => void; - graphJson: GraphJSON | undefined; + graphJson: GraphJSON; modelFile: File | undefined; }; export const PublishModal: FC = ({ open = false, onClose, graphJson, modelFile }) => { const { cid, saveSceneToIpfs, saving: savingToIpfs } = useSaveSceneToIpfs({ modelFile, behaviorGraph: graphJson }); - const [graphJsonString, setGraphJsonString] = useState(null); - - useEffect(() => { - if (!graphJson) return; - setGraphJsonString(JSON.stringify(graphJson, null, 2)); - }, [graphJson]); - const [startMinting, setStartMinting] = useState(false); const handleMint = useCallback(() => { diff --git a/editor/src/web3/PublishingControls.tsx b/editor/src/web3/PublishingControls.tsx old mode 100644 new mode 100755 index f6affe3..abc24db --- a/editor/src/web3/PublishingControls.tsx +++ b/editor/src/web3/PublishingControls.tsx @@ -30,12 +30,14 @@ const PublishingControls = ({ - setPublishModalOpen(false)} - graphJson={graphJson} - modelFile={modelFile} - /> + {graphJson && ( + setPublishModalOpen(false)} + graphJson={graphJson} + modelFile={modelFile} + /> + )} ); }; diff --git a/editor/src/web3/SaveToIpfsAndMintButton.tsx b/editor/src/web3/SaveToIpfsAndMintButton.tsx old mode 100644 new mode 100755 diff --git a/editor/src/web3/Web3Controls.tsx b/editor/src/web3/Web3Controls.tsx old mode 100644 new mode 100755 diff --git a/editor/src/web3/Web3Login.tsx b/editor/src/web3/Web3Login.tsx old mode 100644 new mode 100755 diff --git a/editor/src/web3/client.ts b/editor/src/web3/client.ts old mode 100644 new mode 100755 index 9fff324..110cd68 --- a/editor/src/web3/client.ts +++ b/editor/src/web3/client.ts @@ -5,7 +5,7 @@ import { infuraProvider } from 'wagmi/providers/infura'; import { publicProvider } from 'wagmi/providers/public'; import { getDefaultWallets } from '@rainbow-me/rainbowkit'; -import contractAddresses from '../contracts/addresses.json'; +import { contractAddresses } from '@blocktopia/core'; type ChainIds = keyof typeof contractAddresses; @@ -29,7 +29,7 @@ const getAllowedChains = () => (Object.keys(contractAddresses) as ChainIds[]).map((chainName) => { if (chainName === 'localhost') return chain.hardhat; - if (chainName === 'skale') return buildSkaleChain(); + // if (chainName === 'skale') return buildSkaleChain(); if (chainName === 'mumbai') return chain.polygonMumbai; diff --git a/editor/src/web3/useTokenContractAddress.ts b/editor/src/web3/useTokenContractAddress.ts old mode 100644 new mode 100755 index c5f420f..1c6bd90 --- a/editor/src/web3/useTokenContractAddress.ts +++ b/editor/src/web3/useTokenContractAddress.ts @@ -1,10 +1,10 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { chain } from 'wagmi'; import { useAccount } from 'wagmi'; -import addresses from '../contracts/addresses.json'; +import { contractAddresses } from '@blocktopia/core'; import { buildSkaleChain } from './client'; -const getContractAddress = (chainId: number): string => { +const getContractAddress = (chainId: number): `0x${string}` => { const skaleChain = buildSkaleChain(); const chains = [...Object.values(chain), skaleChain]; @@ -24,7 +24,7 @@ const getContractAddress = (chainId: number): string => { chainName = 'mumbai'; } - const genericAddresses = addresses as { [address: string]: string }; + const genericAddresses = contractAddresses as { [address: string]: `0x${string}` }; if (!genericAddresses[chainName]) throw new Error(`contract not deployed for chain ${chainName}`); @@ -34,7 +34,7 @@ const getContractAddress = (chainId: number): string => { const useTokenContractAddress = () => { const { connector: activeConnector, isConnected } = useAccount(); - const [contractAddress, setContractAddress] = useState(null); + const [contractAddress, setContractAddress] = useState<`0x${string}` | null>(null); useEffect(() => { (async () => { diff --git a/editor/tailwind.config.cjs b/editor/tailwind.config.cjs old mode 100644 new mode 100755 diff --git a/editor/tsconfig.json b/editor/tsconfig.json old mode 100644 new mode 100755 index d487bd3..f1ea168 --- a/editor/tsconfig.json +++ b/editor/tsconfig.json @@ -15,5 +15,6 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["./src", "./typings/*.d.ts"] + "include": ["./src", "./typings/*.d.ts", "../packages/core/src"], + "exclude": ["node_modules", "dist"] } diff --git a/editor/vite.config.ts b/editor/vite.config.ts old mode 100644 new mode 100755 diff --git a/hardhat.config.ts b/hardhat.config.ts old mode 100644 new mode 100755 index b2e26b9..1dd1415 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -6,7 +6,11 @@ config({ path: __dirname + '/.env' }); const { ALCHEMY_API_KEY, GOERLI_PRIVATE_KEY, MUMBAI_PRIVATE_KEY, SKALE_PRIVATE_KEY } = process.env; -let networks = {}; +let networks: any = { + hardhat: { + allowUnlimitedContractSize: true, + }, +}; if (SKALE_PRIVATE_KEY) { networks = { diff --git a/jest.config.js b/jest.config.js old mode 100644 new mode 100755 diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 24809d2..b34b714 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@behavior-graph/monorepo", + "name": "@blocktopia/monorepo", "version": "0.0.1", "description": "On-chain metaverse behavior graph builder", "private": true, @@ -8,27 +8,28 @@ "graph", "flow" ], - "homepage": "http://github.com/bhouston/behave-graph", + "homepage": "http://github.com/oveddan/blocktopia", "author": "behave-graph authors", "bugs": { - "url": "https://github.com/bhouston/behave-graph/issues" + "url": "https://github.com/oveddan/blocktopia/issues" }, "license": "ISC", "repository": { "type": "git", - "url": "https://github.com/bhouston/behave-graph" + "url": "https://github.com/oveddan/blocktopia" }, "devDependencies": { - "@openzeppelin/contracts": "^4.7.3", - "@preconstruct/cli": "^2.2.2", - "@typechain/ethers-v5": "^10.1.1", - "@typechain/hardhat": "^6.1.4", "@babel/core": "^7.17.10", "@babel/preset-env": "^7.17.10", "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.16.7", + "@openzeppelin/contracts": "^4.7.3", + "@preconstruct/cli": "^2.2.2", + "@typechain/ethers-v5": "^10.1.1", + "@typechain/hardhat": "^6.1.4", "dotenv": "^16.0.3", "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0", "ts-node": ">=8.0.0", "typechain": "^8.1.1", "typescript": ">=4.5.0" @@ -37,11 +38,17 @@ "undici": "5.5.1" }, "scripts": { - "build-editor": "yarn workspace @behavior-graph/editor build", + "build-editor": "yarn workspace @blocktopia/editor build", "chain": "npx hardhat node", "compile": "npx hardhat compile", - "test": "npx hardhat test", - "start": "yarn workspace @behavior-graph/editor dev", + "recompile": "rm -r cache && rm -r artifacts && rm -r typechain-types && yarn compile", + "test": "yarn test-core && yarn test-contract", + "test-core": "yarn workspace @blocktopia/core test", + "test-contract": "npx hardhat test", + "build": "preconstruct build", + "dev": "preconstruct dev", + "postinstall": "preconstruct dev", + "start": "yarn workspace @blocktopia/editor dev", "lint": "npx eslint \"src/**/*.{ts,json}\"", "lint:fix": "npm run lint -- --fix", "exec-graph": "cd framework && yarn exec-graph", @@ -57,7 +64,7 @@ ], "preconstruct": { "packages": [ - "editor" + "packages/*" ] } } diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100755 index 0000000..3bde88f --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,36 @@ +{ + "name": "@blocktopia/core", + "version": "0.0.1", + "description": "", + "source": "src/index.ts", + "main": "dist/blocktopia-core.cjs.js", + "module": "dist/blocktopia-core.esm.js", + "types": "dist/blocktopia-core.cjs.d.ts", + "sideEffects": false, + "preconstruct": { + "entrypoints": [ + "index.ts" + ] + }, + "scripts": { + "test": "mocha --require ts-node/register --require tsconfig-paths/register --recursive 'src/**/*.test.ts'", + "test-watch": "yarn test --watch" + }, + "dependencies": { + "@behave-graph/core": "^0.0.1", + "web3.storage": "^4.4.0" + }, + "author": "", + "license": "ISC", + "peerDependencies": { + "ethers": "^5.7.2", + "react": "^18.2.0", + "wagmi": "^0.8.10" + }, + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", + "chai": "^4.3.7", + "mocha": "^10.2.0" + } +} diff --git a/packages/core/src/abstractions.ts b/packages/core/src/abstractions.ts new file mode 100755 index 0000000..7c0ac61 --- /dev/null +++ b/packages/core/src/abstractions.ts @@ -0,0 +1,16 @@ +import { IScene } from '@behave-graph/core'; + +type IntVariableHandler = (value: bigint) => void; + +export interface IChainGraph { + invoke: (invokeId: number) => void; + registerIntVariableValueListener: (id: string, cb: IntVariableHandler) => void; + unRegisterIntVariableValueListener: (id: string, cb: IntVariableHandler) => void; +} + +export interface IVariableStore { + get: (key: string) => any; + set: (key: string, value: any) => void; + // registerOnValueChangedListener: (id: string, cb: (value: any) => void) => void; + // removeOnValueChangedListener: (id: string, cb: (count: bigint) => void) => void; +} diff --git a/packages/core/src/contracts/abi.ts b/packages/core/src/contracts/abi.ts new file mode 100755 index 0000000..d3d98e0 --- /dev/null +++ b/packages/core/src/contracts/abi.ts @@ -0,0 +1,917 @@ +export const abi = [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "_variableId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bool", + "name": "value", + "type": "bool" + } + ], + "name": "BoolVariableUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "_variableId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "int256", + "name": "value", + "type": "int256" + } + ], + "name": "IntVariableUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "toNode", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "uri", + "type": "string" + }, + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "id", + "type": "string" + }, + { + "internalType": "enum NodeType", + "name": "nodeType", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "defined", + "type": "bool" + }, + { + "internalType": "enum ValueType", + "name": "inputValueType", + "type": "uint8" + } + ], + "internalType": "struct NodeDefinition", + "name": "definition", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "bool", + "name": "value", + "type": "bool" + }, + { + "internalType": "uint8", + "name": "socket", + "type": "uint8" + } + ], + "internalType": "struct BooleanValueAndLabel[]", + "name": "booleans", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int256", + "name": "value", + "type": "int256" + }, + { + "internalType": "uint8", + "name": "socket", + "type": "uint8" + } + ], + "internalType": "struct IntValueAndLabel[]", + "name": "integers", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "internalType": "uint8", + "name": "socket", + "type": "uint8" + } + ], + "internalType": "struct StringValueAndLabel[]", + "name": "strings", + "type": "tuple[]" + } + ], + "internalType": "struct InitialValues", + "name": "initialValues", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "variableId", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "invocationId", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "invocationNameDefined", + "type": "bool" + }, + { + "internalType": "bool", + "name": "variableIdDefined", + "type": "bool" + } + ], + "internalType": "struct NodeConfig", + "name": "config", + "type": "tuple" + } + ], + "indexed": false, + "internalType": "struct NodeDefinitionAndValues[]", + "name": "nodes", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "fromNode", + "type": "string" + }, + { + "internalType": "string", + "name": "toNode", + "type": "string" + }, + { + "internalType": "uint8", + "name": "fromSocket", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "toSocket", + "type": "uint8" + } + ], + "indexed": false, + "internalType": "struct EdgeDefinition[]", + "name": "edges", + "type": "tuple[]" + } + ], + "name": "SafeMint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSocketIndecesByNodeType", + "outputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint8", + "name": "outputFlowSocket", + "type": "uint8" + } + ], + "internalType": "struct ExternalInvokeIndeces", + "name": "externalInvoke", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "inputFlow", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "outputCount", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "outputFlow", + "type": "uint8" + } + ], + "internalType": "struct CounterSocketIndeces", + "name": "counter", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "input1", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "input2", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "result", + "type": "uint8" + } + ], + "internalType": "struct Int2Out1SocketIndeces", + "name": "add", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "inputFlow", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "inputVal", + "type": "uint8" + } + ], + "internalType": "struct VariableSetIndeces", + "name": "variableSet", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "inputFlow", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "outputGateTrue", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "outputGateFalse", + "type": "uint8" + } + ], + "internalType": "struct GateSocketIndeces", + "name": "gate", + "type": "tuple" + } + ], + "internalType": "struct SocketIndecesByNodeType", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "invocationName", + "type": "uint8" + } + ], + "name": "invoke", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "sceneUri", + "type": "string" + }, + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "id", + "type": "string" + }, + { + "internalType": "enum NodeType", + "name": "nodeType", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "defined", + "type": "bool" + }, + { + "internalType": "enum ValueType", + "name": "inputValueType", + "type": "uint8" + } + ], + "internalType": "struct NodeDefinition", + "name": "definition", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "bool", + "name": "value", + "type": "bool" + }, + { + "internalType": "uint8", + "name": "socket", + "type": "uint8" + } + ], + "internalType": "struct BooleanValueAndLabel[]", + "name": "booleans", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int256", + "name": "value", + "type": "int256" + }, + { + "internalType": "uint8", + "name": "socket", + "type": "uint8" + } + ], + "internalType": "struct IntValueAndLabel[]", + "name": "integers", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "internalType": "uint8", + "name": "socket", + "type": "uint8" + } + ], + "internalType": "struct StringValueAndLabel[]", + "name": "strings", + "type": "tuple[]" + } + ], + "internalType": "struct InitialValues", + "name": "initialValues", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "variableId", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "invocationId", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "invocationNameDefined", + "type": "bool" + }, + { + "internalType": "bool", + "name": "variableIdDefined", + "type": "bool" + } + ], + "internalType": "struct NodeConfig", + "name": "config", + "type": "tuple" + } + ], + "internalType": "struct NodeDefinitionAndValues[]", + "name": "_nodes", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "string", + "name": "fromNode", + "type": "string" + }, + { + "internalType": "string", + "name": "toNode", + "type": "string" + }, + { + "internalType": "uint8", + "name": "fromSocket", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "toSocket", + "type": "uint8" + } + ], + "internalType": "struct EdgeDefinition[]", + "name": "_edges", + "type": "tuple[]" + } + ], + "name": "safeMint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] as const; \ No newline at end of file diff --git a/packages/core/src/contracts/addresses.json b/packages/core/src/contracts/addresses.json new file mode 100755 index 0000000..ffc1063 --- /dev/null +++ b/packages/core/src/contracts/addresses.json @@ -0,0 +1,4 @@ +{ + "localhost": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", + "mumbai": "0xbefC97Db79157D8AaAedD5B1E6759DCA5B68dF50" +} \ No newline at end of file diff --git a/packages/core/src/contracts/contractAddresses.ts b/packages/core/src/contracts/contractAddresses.ts new file mode 100755 index 0000000..6af7c14 --- /dev/null +++ b/packages/core/src/contracts/contractAddresses.ts @@ -0,0 +1,4 @@ +export const contractAddresses = { + "localhost": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", + "mumbai": "0xbefC97Db79157D8AaAedD5B1E6759DCA5B68dF50" +}; \ No newline at end of file diff --git a/packages/core/src/hooks/index.ts b/packages/core/src/hooks/index.ts new file mode 100755 index 0000000..7e6e6ac --- /dev/null +++ b/packages/core/src/hooks/index.ts @@ -0,0 +1,7 @@ +export * from './useChainGraph'; +export * from './useMockSmartContractActions'; +export * from './useLoadOnChainWorld'; +export * from './useMintWorld'; +export * from './useMockSmartContractActions'; +export * from './useSaveAndLoad'; +export * from './useSaveSceneToIpfs'; diff --git a/editor/src/hooks/ipfs/ipfsInteractiveWorldSaver.ts b/packages/core/src/hooks/ipfs/ipfsInteractiveWorldSaver.ts old mode 100644 new mode 100755 similarity index 100% rename from editor/src/hooks/ipfs/ipfsInteractiveWorldSaver.ts rename to packages/core/src/hooks/ipfs/ipfsInteractiveWorldSaver.ts diff --git a/editor/src/hooks/ipfs/ipfsSceneLoader.ts b/packages/core/src/hooks/ipfs/ipfsSceneLoader.ts old mode 100644 new mode 100755 similarity index 100% rename from editor/src/hooks/ipfs/ipfsSceneLoader.ts rename to packages/core/src/hooks/ipfs/ipfsSceneLoader.ts diff --git a/editor/src/hooks/ipfs/ipfsUrlUtils.ts b/packages/core/src/hooks/ipfs/ipfsUrlUtils.ts old mode 100644 new mode 100755 similarity index 79% rename from editor/src/hooks/ipfs/ipfsUrlUtils.ts rename to packages/core/src/hooks/ipfs/ipfsUrlUtils.ts index 029d1c4..2d0774f --- a/editor/src/hooks/ipfs/ipfsUrlUtils.ts +++ b/packages/core/src/hooks/ipfs/ipfsUrlUtils.ts @@ -1,6 +1,3 @@ -import { useEffect, useState } from 'react'; -import { FileLocationKindLocal, FileLocationLocal, SceneFilesLocal, FileReference, Optional } from './types'; - function convertURIToHTTPSInner({ url, ipfsHost = 'https://ipfs.io' }: { url: string | undefined; ipfsHost?: string }) { if (!url) return undefined; if (url.startsWith('ipfs://')) { diff --git a/editor/src/hooks/ipfs/types.ts b/packages/core/src/hooks/ipfs/types.ts old mode 100644 new mode 100755 similarity index 100% rename from editor/src/hooks/ipfs/types.ts rename to packages/core/src/hooks/ipfs/types.ts diff --git a/editor/src/hooks/ipfs/web3Storage.ts b/packages/core/src/hooks/ipfs/web3Storage.ts old mode 100644 new mode 100755 similarity index 100% rename from editor/src/hooks/ipfs/web3Storage.ts rename to packages/core/src/hooks/ipfs/web3Storage.ts diff --git a/editor/src/onChainWorld/useSmartContractActions.ts b/packages/core/src/hooks/useChainGraph.ts old mode 100644 new mode 100755 similarity index 65% rename from editor/src/onChainWorld/useSmartContractActions.ts rename to packages/core/src/hooks/useChainGraph.ts index af9c579..1897e4c --- a/editor/src/onChainWorld/useSmartContractActions.ts +++ b/packages/core/src/hooks/useChainGraph.ts @@ -1,12 +1,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useContract, useContractEvent, useSigner } from 'wagmi'; -import { abi } from '../contracts/abi'; import { BigNumber } from 'ethers'; -import { ISmartContractActions } from '../abstractions'; +import { abi } from '../contracts/abi'; +import { IChainGraph } from '../abstractions'; type hn = { [id: string]: (count: bigint) => void }; -const useSmartContractActions = (contractAddress: string, tokenId: number) => { +const useChainGraph = (contractAddress: `0x${string}`, tokenId: number) => { const { data: signer } = useSigner(); const contract = useContract({ @@ -32,16 +32,16 @@ const useSmartContractActions = (contractAddress: string, tokenId: number) => { useContractEvent({ address: contractAddress, abi, - eventName: 'ActionExecuted', - listener: (executerAddress, actionTokenId, nodeId, actionCount) => { + eventName: 'SafeMint', + listener: (actionTokenId, owner, nodeId, value) => { if (tokenId !== actionTokenId.toNumber()) return; const handler = actionExecutedHandlers.current[nodeId]; - if (handler) handler(BigInt(actionCount.toNumber())); + if (handler) handler(BigInt(actionTokenId.toNumber())); }, }); - const getActionCount = useCallback( + const getIntVariableValue = useCallback( async (id: string, connectedContract: typeof contract) => { console.log('no connected contract yet'); if (!connectedContract) return; @@ -61,36 +61,36 @@ const useSmartContractActions = (contractAddress: string, tokenId: number) => { console.log('got connected contract', actionExecutedHandlers.current); Object.entries(actionExecutedHandlers.current).forEach(async ([action, handler]) => { - const actionCount = await getActionCount(action, connectedContract); + const actionCount = await getIntVariableValue(action, connectedContract); console.log('connected now executing', actionCount); handler(BigInt(actionCount || 0)); }); - }, [connectedContract, getActionCount]); + }, [connectedContract, getIntVariableValue]); - const registerTriggerHandler = useCallback( + const registerIntVariableValueListener = useCallback( async (id: string, cb: (count: bigint) => void) => { actionExecutedHandlers.current[id] = cb; if (!connectedContract) return; console.log('setting trigger handler', actionExecutedHandlers.current); - const actionCount = await getActionCount(id, connectedContract); + const actionCount = await getIntVariableValue(id, connectedContract); if (actionCount) { cb(BigInt(actionCount)); } }, - [getActionCount, connectedContract] + [getIntVariableValue, connectedContract] ); - const unRegisterTriggerHandler = useCallback((id: string, cb: (count: bigint) => void) => { + const unRegisterIntVariableValueListener = useCallback((id: string, cb: (count: bigint) => void) => { delete actionExecutedHandlers.current[id]; }, []); - const invoke = useCallback( - async (actionId: string, connectedContract: typeof contract) => { + const trigger = useCallback( + async (invokeId: number, connectedContract: typeof contract) => { if (!connectedContract) return; - const transaction = await connectedContract.executeAction(BigNumber.from(tokenId), actionId); + const transaction = await connectedContract.trigger(BigNumber.from(invokeId)); await transaction.wait(); }, @@ -99,19 +99,19 @@ const useSmartContractActions = (contractAddress: string, tokenId: number) => { const smartContractAction = useMemo(() => { if (!connectedContract) return; - const result: ISmartContractActions = { - invoke: (actionId: string) => { + const result: IChainGraph = { + invoke: (invokeId: number) => { if (!connectedContract) return; - invoke(actionId, connectedContract); + trigger(invokeId, connectedContract); }, - registerTriggerHandler, - unRegisterTriggerHandler, + registerIntVariableValueListener, + unRegisterIntVariableValueListener, }; return result; - }, [invoke, registerTriggerHandler, unRegisterTriggerHandler, connectedContract]); + }, [trigger, registerIntVariableValueListener, unRegisterIntVariableValueListener, connectedContract]); return smartContractAction; }; -export default useSmartContractActions; +export default useChainGraph; diff --git a/editor/src/hooks/useLoadOnChainWorld.ts b/packages/core/src/hooks/useLoadOnChainWorld.ts old mode 100644 new mode 100755 similarity index 90% rename from editor/src/hooks/useLoadOnChainWorld.ts rename to packages/core/src/hooks/useLoadOnChainWorld.ts index 89d8a5a..3ab6765 --- a/editor/src/hooks/useLoadOnChainWorld.ts +++ b/packages/core/src/hooks/useLoadOnChainWorld.ts @@ -1,12 +1,12 @@ import { useEffect, useMemo, useState } from 'react'; import { useContractRead } from 'wagmi'; -import { abi } from '../contracts/abi'; import { BigNumber } from 'ethers'; import { loadStoredSceneAndBehaviorGraphFromIpfs } from './ipfs/ipfsSceneLoader'; import { ipfsUrlToCid } from './ipfs/ipfsUrlUtils'; import { GraphJSON } from '@behave-graph/core'; +import { abi } from '../contracts/abi'; -const useLoadOnChainWorld = (tokenId: number, contractAddress: string) => { +export const useLoadOnChainWorld = (tokenId: number, contractAddress: `0x${string}`) => { const tokenIdArgs = useMemo((): [BigNumber] => [BigNumber.from(tokenId)], [tokenId]); const { data: tokenURI } = useContractRead({ @@ -39,5 +39,3 @@ const useLoadOnChainWorld = (tokenId: number, contractAddress: string) => { graphJson, }; }; - -export default useLoadOnChainWorld; diff --git a/packages/core/src/hooks/useMintWorld.ts b/packages/core/src/hooks/useMintWorld.ts new file mode 100755 index 0000000..16609d5 --- /dev/null +++ b/packages/core/src/hooks/useMintWorld.ts @@ -0,0 +1,118 @@ +import { GraphJSON } from '@behave-graph/core'; +import { useCallback, useEffect, useState } from 'react'; +import { usePrepareContractWrite, useContractWrite, useContractEvent, useContractRead } from 'wagmi'; +import { abi } from '../contracts/abi'; +import { generateOnChainNodesFromGraph, SafeMintInputs, SocketIndecesByNodeType } from '../nodes'; + +/** Generates arguments to mint a world. Converts the graph definition to on-chain nodes and edges. */ +const toMintArgs = ({ + cid, + behaviorGraph, + socketIndecesByNodeType, +}: { + cid: string; + behaviorGraph: GraphJSON; + socketIndecesByNodeType: SocketIndecesByNodeType | undefined; +}): SafeMintInputs => { + // convert chain nodes to on chain node defininitions + if (!socketIndecesByNodeType) return [cid, [], []]; + const { nodeDefinitions, edgeDefinitions } = generateOnChainNodesFromGraph({ + graph: behaviorGraph, + socketIndecesByNodeType, + }); + + const result: SafeMintInputs = [cid, nodeDefinitions, edgeDefinitions]; + + return result; +}; +const useWaitForMintedTokenWithContentUri = ({ + contractAddress, + cid, +}: { + contractAddress: `0x${string}`; + cid: string; +}) => { + const [mintedTokenId, setMintedTokenId] = useState(null); + + useContractEvent({ + address: contractAddress, + abi: abi, + eventName: 'SafeMint', + listener(tokenId, to, uri, nodes) { + // hack - if this was minted with the proper cid, we can assume this was the token. + if (uri === cid) { + setMintedTokenId(tokenId.toNumber()); + } + }, + }); + + return mintedTokenId; +}; + +export const useMintWorld = ({ + worldCid, + contractAddress, + behaviorGraph, +}: { + contractAddress: `0x${string}`; + worldCid: string; + behaviorGraph: GraphJSON; +}) => { + const { + data: socketIndecesByNodeType, + error: readError, + isLoading: readIsLoading, + } = useContractRead({ + address: contractAddress, + abi, + functionName: 'getSocketIndecesByNodeType', + }); + + const [args, setArgs] = useState(() => + toMintArgs({ + cid: worldCid, + behaviorGraph, + socketIndecesByNodeType, + }) + ); + + useEffect(() => { + const args = toMintArgs({ + cid: worldCid, + behaviorGraph, + socketIndecesByNodeType, + }); + console.log({ + args: { + cid: args[0], + nodes: args[1], + edges: args[2], + }, + }); + setArgs(args); + }, [worldCid, behaviorGraph, socketIndecesByNodeType]); + + const { config, error, isError } = usePrepareContractWrite({ + address: contractAddress, + abi, + functionName: 'safeMint', + args, + }); + + const { isLoading, isSuccess, write } = useContractWrite({ + ...config, + }); + + const mintedTokenId = useWaitForMintedTokenWithContentUri({ + contractAddress, + cid: worldCid, + }); + + const mint = useCallback(() => { + if (write) write(); + }, [write]); + + return { mint, isSuccess, isLoading, isError, error, mintedTokenId }; +}; + +export type MintWorldReturn = ReturnType; diff --git a/packages/core/src/hooks/useMockSmartContractActions.ts b/packages/core/src/hooks/useMockSmartContractActions.ts new file mode 100755 index 0000000..2ec17dd --- /dev/null +++ b/packages/core/src/hooks/useMockSmartContractActions.ts @@ -0,0 +1,48 @@ +import { useCallback, useMemo, useRef } from 'react'; +import { IChainGraph } from '../abstractions'; + +type hn = { [id: string]: (count: bigint) => void }; + +export const makeEmptySmartContractActions = (): IChainGraph => ({ + invoke: () => {}, + registerIntVariableValueListener: () => {}, + unRegisterIntVariableValueListener: () => {}, +}); + +export const useMockSmartContractActions = () => { + const actionExecutedHandlers = useRef({}); + + const mockCounts = useRef<{ [id: string]: number }>({}); + + const registerIntVariableValueListener = useCallback((id: string, cb: (count: bigint) => void) => { + actionExecutedHandlers.current[id] = cb; + }, []); + + const unRegisterIntVariableValueListener = useCallback((id: string, cb: (count: bigint) => void) => { + delete actionExecutedHandlers.current[id]; + }, []); + + const invoke = useCallback(async (actionId: string) => { + const newCount = (mockCounts.current[actionId] || 0) + 1; + mockCounts.current[actionId] = newCount; + + const handler = actionExecutedHandlers.current[actionId]; + if (handler) { + handler(BigInt(newCount)); + } + }, []); + + const trigger = useCallback(() => {}, []); + + const smartContractAction = useMemo(() => { + const result: IChainGraph = { + invoke: trigger, + registerIntVariableValueListener, + unRegisterIntVariableValueListener, + }; + + return result; + }, [invoke, registerIntVariableValueListener, unRegisterIntVariableValueListener]); + + return smartContractAction; +}; diff --git a/packages/core/src/hooks/useSaveAndLoad.ts b/packages/core/src/hooks/useSaveAndLoad.ts new file mode 100755 index 0000000..696af67 --- /dev/null +++ b/packages/core/src/hooks/useSaveAndLoad.ts @@ -0,0 +1,53 @@ +import { GraphJSON } from '@behave-graph/core'; + +function readFileContents(file: File) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onabort = () => console.log('file reading was aborted'); + reader.onerror = () => console.log('file reading has failed'); + reader.onload = () => { + // Do whatever you want with the file contents + const binaryStr = reader.result; + + if (!binaryStr) reject('no binary string'); + else resolve(binaryStr); + }; + reader.readAsArrayBuffer(file); + }); +} + +export const dataUrlFromFile = async (file: File) => { + const fileContents = await readFileContents(file); + if (fileContents) { + if (typeof fileContents === 'string') { + return fileContents; + } else { + const blobUrl = URL.createObjectURL(new Blob([fileContents])); + + return blobUrl; + } + } +}; + +export const emptyGraphJson = (): GraphJSON => ({}); + +export type ModelFile = + | { + fileUrl: string; + fileType: 'url'; + fileContents: undefined; + } + | { + fileUrl: undefined; + fileType: 'uploaded'; + fileContents: string; + }; + +export const fetchModelFile = async (url: string, fileName: string) => { + const blob = await (await fetch(url)).blob(); + + const file = new File([blob], fileName); + + return file; +}; diff --git a/editor/src/hooks/useSaveSceneToIpfs.ts b/packages/core/src/hooks/useSaveSceneToIpfs.ts old mode 100644 new mode 100755 similarity index 100% rename from editor/src/hooks/useSaveSceneToIpfs.ts rename to packages/core/src/hooks/useSaveSceneToIpfs.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100755 index 0000000..32fa994 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,5 @@ +export * from './nodes'; +export * from './abstractions'; +export * from './hooks'; +export * from './contracts/abi'; +export * from './contracts/contractAddresses'; diff --git a/packages/core/src/nodes/chain/IChainNode.ts b/packages/core/src/nodes/chain/IChainNode.ts new file mode 100755 index 0000000..eb9ce2a --- /dev/null +++ b/packages/core/src/nodes/chain/IChainNode.ts @@ -0,0 +1,113 @@ +import { + IHasTriggered, + INodeDefinition, + NodeConfiguration, + NodeConfigurationDescription, + Socket, + SocketNames, + SocketsDefinition, +} from '@behave-graph/core'; +import { ExtractAbiFunction, AbiParametersToPrimitiveTypes } from 'abitype'; +import { + CounterSocketIndecesStruct, + SocketIndecesByNodeTypeStruct, +} from 'typechain-types/contracts/BehaviorGraphToken'; +import { abi } from '../../contracts/abi'; + +type SafeMintFunction = ExtractAbiFunction; + +export type SafeMintInputs = AbiParametersToPrimitiveTypes; + +export type ChainNodeDefinitionAndValues = SafeMintInputs[1][0]; +export type ChainEdgeNodeDefinition = SafeMintInputs[2][0]; + +export type ChainNodeDefinition = ChainNodeDefinitionAndValues['definition']; +export type ChainnInitialValues = ChainNodeDefinitionAndValues['initialValues']; + +export type ChainNodeSpec = Pick; + +type SocketIndecesByNodeTypeFunction = ExtractAbiFunction; +export type SocketIndecesByNodeType = AbiParametersToPrimitiveTypes[0]; + +export enum ChainNodeTypes { + ExternalInvoke = 0, + Counter = 1, + Add = 2, + Gate = 3, + VariableSet = 4, +} + +export enum ChainValueType { + Int = 0, + Bool = 1, + NotAVariable = 2, +} + +export type ChainNodeConfig = ChainNodeDefinitionAndValues['config']; + +export const emptyNodeConfig: ChainNodeConfig = { + invocationId: 0, + invocationNameDefined: false, + variableId: 0, + variableIdDefined: false, +}; + +export type SocketMappings< + TSockets extends SocketsDefinition, + TSocketIdKey extends keyof SocketIndecesByNodeTypeStruct +> = { + [key in SocketNames]?: keyof SocketIndecesByNodeType[TSocketIdKey]; +}; + +export type ToOnChainDefinitionForNode< + TInput extends SocketsDefinition = SocketsDefinition, + TOutput extends SocketsDefinition = SocketsDefinition, + TSocketIdKey extends keyof SocketIndecesByNodeTypeStruct = 'externalInvoke' +> = { + nodeType: ChainNodeTypes; + inputValueType: ChainValueType; + socketIdKey: TSocketIdKey; + getConfig?: (config: NodeConfiguration | undefined) => Partial; + socketMappings: { + out?: SocketMappings; + in?: SocketMappings; + }; +}; + +type SocketIdValue = CounterSocketIndecesStruct['inputFlow']; + +export type SocketIds = Record; + +export interface IHasOnChainDefinition< + TInput extends SocketsDefinition = SocketsDefinition, + TOutput extends SocketsDefinition = SocketsDefinition, + TSocketIdKey extends keyof SocketIndecesByNodeTypeStruct = any +> { + chain: ToOnChainDefinitionForNode; +} + +export const makeChainSocketMapping = < + TInput extends SocketsDefinition, + TOutput extends SocketsDefinition, + TConfig extends NodeConfigurationDescription, + TSocketIdKey extends keyof SocketIndecesByNodeTypeStruct +>( + definition: INodeDefinition, + chain: ToOnChainDefinitionForNode +) => ({ + ...definition, + chain, +}); + +export interface IHasOnChainTrigger + extends IHasTriggered {} + +export type ChainNodeDefinitions = { + [key in ChainNodeTypes]: {}; +}; + +export class SocketWithChainIndex extends Socket { + constructor(valueType: string, name: string, public readonly chainIndex: number) { + super(valueType, name, chainIndex); + } +} diff --git a/packages/core/src/nodes/chain/InvokeOnChainAction.ts b/packages/core/src/nodes/chain/InvokeOnChainAction.ts new file mode 100755 index 0000000..7b6af09 --- /dev/null +++ b/packages/core/src/nodes/chain/InvokeOnChainAction.ts @@ -0,0 +1,47 @@ +import { makeFlowNodeDefinition, NodeCategory } from '@behave-graph/core'; +import { IChainGraph } from '../../abstractions'; +import { ChainNodeTypes, ChainValueType, makeChainSocketMapping } from './IChainNode'; +import { chainGraphDependencyKey } from './OnChainVariableGet'; + +export const externalTriggerNodeTypeName = 'chain/invokeAction'; + +/** + * Interface to the outside world from the on-chain graph; Allows for invoking the graph from the outside. + * @param chainGraph + * @returns + */ +const local = makeFlowNodeDefinition({ + typeName: externalTriggerNodeTypeName, + category: NodeCategory.Flow, + label: 'Invoke Action', + configuration: { + invokeId: { + valueType: 'number', + }, + }, + initialState: undefined, + in: { + flow: 'flow', + }, + out: { + flow: 'flow', + }, + triggered: ({ configuration, graph: { getDependency } }) => { + // todo: how do we handle needing a node id? + const chainGraph = getDependency(chainGraphDependencyKey); + chainGraph.invoke(configuration.invokeId); + + return undefined; + }, +}); + +export const InvokeOnChainAction = makeChainSocketMapping(local, { + nodeType: ChainNodeTypes.ExternalInvoke, + inputValueType: ChainValueType.NotAVariable, + socketIdKey: 'externalInvoke', + socketMappings: { + out: { + flow: 'outputFlowSocket', + }, + }, +}); diff --git a/packages/core/src/nodes/chain/OnChainCounter.ts b/packages/core/src/nodes/chain/OnChainCounter.ts new file mode 100755 index 0000000..d64ffd1 --- /dev/null +++ b/packages/core/src/nodes/chain/OnChainCounter.ts @@ -0,0 +1,37 @@ +import { makeFlowNodeDefinition, NodeCategory } from '@behave-graph/core'; +import { ChainNodeTypes, ChainValueType, makeChainSocketMapping } from './IChainNode'; + +const chainCounterLocal = makeFlowNodeDefinition({ + typeName: 'chain/counter', + category: NodeCategory.Flow, + label: 'Chain Counter', + initialState: undefined, + in: { + flow: 'flow', + }, + out: { + flow: 'flow', + count: 'integer', + }, + triggered: () => { + return undefined; + }, +}); + +/** + * An on-chain counter that increments on each flow. + */ +export const OnChainCounter = makeChainSocketMapping(chainCounterLocal, { + nodeType: ChainNodeTypes.Counter, + inputValueType: ChainValueType.Int, + socketIdKey: 'counter', + socketMappings: { + in: { + flow: 'inputFlow', + }, + out: { + flow: 'outputFlow', + count: 'outputCount', + }, + }, +}); diff --git a/packages/core/src/nodes/chain/OnChainVariableGet.ts b/packages/core/src/nodes/chain/OnChainVariableGet.ts new file mode 100755 index 0000000..cb38f79 --- /dev/null +++ b/packages/core/src/nodes/chain/OnChainVariableGet.ts @@ -0,0 +1,65 @@ +import { makeEventNodeDefinition, NodeCategory, Variable } from '@behave-graph/core'; +import { IChainGraph } from '../../abstractions'; +import { ChainNodeTypes, ChainValueType, makeChainSocketMapping } from './IChainNode'; + +const smartActionInvokedTypeName = 'chain/intVariableGet'; +export const variableNameSocket = 'variableName'; +export const valueSocketName = 'value'; +const flowSocketName = 'flow'; + +type State = { + handleValueUpdated?: (count: bigint) => void; + variableId?: string; +}; + +const makeInitialState = (): State => ({}); + +export const chainGraphDependencyKey = 'chainGraph'; + +/** + * Listens for chainges to an on-chain variable, and write the value to the output. + * Commits the flow on each change. + * This node isn't written tot he blockchain, as it just fetches the value via the subgraph. + * @param smartContractActions + * @returns + */ +export const OnChainVariableGet = makeEventNodeDefinition({ + typeName: smartActionInvokedTypeName, + category: NodeCategory.Variable, + label: 'Get On Chain Int Value', + configuration: { + variableId: { + valueType: 'number', + }, + }, + initialState: makeInitialState(), + init: ({ write, commit, configuration, graph: { getDependency } }) => { + // const variable = variables[configuration.variableId] || new Variable('-1', 'undefined', 'string', ''); + const variableId = configuration.variableId || 1; + + const handleValueUpdated = (count: bigint) => { + write(valueSocketName, count); + commit(flowSocketName); + }; + const smartContractActions = getDependency(chainGraphDependencyKey); + smartContractActions.registerIntVariableValueListener(variableId, handleValueUpdated); + + return { + handleValueUpdated, + variableId, + }; + }, + dispose: ({ state, graph: { getDependency } }) => { + if (state.handleValueUpdated && state.variableId) { + const smartContractActions = getDependency(chainGraphDependencyKey); + smartContractActions.unRegisterIntVariableValueListener(state.variableId, state.handleValueUpdated); + } + + return {}; + }, + in: {}, + out: { + [flowSocketName]: 'flow', + [valueSocketName]: 'integer', + }, +}); diff --git a/packages/core/src/nodes/chain/OnChainVariableSet.ts b/packages/core/src/nodes/chain/OnChainVariableSet.ts new file mode 100755 index 0000000..ca530df --- /dev/null +++ b/packages/core/src/nodes/chain/OnChainVariableSet.ts @@ -0,0 +1,42 @@ +import { makeFlowNodeDefinition, NodeCategory } from '@behave-graph/core'; +import { ChainNodeTypes, ChainValueType, makeChainSocketMapping } from './IChainNode'; + +export const chainVariableSet = makeFlowNodeDefinition({ + typeName: 'chain/intVariableSet', + category: NodeCategory.Variable, + label: 'Set On Chain Int Value', + configuration: { + variableId: { + valueType: 'number', + }, + }, + initialState: undefined, + in: { + flow: 'flow', + value: 'integer', + }, + out: {}, + triggered: () => { + // doesnt actually do anything underneath, just triggers the flow on chain + return undefined; + }, +}); + +/** + * Sets an on-chain variable to a value. + */ +export const OnChainVariableSet = makeChainSocketMapping(chainVariableSet, { + nodeType: ChainNodeTypes.VariableSet, + inputValueType: ChainValueType.Int, + getConfig: (configuration) => ({ + variableId: configuration?.variableId || 1, + variableIdDefined: true, + }), + socketIdKey: 'variableSet', + socketMappings: { + in: { + flow: 'inputFlow', + value: 'inputVal', + }, + }, +}); diff --git a/packages/core/src/nodes/chain/onChainGraph/extractOnChainNodesFromGraph.test.ts b/packages/core/src/nodes/chain/onChainGraph/extractOnChainNodesFromGraph.test.ts new file mode 100755 index 0000000..a1d57aa --- /dev/null +++ b/packages/core/src/nodes/chain/onChainGraph/extractOnChainNodesFromGraph.test.ts @@ -0,0 +1,116 @@ +import { ExpectTrue, GraphJSON, NodeJSON } from '@behave-graph/core'; +import { generateOnChainNodesFromGraph } from '../onChainGraph/extractOnChainNodesFromGraph'; +import { SocketIndecesByNodeType } from '../IChainNode'; +import { expect } from 'chai'; +import { OnChainCounter } from '../OnChainCounter'; + +// describe('extractOnChainNodesFromGraph', () => { +// const variableName = 'myVar'; +// const chainVariableSet: NodeJSON = { +// id: 'c', +// type: ChainCounter.typeName, +// parameters: { +// [chainVariableSetSocketSpec.socketNames.variableName]: { +// value: variableName, +// }, +// }, +// }; +// const chainCounter: NodeJSON = { +// id: '2', +// type: chainCointerSocketSpec.nodeTypeName, +// flows: { +// [chainCointerSocketSpec.socketNames.outputFlow]: { +// nodeId: chainVariableSet.id, +// socket: chainVariableSetSocketSpec.socketNames.inputFlow, +// }, +// [chainCointerSocketSpec.socketNames.outputCount]: { +// nodeId: chainVariableSet.id, +// socket: chainVariableSetSocketSpec.socketNames.inputVal, +// }, +// }, +// }; + +// const externalTrigger: NodeJSON = { +// id: 'a', +// type: externalTriggerSocketSpec.nodeTypeName, +// flows: { +// [externalTriggerSocketSpec.socketNames.outputFlowSocket]: { +// nodeId: chainCounter.id, +// socket: chainCointerSocketSpec.socketNames.inputFlow, +// }, +// }, +// }; + +// const graph: GraphJSON = { +// nodes: [externalTrigger, chainCounter, chainVariableSet], +// }; + +// const socketIndecesByNodeType: SocketIndecesByNodeType = { +// add: { +// input1: 0, +// input2: 1, +// result: 2, +// }, +// counter: { +// inputFlow: 0, +// outputCount: 1, +// outputFlow: 2, +// }, +// externalInvoke: { +// outputFlowSocket: 1, +// }, +// gate: { +// inputFlow: 0, +// outputGateFalse: 1, +// outputGateTrue: 2, +// }, +// variableSet: { +// inputFlow: 0, +// inputVal: 1, +// variableName: 2, +// }, +// }; + +// const result = extractOnChainNodesFromGraph(graph, socketIndecesByNodeType); + +// it('generates on chain nodes for each chain node in graph', () => { +// expect(result.nodeDefinitions).to.have.lengthOf(3); + +// const externalTriggerNode = result.nodeDefinitions.find((node) => node.definition.id === externalTrigger.id); +// expect(externalTriggerNode?.definition.defined).to.be.true; +// expect(externalTriggerNode?.definition.inputValueType).to.equal(externalTriggerSocketSpec.inputValueType); + +// const counterNode = result.nodeDefinitions.find((node) => node.definition.id === chainCounter.id); +// expect(counterNode?.definition.defined).to.be.true; + +// const variableSetNode = result.nodeDefinitions.find((node) => node.definition.id === chainVariableSet.id); +// expect(variableSetNode?.definition.defined).to.be.true; +// }); + +// it('sets the on chain-node initial values', () => { +// const variableSetNode = result.nodeDefinitions.find((node) => node.definition.id === chainVariableSet.id); + +// expect(variableSetNode?.initialValues.strings).to.have.lengthOf(1); +// expect(variableSetNode?.initialValues.strings[0]).to.eql({ +// value: variableName, +// socket: socketIndecesByNodeType.variableSet.variableName, +// }); +// }); + +// it.only('generates on-chain edges for each edge between on-chain nodes', () => { +// const triggerToCounterEdges = result.edgeDefinitions.filter((x) => { +// return x.fromNode === externalTrigger.id && x.toNode === chainCounter.id; +// }); + +// console.log({ triggerToCounterEdges }); + +// expect(triggerToCounterEdges).to.have.lengthOf(1); + +// expect(triggerToCounterEdges[0].fromSocket).to.equal(socketIndecesByNodeType.externalTrigger.outputFlowSocket); +// expect(triggerToCounterEdges[0].toSocket).to.equal(socketIndecesByNodeType.counter.inputFlow); +// }); + +// it('excludes off-chain nodes', () => {}); + +// it('excludes edges between off-chain and on-chain nodes', () => {}); +// }); diff --git a/packages/core/src/nodes/chain/onChainGraph/extractOnChainNodesFromGraph.ts b/packages/core/src/nodes/chain/onChainGraph/extractOnChainNodesFromGraph.ts new file mode 100755 index 0000000..9f77da4 --- /dev/null +++ b/packages/core/src/nodes/chain/onChainGraph/extractOnChainNodesFromGraph.ts @@ -0,0 +1,144 @@ +import { GraphJSON, NodeJSON, NodeParameterJSON, NodeParameterValueJSON } from '@behave-graph/core'; +import { IChainGraph } from '../../../abstractions'; +import { + ChainEdgeNodeDefinition, + ChainNodeDefinitionAndValues, + ChainnInitialValues, + SocketIndecesByNodeType, + IHasOnChainDefinition, + emptyNodeConfig, + ToOnChainDefinitionForNode, +} from '../IChainNode'; +import { makeToOnChainNodeConverterters } from '../profile'; +import { getOnChainEdges } from './getOnChainEdges'; + +function appendInitialValue( + value: T, + index: number, + values: readonly { value: T; socket: number }[] +): { value: T; socket: number }[] { + return [...values, { value, socket: index }]; +} + +function isNodeParameterValueJSON(node: NodeParameterJSON): node is NodeParameterValueJSON { + // @ts-ignore + return typeof node.value !== undefined; +} + +const extractInitialValues = (node: NodeJSON, spec: IHasOnChainDefinition['chain']): ChainnInitialValues => { + // for each input socket, get value from socket and append it to list of values + return Object.entries(node.parameters || {}).reduce( + (acc: ChainnInitialValues, [key, param]): ChainnInitialValues => { + return acc; + // if (!isNodeParameterValueJSON(param)) return acc; + + // const input = nodes[node.type].getInput(key); + // if (!input) return acc; + + // const { index, valueTypeName } = input; + + // const { booleans, integers, strings } = acc; + // return { + // booleans: + // valueTypeName === 'boolean' ? appendInitialValue(param.value as boolean, index, booleans) : booleans, + // integers: + // valueTypeName === 'integer' ? appendInitialValue(BigInt(param.value), index, integers) : integers, + // strings: + // valueTypeName === 'string' ? appendInitialValue(param.value as string, index, strings) : strings, + // }; + }, + { + booleans: [], + integers: [], + strings: [], + } + ); +}; + +export function chainNodeToNodeDefinitionAndEdges({ + node, + nodeSpec, + nodes, + socketIndecesByNodeType, +}: { + node: NodeJSON; + nodeSpec: ToOnChainDefinitionForNode; + nodes: NodeJSON[]; + socketIndecesByNodeType: SocketIndecesByNodeType; +}): { + nodeDefinition: ChainNodeDefinitionAndValues; + edgeDefinitions: ChainEdgeNodeDefinition[]; +} { + const nodeDefinition: ChainNodeDefinitionAndValues = { + definition: { + id: node.id, + defined: true, + inputValueType: nodeSpec.inputValueType, + nodeType: nodeSpec.nodeType, + }, + config: { + ...emptyNodeConfig, + ...(nodeSpec.getConfig ? nodeSpec.getConfig(node.configuration) : {}), + }, + initialValues: extractInitialValues(node, nodeSpec), + }; + + const edgeDefinitions = getOnChainEdges({ + node, + nodes, + toOnChainNodeDefinitions: makeToOnChainNodeConverterters(), + socketIndeces: socketIndecesByNodeType, + }); + + return { + nodeDefinition, + edgeDefinitions, + }; +} + +/** + * Takes a behavior graph and instructions on how to map socket ids to on chain integers + * and generates the parameters needed to create the on chain nodes and edges. + * @param param0 + * @returns + */ +export const generateOnChainNodesFromGraph = ({ + graph, + socketIndecesByNodeType, +}: { + graph: GraphJSON; + socketIndecesByNodeType: SocketIndecesByNodeType; +}): { + nodeDefinitions: ChainNodeDefinitionAndValues[]; + edgeDefinitions: ChainEdgeNodeDefinition[]; +} => { + const nodes = graph.nodes; + if (!nodes) + return { + nodeDefinitions: [], + edgeDefinitions: [], + }; + + // using the graph, get the chain node specs for each node type + const chainNodeSpecs = makeToOnChainNodeConverterters(); + + // get chain node specs for each node + const chainNodes = nodes + .filter((x) => !!chainNodeSpecs[x.type]) + .map((node) => ({ + node, + spec: chainNodeSpecs[node.type], + })); + + const nodeAndEdgeDefinitions = chainNodes.map(({ node, spec }) => + chainNodeToNodeDefinitionAndEdges({ node, nodeSpec: spec, nodes, socketIndecesByNodeType }) + ); + + const nodeDefinitions = nodeAndEdgeDefinitions.map(({ nodeDefinition }) => nodeDefinition); + const edgeDefinitions = nodeAndEdgeDefinitions.flatMap(({ edgeDefinitions }) => edgeDefinitions); + + return { + nodeDefinitions, + edgeDefinitions, + }; +}; diff --git a/packages/core/src/nodes/chain/onChainGraph/getOnChainEdges.test.ts b/packages/core/src/nodes/chain/onChainGraph/getOnChainEdges.test.ts new file mode 100755 index 0000000..ba814e5 --- /dev/null +++ b/packages/core/src/nodes/chain/onChainGraph/getOnChainEdges.test.ts @@ -0,0 +1,195 @@ +import { FlowsJSON, NodeJSON, SocketNames, SocketsDefinition } from '@behave-graph/core'; +import { expect } from 'chai'; +import { IChainGraph } from 'packages/core/src/abstractions'; +import { EdgeDefinitionStruct as EdgeDefinition } from 'typechain-types/contracts/BehaviorGraphToken'; +import { OnChainCounter } from '../OnChainCounter'; +import { OnChainVariableSet } from '../OnChainVariableSet'; +import { SocketIndecesByNodeType } from '../IChainNode'; +import { makeToOnChainNodeConverterters } from '../profile'; +import { getOnChainEdges } from './getOnChainEdges'; +import { + NodeParameterJSON, + NodeParameterLinkJSON, + NodeParametersJSON, +} from 'packages/core/.yalc/@behave-graph/core/dist/behave-graph-core.cjs'; + +const makeFlowsNodeJson = ({ + flows, + to: { nodeId }, +}: { + from: TOut; + to: { + nodeId: string; + sockets: TIn; + }; + flows: { + from: SocketNames; + to: SocketNames; + }[]; +}): FlowsJSON => + flows.reduce((acc, { from: key, to: socket }) => { + return { + ...acc, + [key]: { + socket, + nodeId, + }, + }; + }, {}); + +const makeInputLinkParams = ({ + flows, + to: { nodeId: toNodeId }, +}: { + from: TIn; + to: { + nodeId: string; + sockets: TOut; + }; + flows: { + to: SocketNames; + from: SocketNames; + }[]; +}): NodeParametersJSON => + flows.reduce((acc: NodeParametersJSON, { from: key, to: toSocketId }) => { + const paramJson: NodeParameterLinkJSON = { + link: { + socket: toSocketId, + nodeId: toNodeId, + }, + }; + return { + ...acc, + [key]: paramJson, + }; + }, {}); + +const socketIndeces: SocketIndecesByNodeType = { + counter: { + inputFlow: 0, + outputFlow: 1, + outputCount: 2, + }, + add: { + input1: 0, + input2: 1, + result: 2, + }, + variableSet: { + inputFlow: 0, + inputVal: 1, + }, + externalInvoke: { + outputFlowSocket: 1, + }, + gate: { + inputFlow: 0, + outputGateFalse: 1, + outputGateTrue: 2, + }, +}; + +const x: IChainGraph | undefined = undefined; + +// @ts-ignore +const chainNodeSpecs = makeToOnChainNodeConverterters(x); + +describe('getOnChainEdges', () => { + it('should return an empty array if there are no edges', () => { + const chainNodeJson: NodeJSON = { + id: '0', + type: OnChainCounter.typeName, + }; + + const edges = getOnChainEdges({ + node: chainNodeJson, + nodes: [chainNodeJson], + toOnChainNodeDefinitions: chainNodeSpecs, + socketIndeces, + }); + + expect(edges).to.have.lengthOf(0); + }); + + it('should return an edge with node and edge ids corresponding to node ids and edge indeces based on the spec', () => { + const countNodeId = 'countNodeId'; + + const variableSetNodeId = 'variableSetNodeId'; + + // this is poorly readable - maybe we can make a better function? + const chainNodeJson: NodeJSON = { + id: countNodeId, + type: OnChainCounter.typeName, + flows: makeFlowsNodeJson({ + from: OnChainCounter.out, + to: { + nodeId: variableSetNodeId, + sockets: OnChainVariableSet.in, + }, + flows: [ + { + from: 'flow', + to: 'flow', + }, + ], + }), + }; + + // this is poorly readable - maybe we can make a better function? + const variableSetNodeJson: NodeJSON = { + id: variableSetNodeId, + type: OnChainVariableSet.typeName, + parameters: makeInputLinkParams({ + from: OnChainVariableSet.in, + to: { + nodeId: countNodeId, + sockets: OnChainCounter.out, + }, + flows: [ + { + from: 'value', + to: 'count', + }, + ], + }), + }; + + const allNodes = [chainNodeJson, variableSetNodeJson]; + + const common = { + nodes: allNodes, + toOnChainNodeDefinitions: chainNodeSpecs, + socketIndeces, + }; + + const chainNodeEdges = getOnChainEdges({ + node: chainNodeJson, + ...common, + }); + + const variableSetNodeEdges = getOnChainEdges({ + node: variableSetNodeJson, + ...common, + }); + + const edges = [...chainNodeEdges, ...variableSetNodeEdges]; + + expect(edges).to.have.lengthOf(2); + + const expectedFirstEdge: EdgeDefinition = { + fromNode: countNodeId, + fromSocket: socketIndeces.counter.outputFlow, + toNode: variableSetNodeId, + toSocket: socketIndeces.variableSet.inputFlow, + }; + + const expetedSecondEdge: EdgeDefinition = { + fromNode: countNodeId, + fromSocket: socketIndeces.counter.outputCount, + toNode: variableSetNodeId, + toSocket: socketIndeces.variableSet.inputVal, + }; + + expect(edges).to.deep.equal([expectedFirstEdge, expetedSecondEdge]); + }); +}); diff --git a/packages/core/src/nodes/chain/onChainGraph/getOnChainEdges.ts b/packages/core/src/nodes/chain/onChainGraph/getOnChainEdges.ts new file mode 100755 index 0000000..a6f1ebc --- /dev/null +++ b/packages/core/src/nodes/chain/onChainGraph/getOnChainEdges.ts @@ -0,0 +1,145 @@ +import { + FlowsJSON, + NodeJSON, + NodeParameterJSON, + NodeParameterLinkJSON, + SocketDefinition, + SocketsDefinition, +} from '@behave-graph/core'; +import { SocketIndecesByNodeTypeStruct } from 'typechain-types/contracts/BehaviorGraphToken'; +import { ChainEdgeNodeDefinition, SocketMappings, ToOnChainDefinitionForNode } from '../IChainNode'; + +function getSocketIndecesForNodeMapping({ + socketMappings, + socketIdKey, + fromSocket, + socketIndeces, +}: { + socketMappings: SocketMappings | undefined; + socketIdKey: TSocketIdKey; + fromSocket: string; + socketIndeces: SocketIndecesByNodeTypeStruct; +}) { + if (!socketMappings) throw new Error('out not configured'); + const fromSocketIndexKey = socketMappings[fromSocket]; + if (!fromSocketIndexKey) throw new Error(`missing socket mapping for key: ${fromSocket}`); + // @ts-ignore + return socketIndeces[socketIdKey][fromSocketIndexKey]; +} + +const isLink = (param: NodeParameterJSON): param is NodeParameterLinkJSON => { + return (param as NodeParameterLinkJSON).link !== undefined; +}; + +/** + * Needs to convert from sockets that are declared as strings to integers, as thats how they are stored on chain. + * So this will for each flow, generate the edge, and use the socketIndeces and the toOnChainNode + * definitions for the source and destination node to get the correct socket index. + * @param param0 + * @returns + */ +export function getOnChainEdges({ + node, + nodes, + toOnChainNodeDefinitions, + socketIndeces, +}: { + node: NodeJSON; + nodes: NodeJSON[]; + toOnChainNodeDefinitions: Record; + socketIndeces: SocketIndecesByNodeTypeStruct; +}): ChainEdgeNodeDefinition[] { + const edges: ChainEdgeNodeDefinition[] = []; + // get instructions on how to convert from this node's node ids + // to corresponding on chain integers + const thisOnChainNodeDefinition = toOnChainNodeDefinitions[node.type]; + + if (!thisOnChainNodeDefinition) { + throw new Error(`missing node spec for node type: ${node.type}`); + } + + const thisNodeId = node.id; + + // get edges from the output flows + Object.entries(node.flows || {}).forEach(([fromSocket, { nodeId: flowNodeId, socket: toSocket }]) => { + const otherNode = nodes.find((n) => n.id === flowNodeId); + if (!otherNode) return; + + const edge = getOnChainEdge({ + socketIndeces, + sourceNodeId: thisNodeId, + sourceOnChainNodeDefinition: thisOnChainNodeDefinition, + sourceSocket: fromSocket, + destinationNodeId: flowNodeId, + destinationNodeToOnChainNodeDefinition: toOnChainNodeDefinitions[otherNode.type], + destinationSocket: toSocket, + }); + + edges.push(edge); + }); + + // parameters links point backwards (it's wierd), so we need to make an edge in the reverse direction + // from the parameter + Object.entries(node.parameters || {}).forEach(([toSocket, param]) => { + if (isLink(param)) { + const { nodeId: fromNodeId, socket: fromSocket } = param.link; + const otherNode = nodes.find((n) => n.id === fromNodeId); + if (!otherNode) return; + + const edge = getOnChainEdge({ + socketIndeces, + sourceNodeId: fromNodeId, + sourceOnChainNodeDefinition: toOnChainNodeDefinitions[otherNode.type], + sourceSocket: fromSocket, + destinationNodeId: thisNodeId, + destinationNodeToOnChainNodeDefinition: thisOnChainNodeDefinition, + destinationSocket: toSocket, + }); + + edges.push(edge); + } + }); + + return edges; +} +function getOnChainEdge({ + sourceNodeId, + destinationNodeId, + socketIndeces, + sourceOnChainNodeDefinition, + sourceSocket, + destinationNodeToOnChainNodeDefinition, + destinationSocket, +}: { + sourceNodeId: string; + destinationNodeId: string; + socketIndeces: SocketIndecesByNodeTypeStruct; + sourceOnChainNodeDefinition: ToOnChainDefinitionForNode; + sourceSocket: string; + destinationNodeToOnChainNodeDefinition: ToOnChainDefinitionForNode; + destinationSocket: string; +}) { + const socketIndexFrom = getSocketIndecesForNodeMapping({ + socketIdKey: sourceOnChainNodeDefinition.socketIdKey, + socketMappings: sourceOnChainNodeDefinition.socketMappings.out, + fromSocket: sourceSocket, + socketIndeces, + }); + + // get the socket index for the destination node + const socketIndexTo = getSocketIndecesForNodeMapping({ + socketIdKey: destinationNodeToOnChainNodeDefinition.socketIdKey, + socketMappings: destinationNodeToOnChainNodeDefinition.socketMappings.in, + fromSocket: destinationSocket, + socketIndeces, + }); + + const result: ChainEdgeNodeDefinition = { + fromNode: sourceNodeId, + fromSocket: socketIndexFrom as number, + toNode: destinationNodeId, + toSocket: socketIndexTo as number, + }; + + return result; +} diff --git a/packages/core/src/nodes/chain/profile.ts b/packages/core/src/nodes/chain/profile.ts new file mode 100755 index 0000000..888bd6c --- /dev/null +++ b/packages/core/src/nodes/chain/profile.ts @@ -0,0 +1,46 @@ +import { IRegistry, Registry } from '@behave-graph/core'; +import { IChainGraph } from '../../abstractions'; +import { OnChainCounter } from './OnChainCounter'; +import { chainGraphDependencyKey, OnChainVariableGet } from './OnChainVariableGet'; +import { OnChainVariableSet } from './OnChainVariableSet'; +import { InvokeOnChainAction } from './InvokeOnChainAction'; +import { ChainNodeTypes, ChainValueType, IHasOnChainDefinition } from './IChainNode'; +import { SocketIO } from './socketGeneration'; + +const getChainDefinitions = () => [OnChainCounter, OnChainVariableSet, InvokeOnChainAction]; + +export function registerChainGraphProfile(registry: Pick) { + getChainDefinitions().forEach((x) => registry.nodes.register(x)); + + registry.nodes.register(OnChainVariableGet); +} + +export function registerChainGraphDepenency(dependencies: IRegistry['dependencies'], chainGraph: IChainGraph) { + dependencies.register(chainGraphDependencyKey, chainGraph); +} + +export type NodeSocketIO = Record< + string, + SocketIO & { + nodeType: ChainNodeTypes; + inputValueType: ChainValueType; + } +>; + +/** + * Makes a mapping of node type to the on chain node converter. + * @param actions + * @returns a mapping of node type to the on chain node converter. + */ +export const makeToOnChainNodeConverterters = (): Record => { + const chainDefinitions = getChainDefinitions(); + + return chainDefinitions.reduce((acc: Record, def) => { + const chain = def.chain; + if (!chain) throw new Error(`Chain definition for ${def.typeName} is missing!`); + return { + ...acc, + [def.typeName]: chain, + }; + }, {}); +}; diff --git a/packages/core/src/nodes/chain/socketGeneration.ts b/packages/core/src/nodes/chain/socketGeneration.ts new file mode 100755 index 0000000..3bf7a58 --- /dev/null +++ b/packages/core/src/nodes/chain/socketGeneration.ts @@ -0,0 +1,58 @@ +import { Socket } from '@behave-graph/core'; +import { ChainNodeTypes, ChainValueType, SocketIndecesByNodeType } from './IChainNode'; + +export type SocketIO = { + getInput: (key: string) => { index: number; valueTypeName: string } | undefined; + getOutput: (key: string) => { index: number; valueTypeName: string } | undefined; +}; + +type SocketIndeces = { [key: string]: number }; +export type SocketNames = { [key in keyof TSocketIndeces]: string }; + +type ChainSocketSpecGenerator< + TSocketIndeces extends SocketIndeces, + TSocketNames extends SocketNames +> = { + socketIndecesForType: (socketIndeces: SocketIndecesByNodeType) => TSocketIndeces; + nodeTypeName: string; + nodeType: ChainNodeTypes; + inputValueType: ChainValueType; + socketNames: TSocketNames; + inputSockets: (socketNames: TSocketNames) => Socket[]; + outputSockets: (socketNames: TSocketNames) => Socket[]; +}; + +function makeChainNodeSpec>( + chainSocketSpec: ChainSocketSpecGenerator +) { + return { + ...chainSocketSpec, + inputSockets: () => chainSocketSpec.inputSockets(chainSocketSpec.socketNames), + outputSockets: () => chainSocketSpec.outputSockets(chainSocketSpec.socketNames), + }; +} + +const getSocketIndex = ( + sockets: Socket[], + socketNames: SocketNames, + indeces: TSocketIndeces +) => { + const socketMapping = sockets.reduce( + (acc: Record, { name, valueTypeName }) => { + const socketName = socketNames[name]; + const socketIndex = indeces[name]; + if (!socketIndex) return acc; + + return { + ...acc, + [socketName]: { + index: socketIndex, + valueTypeName, + }, + }; + }, + {} + ); + + return (key: string) => socketMapping[key]; +}; diff --git a/packages/core/src/nodes/index.ts b/packages/core/src/nodes/index.ts new file mode 100755 index 0000000..4c0b960 --- /dev/null +++ b/packages/core/src/nodes/index.ts @@ -0,0 +1,8 @@ +/* chain nodes */ +export * from './chain/OnChainCounter'; +export * from './chain/OnChainVariableSet'; +export * from './chain/OnChainVariableGet'; +export * from './chain/InvokeOnChainAction'; +export * from './chain/onChainGraph/extractOnChainNodesFromGraph'; +export * from './chain/IChainNode'; +export * from './chain/profile'; diff --git a/scripts/deploy.js b/scripts/deploy.js deleted file mode 100644 index f655240..0000000 --- a/scripts/deploy.js +++ /dev/null @@ -1,70 +0,0 @@ -"use strict"; -// This is a script for deploying your contracts. You can adapt it to deploy -// yours, or create new ones. -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const path_1 = __importDefault(require("path")); -require("@nomiclabs/hardhat-ethers"); -const hardhat_1 = require("hardhat"); -const fs_1 = require("fs"); -const util_1 = require("util"); -const writeFileAsync = (0, util_1.promisify)(fs_1.writeFile); -const readFileAsync = (0, util_1.promisify)(fs_1.readFile); -const mkdirAsync = (0, util_1.promisify)(fs_1.mkdir); -async function main() { - // This is just a convenience check - if (hardhat_1.network.name === 'hardhat') { - console.warn('You are trying to deploy a contract to the Hardhat Network, which' + - 'gets automatically created and destroyed every time. Use the Hardhat' + - " option '--network localhost'"); - } - // ethers is available in the global scope - const [deployer] = await hardhat_1.ethers.getSigners(); - const address = await deployer.getAddress(); - console.log('Deploying the contracts with the account:', address); - console.log('Account balance:', (await deployer.getBalance()).toString()); - const contractFactory = await hardhat_1.ethers.getContractFactory('BehaviorGraph'); - const deployedContract = await contractFactory.deploy(); - await deployedContract.deployed(); - console.log('Contract address:', deployedContract.address); - // We also save the contract's artifacts and address in the frontend directory - await saveFrontendFiles(deployedContract); -} -const contractsDir = path_1.default.join(__dirname, '..', 'editor', 'src', 'contracts'); -async function writeAbi() { - const ContractArtifact = await hardhat_1.artifacts.readArtifact('BehaviorGraph'); - const abiTs = `export const abi = ${JSON.stringify(ContractArtifact.abi, null, 2)} as const`; - await writeFileAsync(path_1.default.join(contractsDir, 'abi.ts'), abiTs); -} -const contractAddressesFile = path_1.default.join(contractsDir, 'addresses.json'); -async function writeContractAddress(contract) { - let currentAddresses; - if (!(0, fs_1.existsSync)(contractAddressesFile)) { - currentAddresses = {}; - } - else { - currentAddresses = JSON.parse(await readFileAsync(contractAddressesFile, { - encoding: 'utf-8', - })); - } - const updatedAddresses = { - ...currentAddresses, - [hardhat_1.network.name]: contract.address, - }; - await writeFileAsync(contractAddressesFile, JSON.stringify(updatedAddresses, undefined, 2)); -} -async function saveFrontendFiles(contract) { - if (!(0, fs_1.existsSync)(contractsDir)) { - await mkdirAsync(contractsDir); - } - await writeContractAddress(contract); - await writeAbi(); -} -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); -}); diff --git a/scripts/deploy.ts b/scripts/deploy.ts old mode 100644 new mode 100755 index 51cc154..c172db3 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -29,7 +29,7 @@ async function main() { console.log('Account balance:', (await deployer.getBalance()).toString()); - const contractFactory = await ethers.getContractFactory('BehaviorGraph'); + const contractFactory = await ethers.getContractFactory('BehaviorGraphToken'); const deployedContract = await contractFactory.deploy(); await deployedContract.deployed(); @@ -39,12 +39,12 @@ async function main() { await saveFrontendFiles(deployedContract); } -const contractsDir = path.join(__dirname, '..', 'editor', 'src', 'contracts'); +const contractsDir = path.join(__dirname, '..', 'packages', 'core', 'src', 'contracts'); async function writeAbi() { - const ContractArtifact = await artifacts.readArtifact('BehaviorGraph'); + const ContractArtifact = await artifacts.readArtifact('BehaviorGraphToken'); - const abiTs = `export const abi = ${JSON.stringify(ContractArtifact.abi, null, 2)} as const`; + const abiTs = `export const abi = ${JSON.stringify(ContractArtifact.abi, null, 2)} as const;`; await writeFileAsync(path.join(contractsDir, 'abi.ts'), abiTs); } @@ -74,6 +74,10 @@ async function writeContractAddress(contract: BaseContract) { }; await writeFileAsync(contractAddressesFile, JSON.stringify(updatedAddresses, undefined, 2)); + + const addressesTs = `export const contractAddresses = ${JSON.stringify(updatedAddresses, null, 2)};`; + + await writeFileAsync(path.join(contractsDir, 'contractAddresses.ts'), addressesTs); } async function saveFrontendFiles(contract: BaseContract) { diff --git a/scripts/package.json b/scripts/package.json old mode 100644 new mode 100755 diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json old mode 100644 new mode 100755 diff --git a/test/BehaviorGraph.ts b/test/BehaviorGraph.ts old mode 100644 new mode 100755 index 17438bf..390adbe --- a/test/BehaviorGraph.ts +++ b/test/BehaviorGraph.ts @@ -1,14 +1,68 @@ -import { time, loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; import '@nomicfoundation/hardhat-chai-matchers'; -import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'; import { expect } from 'chai'; import '@nomiclabs/hardhat-ethers'; -import { BehaviorGraph__factory } from '../typechain-types'; +import { BehaviorGraphToken__factory, BehaviorGraph__factory } from '../typechain-types'; import { ethers } from 'hardhat'; -import { BigNumber, Signer } from 'ethers'; -import { BehaviorGraph, NodeStruct, TokenGateRuleStruct } from '../typechain-types/contracts/BehaviorGraph'; -import { Token } from '../typechain-types/contracts/Token'; -import { token } from '../typechain-types/@openzeppelin/contracts'; +import { + NodeDefinitionStruct, + EdgeDefinitionStruct, + NodeDefinitionAndValuesStruct, + InitialValuesStruct, + SocketIndecesByNodeTypeStruct, + NodeConfigStruct, +} from '../typechain-types/contracts/BehaviorGraph'; +import { PromiseOrValue } from '../typechain-types/common'; + +enum VariableType { + Int = 0, + Bool = 1, + NotAVariable = 2, +} + +enum NodeType { + ExternalTrigger = 0, + Counter = 1, + Add = 2, + Gate = 3, + VariableSet = 4, +} + +function connectEdge({ + a, + b, + fromSocket, + toSocket, +}: { + a: Pick; + b: Pick; + fromSocket: number; + toSocket: number; +}) { + const result: EdgeDefinitionStruct = { + fromNode: a.id, + toNode: b.id, + fromSocket, + toSocket, + }; + + return result; +} + +const emptyInitialValues = (): InitialValuesStruct => ({ + booleans: [], + integers: [], + strings: [], +}); + +const VARIABLE_NAME_SOCKET = 6; + +const emptyConfig: NodeConfigStruct = { + invocationId: 0, + invocationNameDefined: false, + variableId: 0, + variableIdDefined: false, +}; describe('BehaviorGraph', function () { // We define a fixture to reuse the same setup in every test. @@ -18,10 +72,12 @@ describe('BehaviorGraph', function () { // Contracts are deployed using the first signer/account by default const [owner, otherAccount, anotherAccount] = await ethers.getSigners(); - const BehaviorGraph = (await ethers.getContractFactory('BehaviorGraph')) as BehaviorGraph__factory; + const BehaviorGraph = (await ethers.getContractFactory('BehaviorGraphToken')) as BehaviorGraphToken__factory; const behaviorGraph = await BehaviorGraph.deploy(); - return { behaviorGraph, owner, otherAccount, anotherAccount }; + const socketIndeces = await behaviorGraph.getSocketIndecesByNodeType(); + + return { behaviorGraph, owner, otherAccount, anotherAccount, socketIndeces }; } describe('safeMint', () => { @@ -30,157 +86,163 @@ describe('BehaviorGraph', function () { const ipfsHash = 'asdfasdfasfda'; - const nodesToCreate: NodeStruct[] = []; + const nodesToCreate: NodeDefinitionAndValuesStruct[] = []; + const edgesToCreate: EdgeDefinitionStruct[] = []; - await expect(behaviorGraph.connect(otherAccount).safeMint(ipfsHash, nodesToCreate)).to.not.be.rejected; + await expect(behaviorGraph.connect(otherAccount).safeMint(ipfsHash, nodesToCreate, edgesToCreate)).to.not.be + .rejected; }); - it('creates a token with the list of node onto the list of nodes', async () => { - const { behaviorGraph, otherAccount } = await loadFixture(deployFixture); - - const ipfsHash = 'asdfasdfasfda'; - - const nodesToCreate: NodeStruct[] = [ - { - nodeType: 0, - id: '0', - tokenGateRule: { - active: false, - tokenContract: otherAccount.address, + }); + describe('trigger', () => { + describe('basic counter', () => { + const externalTriggerNodeId = 'external'; + const counterNodeId = 'a'; + const variableNodeId = 'b'; + const variableId = 1; + const invocationId = 1; + const nodeDefinitions: { [key: string]: NodeDefinitionAndValuesStruct } = { + externalTrigger: { + definition: { + id: externalTriggerNodeId, + defined: true, + nodeType: NodeType.ExternalTrigger, + inputValueType: VariableType.NotAVariable, + }, + initialValues: emptyInitialValues(), + config: { + ...emptyConfig, + invocationNameDefined: true, + invocationId, }, }, - { - nodeType: 1, - id: '5', - tokenGateRule: { - active: true, - tokenContract: behaviorGraph.address, + counter: { + definition: { + id: counterNodeId, + defined: true, + nodeType: NodeType.Counter, + inputValueType: VariableType.NotAVariable, }, + initialValues: emptyInitialValues(), + config: emptyConfig, }, - ]; - - const tx = await behaviorGraph.safeMint(ipfsHash, nodesToCreate); - - await tx.wait(); - - const tokenId = 0; + variable: { + definition: { + id: variableNodeId, + defined: true, + nodeType: NodeType.VariableSet, + inputValueType: VariableType.Int, + }, + initialValues: emptyInitialValues(), + config: { + ...emptyConfig, + variableIdDefined: true, + variableId, + }, + }, + }; - const node = await behaviorGraph.getNode(tokenId, '5'); + it('should raise an error if the counter is triggered directly', async () => { + const { behaviorGraph, socketIndeces } = await loadFixture(deployFixture); - expect(node.nodeType).to.eql(nodesToCreate[1].nodeType); - }); - }); + const nodes = [nodeDefinitions.counter, nodeDefinitions.variable]; - describe('executeAction', () => { - let nodesToCreate: NodeStruct[] = []; + const edges: EdgeDefinitionStruct[] = [ + // edge from output value of counter to the variable + connectEdge({ + a: { id: counterNodeId }, + b: { id: variableNodeId }, + fromSocket: socketIndeces.counter.outputCount as number, + toSocket: socketIndeces.variableSet.inputVal as number, + }), + ]; - let contract: BehaviorGraph; - let otherTokenContract: Token; - let otherAccount: Signer; + const ipfsHash = 'asdfasdfasfda'; + const tx = await behaviorGraph.safeMint(ipfsHash, nodes, edges); - beforeEach(async () => { - const { behaviorGraph, otherAccount: _otherAccount } = await deployFixture(); - const TokenContract = await ethers.getContractFactory('Token'); + await tx.wait(); - const tokenContract = (await TokenContract.deploy()) as Token; + const tokenId = 0; - contract = behaviorGraph; - otherTokenContract = tokenContract; - otherAccount = _otherAccount; - }); + await expect(behaviorGraph.invoke(tokenId, invocationId)).to.be.rejected; + }); - describe('when the action is not token gated', () => { - it('can successfully execute that action and emits an event with the count', async () => { - const actionId = '5'; - const nodesToCreate = [ - { - nodeType: 0, - id: actionId, - tokenGateRule: { - active: false, - tokenContract: contract.address, - }, - }, + it('should not trigger an action when there is no flow connection to a variable', async () => { + const { behaviorGraph, socketIndeces } = await loadFixture(deployFixture); + + const nodes = [nodeDefinitions.externalTrigger, nodeDefinitions.counter, nodeDefinitions.variable]; + + const edges: EdgeDefinitionStruct[] = [ + // edge from external trigger to counter + connectEdge({ + a: nodeDefinitions.externalTrigger.definition, + b: nodeDefinitions.counter.definition, + fromSocket: socketIndeces.externalInvoke.outputFlowSocket as number, + toSocket: socketIndeces.counter.inputFlow as number, + }), + // edge from output value of counter to the variable + connectEdge({ + a: nodeDefinitions.counter.definition, + b: nodeDefinitions.variable.definition, + fromSocket: socketIndeces.counter.outputCount as number, + toSocket: socketIndeces.variableSet.inputVal as number, + }), ]; - const ipfsHash = 'asdfasfda'; - const tx = await contract.safeMint(ipfsHash, nodesToCreate); + const ipfsHash = 'asdfasdfasfda'; + const tx = await behaviorGraph.safeMint(ipfsHash, nodes, edges); + await tx.wait(); const tokenId = 0; - const executerAddress = otherAccount; - - const actionCount = 1; - - await expect(contract.connect(executerAddress).executeAction(tokenId, actionId)) - .to.emit(contract, 'ActionExecuted') - .withArgs(await executerAddress.getAddress(), tokenId, actionId, actionCount); + await expect(behaviorGraph.invoke(tokenId, invocationId)).to.not.emit(behaviorGraph, 'IntVariableUpdated'); - await expect(contract.connect(executerAddress).executeAction(tokenId, actionId)) - .to.emit(contract, 'ActionExecuted') - .withArgs(await executerAddress.getAddress(), tokenId, actionId, actionCount + 1); + await expect(behaviorGraph.invoke(tokenId, invocationId)).to.not.emit(behaviorGraph, 'IntVariableUpdated'); }); - }); - describe('when the action is token gated', () => { - beforeEach(async () => { - const nodesToCreate = [ - { - nodeType: 0, - id: '10', - tokenGateRule: { - active: false, - tokenContract: contract.address, - }, - }, - { - nodeType: 0, - id: '1', - // this rule requires you to have a token from another contract - tokenGateRule: { - active: true, - tokenContract: otherTokenContract.address, - }, - }, + it('should emit that a variable is updated when a flow is connected to the variable input', async () => { + const { behaviorGraph, owner, socketIndeces } = await loadFixture(deployFixture); + + const nodes = [nodeDefinitions.externalTrigger, nodeDefinitions.counter, nodeDefinitions.variable]; + + const edges: EdgeDefinitionStruct[] = [ + // edge from external trigger to counter + connectEdge({ + a: nodeDefinitions.externalTrigger.definition, + b: nodeDefinitions.counter.definition, + fromSocket: socketIndeces.externalInvoke.outputFlowSocket as number, + toSocket: socketIndeces.counter.inputFlow as number, + }), + // edge from output value of counter to the variable + connectEdge({ + a: nodeDefinitions.counter.definition, + b: nodeDefinitions.variable.definition, + fromSocket: socketIndeces.counter.outputCount as number, + toSocket: socketIndeces.variableSet.inputVal as number, + }), + // edge from flow of counter to flow of variable + connectEdge({ + a: nodeDefinitions.counter.definition, + b: nodeDefinitions.variable.definition, + fromSocket: socketIndeces.counter.outputFlow as number, + toSocket: socketIndeces.variableSet.inputFlow as number, + }), ]; - // mint 2 tokens - await contract.safeMint('0asdfasfd', []); - const tx = await contract.safeMint('asdfasdfasfd', nodesToCreate); + const ipfsHash = 'asdfasdfasfda'; + const tx = await behaviorGraph.safeMint(ipfsHash, nodes, edges); await tx.wait(); - }); - describe('when the user does not have a token of that acollection', () => { - it('cannot successfully execute that action', async () => { - // user does not have a token of that other account, so this fails. - const tokenId = 1; - const actionId = '1'; - await expect(contract.connect(otherAccount).executeAction(tokenId, actionId)).to.be.reverted; - }); - }); - - describe('when the user has a token of that collection', () => { - it('can successfully execute that action and emits the result', async () => { - const executerAccount = otherAccount; - const executerAccountAddress = await executerAccount.getAddress(); - const tokenId = 1; - const actionId = '1'; - const actionCount = 1; - - // mint a token on the other contract the balance should be good now - const tx = await otherTokenContract.connect(executerAccount).safeMint('asdfasdfafs'); - await tx.wait(); + const tokenId = 0; - // verify that we have a token in the other contract - const balance = await otherTokenContract.balanceOf(executerAccountAddress); - expect(balance).to.eql(BigNumber.from(1)); + await expect(behaviorGraph.invoke(tokenId, invocationId)) + .to.emit(behaviorGraph, 'IntVariableUpdated') + .withArgs(await owner.getAddress(), tokenId, variableId, 1); - // successfully call eecute the other action - await expect(contract.connect(otherAccount).executeAction(tokenId, actionId)) - .to.emit(contract, 'ActionExecuted') - .withArgs(await executerAccountAddress, tokenId, actionId, actionCount); // We accept any value as `when` arg - }); + await expect(behaviorGraph.invoke(tokenId, invocationId)) + .to.emit(behaviorGraph, 'IntVariableUpdated') + .withArgs(await owner.getAddress(), tokenId, variableId, 2); }); }); }); diff --git a/test/package.json b/test/package.json old mode 100644 new mode 100755 index a48a177..dc3329d --- a/test/package.json +++ b/test/package.json @@ -13,6 +13,8 @@ "@nomicfoundation/hardhat-toolbox": "^2.0.0", "@nomiclabs/hardhat-etherscan": "^3.1.2", "@typechain/hardhat": "^6.1.4", + "@types/chai": "^4.3.4", + "@types/mocha": "^10.0.1", "chai": "^4.3.6", "hardhat": "^2.12.2", "hardhat-gas-reporter": "^1.0.9", diff --git a/test/tsconfig.json b/test/tsconfig.json old mode 100644 new mode 100755 diff --git a/test/yarn.lock b/test/yarn.lock old mode 100644 new mode 100755 diff --git a/tsconfig.json b/tsconfig.json old mode 100644 new mode 100755 index 7bd41e0..0c2448e --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "es6", - "lib": ["es2019", "dom"], + "target": "ESNext", + "lib": ["es2020", "dom"], "module": "commonjs", "moduleResolution": "node", "esModuleInterop": true, @@ -10,11 +10,12 @@ "strict": true, "skipLibCheck": true, "declaration": true, - "removeComments": true, + "removeComments": false, "emitDeclarationOnly": true, "resolveJsonModule": true, "noImplicitThis": false, - "baseUrl": "./" + "baseUrl": "./", + "types": ["mocha"] }, "include": ["packages/**/*"], "exclude": ["node_modules", "dist"] diff --git a/tsconfig.node.json b/tsconfig.node.json old mode 100644 new mode 100755 diff --git a/yarn.lock b/yarn.lock old mode 100644 new mode 100755 index b3c0a96..b7288bf --- a/yarn.lock +++ b/yarn.lock @@ -1031,6 +1031,13 @@ dependencies: regenerator-runtime "^0.13.10" +"@babel/runtime@^7.14.8": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" @@ -1065,11 +1072,6 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@behave-graph/core@^0.9.11": - version "0.9.11" - resolved "https://registry.yarnpkg.com/@behave-graph/core/-/core-0.9.11.tgz#5eea457ba43a391ff0b261a4e2aeb522cd3e4f0f" - integrity sha512-6jal+yvJOVAo4/+RVcX5cJi1PHgQmeSP/RFnOmdLVWfaWPPLUwQ/bgH4GKrU0RIzK6xwCEwYp+RQnWMkmBDk3g== - "@chevrotain/cst-dts-gen@10.4.1": version "10.4.1" resolved "https://registry.yarnpkg.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.4.1.tgz#b5b5656d6121dd6cf21675ffc19641057c005c44" @@ -1098,12 +1100,12 @@ integrity sha512-vPIgzES8QhHMchb5UaQ4V/c9xmoaECN+4EXpuhWE+pu3LXJUUtAwDn/SEKFgtyiRo269Hxv3b0NbPlQfH0jeVA== "@coinbase/wallet-sdk@^3.5.3": - version "3.5.4" - resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-3.5.4.tgz#957aa74521a4afa61ae99e1e2b948a6e017fcc87" - integrity sha512-+5s05dmB6YUPUnV5vThP96GVG1O4GgS3qYvjYJhe56ds8YFz+keU3/docFBLf8FQOCIO5j7IZtZmVGvz7G+kRw== + version "3.6.3" + resolved "https://registry.yarnpkg.com/@coinbase/wallet-sdk/-/wallet-sdk-3.6.3.tgz#fd96f6f19d5a0090520c1b014ad4737bbc8e1267" + integrity sha512-XUR4poOJE+dKzwBTdlM693CdLFitr046oZOVY3iDnbFcRrrQswhbDji7q4CmUcD4HxbfViX7PFoIwl79YQcukg== dependencies: "@metamask/safe-event-emitter" "2.0.0" - "@solana/web3.js" "1.52.0" + "@solana/web3.js" "^1.70.1" bind-decorator "^1.0.11" bn.js "^5.1.1" buffer "^6.0.3" @@ -1405,7 +1407,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.5.0", "@ethersproject/sha2@^5.7.0": +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== @@ -1519,6 +1521,18 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz#76467a94aa888aeb22aafa43eb6ff889df3a5a7f" integrity sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg== +"@fortawesome/fontawesome-common-types@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz#411e02a820744d3f7e0d8d9df9d82b471beaa073" + integrity sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ== + +"@fortawesome/fontawesome-svg-core@^6.1.2": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.1.tgz#e87e905e444b5e7b715af09b64d27b53d4c8f9d9" + integrity sha512-HELwwbCz6C1XEcjzyT1Jugmz2NNklMrSPjZOWMlc+ZsHIVk+XOvOXLGGQtFBwSyqfJDNgRq4xBCwWOaZ/d9DEA== + dependencies: + "@fortawesome/fontawesome-common-types" "6.2.1" + "@fortawesome/fontawesome-svg-core@^6.2.0": version "6.2.0" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz#11856eaf4dd1d865c442ddea1eed8ee855186ba2" @@ -1526,6 +1540,13 @@ dependencies: "@fortawesome/fontawesome-common-types" "6.2.0" +"@fortawesome/free-solid-svg-icons@^6.1.2": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.1.tgz#2290ea5adcf1537cbd0c43de6feb38af02141d27" + integrity sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw== + dependencies: + "@fortawesome/fontawesome-common-types" "6.2.1" + "@fortawesome/free-solid-svg-icons@^6.2.0": version "6.2.0" resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.0.tgz#8dcde48109354fd7a5ece8ea48d678bb91d4b5f0" @@ -1724,7 +1745,7 @@ dependencies: eslint-scope "5.1.1" -"@noble/ed25519@^1.5.1": +"@noble/ed25519@^1.5.1", "@noble/ed25519@^1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.1.tgz#6899660f6fbb97798a6fbd227227c4589a454724" integrity sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw== @@ -1734,6 +1755,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183" integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA== +"@noble/hashes@^1.1.2": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11" + integrity sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ== + "@noble/hashes@~1.1.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.3.tgz#360afc77610e0a61f3417e497dcf36862e4f8111" @@ -1744,7 +1770,7 @@ resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.6.3.tgz#7eed12d9f4404b416999d0c87686836c4c5c9b94" integrity sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ== -"@noble/secp256k1@^1.3.0": +"@noble/secp256k1@^1.3.0", "@noble/secp256k1@^1.6.3": version "1.7.0" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1" integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw== @@ -2018,6 +2044,26 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.3.tgz#939534757a81f8d69cc854c7692805684ff3111e" integrity sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw== +"@behave-graph/core@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@behave-graph/core/-/core-0.0.1.tgz#73541c220cd1892cf12742f527cf15e39c3285dd" + integrity sha512-CmauyoMqvX2g9t/wOam4rdvouOJZaS6IcnUvaZEkaVKmYoyPahQS/mo8zlkXU/Cr5z0cEzbZQ5h9tcB+foxdlg== + dependencies: + gl-matrix "^3.4.3" + +"@behave-graph/flow@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@behave-graph/flow/-/flow-1.5.0.tgz#6b16e52b5da3885f26bc361fa3cfc3ad5b4b765d" + integrity sha512-DtiHkTsY42JUB2WbdfKftSSKoOp2USmJ8aEmAW5tE6OBZnu6L1lBtdaky5M67gzpPAO2AsErezkMf/LBc7dUWg== + dependencies: + "@fortawesome/fontawesome-svg-core" "^6.1.2" + "@fortawesome/free-solid-svg-icons" "^6.1.2" + "@fortawesome/react-fontawesome" "^0.2.0" + classnames "^2.3.1" + downshift "^6.1.7" + typescript "^4.7.4" + uuid "^8.3.2" + "@pedrouid/environment@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@pedrouid/environment/-/environment-1.0.1.tgz#858f0f8a057340e0b250398b75ead77d6f4342ec" @@ -2129,10 +2175,10 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== -"@rainbow-me/rainbowkit@^0.7.1": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@rainbow-me/rainbowkit/-/rainbowkit-0.7.4.tgz#5cf25d9108ae125c3b1b2b6ddb01d785940011bd" - integrity sha512-o3kwaHZw9O40gfK2DamJZzoqPbK7EHC+8++QtFy5AQ0Sw5P+U0tzb/87KHatSIXyIwrZTEGD0uciPjCGikJMjg== +"@rainbow-me/rainbowkit@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@rainbow-me/rainbowkit/-/rainbowkit-0.8.1.tgz#de93b55a4629cdd33a9acf6a8699a7cd70b29ccd" + integrity sha512-A8BjihvgCY/xjQWOpqgOce+uO4mmIV4Qlo3XMF87kk5WNmyLLaPx2oYTJQz1uOinWE77h4G0HozJr5wnrermgw== dependencies: "@vanilla-extract/css" "1.9.1" "@vanilla-extract/dynamic" "2.0.2" @@ -2436,14 +2482,17 @@ dependencies: buffer "~6.0.3" -"@solana/web3.js@1.52.0": - version "1.52.0" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.52.0.tgz#71bd5c322a31e3e2fa8cda2261c594846810b8ea" - integrity sha512-oG1+BX4nVYZ0OBzmk6DRrY8oBYMsbXVQEf9N9JOfKm+wXSmjxVEEo8v3IPV8mKwR0JvUWuE8lOn3IUDiMlRLgg== +"@solana/web3.js@^1.70.1": + version "1.73.0" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.0.tgz#c65f9f954ac80fca6952765c931dd72e57e1b572" + integrity sha512-YrgX3Py7ylh8NYkbanoINUPCj//bWUjYZ5/WPy9nQ9SK3Cl7QWCR+NmbDjmC/fTspZGR+VO9LTQslM++jr5PRw== dependencies: "@babel/runtime" "^7.12.5" - "@ethersproject/sha2" "^5.5.0" + "@noble/ed25519" "^1.7.0" + "@noble/hashes" "^1.1.2" + "@noble/secp256k1" "^1.6.3" "@solana/buffer-layout" "^4.0.0" + agentkeepalive "^4.2.1" bigint-buffer "^1.1.5" bn.js "^5.0.0" borsh "^0.7.0" @@ -2451,51 +2500,47 @@ buffer "6.0.1" fast-stable-stringify "^1.0.0" jayson "^3.4.4" - js-sha3 "^0.8.0" node-fetch "2" - react-native-url-polyfill "^1.3.0" rpc-websockets "^7.5.0" - secp256k1 "^4.0.2" superstruct "^0.14.2" - tweetnacl "^1.0.3" -"@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1": +"@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1", "@solidity-parser/parser@^0.14.5": version "0.14.5" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" integrity sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg== dependencies: antlr4ts "^0.5.0-alpha.4" -"@tanstack/query-core@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.14.3.tgz#9ab6763b61d5e491816483f3aeed771c1fbd657b" - integrity sha512-1OrxZk5jSKAjTIDgH/OHa6GfVpgGDVbCVf3KHQjXLFdutK4PSSXQIyX7I1HvBR3mYKyvFPo6yXKNG7QrkwUj9g== +"@tanstack/query-core@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.19.1.tgz#2e92d9e8a50884eb231c5beb4386e131ebe34306" + integrity sha512-Zp0aIose5C8skBzqbVFGk9HJsPtUhRVDVNWIqVzFbGQQgYSeLZMd3Sdb4+EnA5wl1J7X+bre2PJGnQg9x/zHOA== -"@tanstack/query-persist-client-core@4.14.3": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-4.14.3.tgz#3401c9eccb685b0e926c3421af3edb9194d592de" - integrity sha512-NXzvzhB26d3GC9HreY7m0WQ+Nqa6zW90e+CyMGo/qt8ghgZ7V6q3sjHBC4S8O+dfSk/eFC0HUpXVPtg0LMs0Tw== +"@tanstack/query-persist-client-core@4.19.1": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-4.19.1.tgz#e473859ee07d281e6da5f213bf4cddb58dfa738f" + integrity sha512-Tlx9tgeYMDoJ5w8O79TXx85Bk46LDQLvojdjWzQbzKWPIEtvwK6OmG2Z3zxz6qEA3FiVmG0BYjsVMsT6PZOhGg== -"@tanstack/query-sync-storage-persister@^4.10.1": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.14.3.tgz#b93d9ffc6ecc9fd47b753279e9214f6e1964d0eb" - integrity sha512-ueejVuQJFLXwGhoFr/KrjNoK8H/XGM2tmspWQi7TpxYwq5haTScQ4XgnFpOV5gR7gQ30H/FsDahpTZfmuco/rA== +"@tanstack/query-sync-storage-persister@^4.14.5": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-4.19.1.tgz#d8e182506dfed65b70dcd3b806261881cc62cc9d" + integrity sha512-gY8rDi6XJ4zPgPF8wEsryu8a87+E9vIByXWryoVO0ucKJzgjVLFNargPSZcMMvRsVw7nhyDCCd/nZZgW+Z3C9g== dependencies: - "@tanstack/query-persist-client-core" "4.14.3" + "@tanstack/query-persist-client-core" "4.19.1" -"@tanstack/react-query-persist-client@^4.10.1": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-4.14.3.tgz#c65134d899a540300f79c2c428459edf02c17903" - integrity sha512-hPjixDlIFGI9o7C+ISR6T5Iczpng9FP4lVL8SSH5bzhJoxzFMSZXnFfp5e2tyC2AfCp5UjkiHo9qn/Sw4vX7Og== +"@tanstack/react-query-persist-client@^4.14.5": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-4.19.1.tgz#8cab5776d2ac4e3134f2022f21797655b0dbd474" + integrity sha512-uNHCBfK7YiNXTjlO3B/Rym7HrNX6OnrcWIw3iiuGFK+gC566/BB3lcLHvriHwanxbnFvWiua8g9hvddeXVQzeA== dependencies: - "@tanstack/query-persist-client-core" "4.14.3" + "@tanstack/query-persist-client-core" "4.19.1" -"@tanstack/react-query@^4.10.1": - version "4.14.3" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.14.3.tgz#3bafea000480463de35721c742265730e2973550" - integrity sha512-PP7DEFsOMl/JaGQr1B4IeGN3lqHrC5EpG+AgG03OlVIOQMtXNhb0N66Nt1pdrladvzYsKD20L3O3550rBzgLtg== +"@tanstack/react-query@^4.14.5": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.19.1.tgz#43356dd537127e76d75f5a2769eb23dafd9a3690" + integrity sha512-5dvHvmc0vrWI03AJugzvKfirxCyCLe+qawrWFCXdu8t7dklIhJ7D5ZhgTypv7mMtIpdHPcECtCiT/+V74wCn2A== dependencies: - "@tanstack/query-core" "4.14.3" + "@tanstack/query-core" "4.19.1" use-sync-external-store "^1.2.0" "@tsconfig/node10@^1.0.7": @@ -2564,6 +2609,11 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g== +"@types/chai@^4.3.4": + version "4.3.4" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.4.tgz#e913e8175db8307d78b4e8fa690408ba6b65dee4" + integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw== + "@types/concat-stream@^1.6.0": version "1.6.1" resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.1.tgz#24bcfc101ecf68e886aaedce60dfd74b632a1b74" @@ -2853,6 +2903,11 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== +"@types/mocha@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.1.tgz#2f4f65bb08bc368ac39c96da7b2f09140b26851b" + integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== + "@types/node@*", "@types/node@>=13.7.0": version "18.11.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" @@ -3212,14 +3267,14 @@ magic-string "^0.26.7" react-refresh "^0.14.0" -"@wagmi/core@^0.6.11": - version "0.6.11" - resolved "https://registry.yarnpkg.com/@wagmi/core/-/core-0.6.11.tgz#7a9aa8781ed759cdaca3c8874bb732d26a682cc4" - integrity sha512-K9QWsm0yBK5agETUaGHyOQi/SttDoSpx0igiHeXSsI6W8Dq2Qml/IfQfjJ+C8Ays5bm63cbauuUs1DUzwJFN9Q== +"@wagmi/core@0.7.9": + version "0.7.9" + resolved "https://registry.yarnpkg.com/@wagmi/core/-/core-0.7.9.tgz#6ba054d8496e81b16b1c36a6faa6942233f4ecfb" + integrity sha512-97KCELUP5Q1AagRyE7SmpLh4/v3xdcy/XMbybLvvtQbgmi3y5oEzy4h/wJMY1hEZlmicqyWZAHHm6xIr1tP5HA== dependencies: - abitype "^0.1.7" + abitype "^0.1.8" eventemitter3 "^4.0.7" - zustand "^4.1.1" + zustand "^4.1.4" "@walletconnect/browser-utils@^1.8.0": version "1.8.0" @@ -3508,10 +3563,15 @@ abbrev@1.0.x: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q== -abitype@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.1.7.tgz#8a42b1845629abed715bd4975532aca43e291eda" - integrity sha512-mNBIrA8xbkR0PrxXSO/7p3irNhyLKO6S4VfU3YrR37cqpJIq1D63Yg8KlovOZkCVAaQ+lJkGDkOhSpv1QmMXIg== +abitype@^0.1.7, abitype@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.1.8.tgz#f47d397b95d81a445a0d0ef2fdd7827131b3fd74" + integrity sha512-2pde0KepTzdfu19ZrzYTYVIWo69+6UbBCY4B1RDiwWgo2XZtFSJhF6C+XThuRXbbZ823J0Rw1Y5cP0NXYVcCdQ== + +abitype@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.2.5.tgz#e571ef2ed99db1cae551fffde5bcbcee4e446177" + integrity sha512-t1iiokWYpkrziu4WL2Gb6YdGvaP9ZKs7WnA39TI8TsW2E99GVRgDPW/xOKhzoCdyxOYt550CNYEFluCwGaFHaA== abort-controller@^3.0.0: version "3.0.0" @@ -3594,6 +3654,15 @@ agent-base@6: dependencies: debug "4" +agentkeepalive@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -4012,11 +4081,6 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -behave-graph@^0.9.10: - version "0.9.10" - resolved "https://registry.yarnpkg.com/behave-graph/-/behave-graph-0.9.10.tgz#55fc7da9492a3fe65d27683d8a4fa6abf7edc51d" - integrity sha512-Npul42y0f4lHPyRz0O2ex3Ho7hxVJLN4xY3SmUMoRmlF79SVXLSkCTTl6sxgXRFq1+7LopfQuWBDAoAH7eL79A== - bidi-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.2.tgz#1a497a762c2ddea377429d2649c9ce0f8a91527f" @@ -4383,6 +4447,19 @@ chai@^4.3.6: pathval "^1.1.1" type-detect "^4.0.5" +chai@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" + integrity sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^4.1.2" + get-func-name "^2.0.0" + loupe "^2.3.1" + pathval "^1.1.1" + type-detect "^4.0.5" + chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4486,7 +4563,7 @@ classic-level@^1.2.0: napi-macros "~2.0.0" node-gyp-build "^4.3.0" -classnames@^2.3.2: +classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== @@ -4629,6 +4706,11 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== +compute-scroll-into-view@^1.0.17: + version "1.0.20" + resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43" + integrity sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -4927,6 +5009,13 @@ deep-eql@^4.0.1: dependencies: type-detect "^4.0.0" +deep-eql@^4.1.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" + integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== + dependencies: + type-detect "^4.0.0" + deep-extend@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -4975,6 +5064,11 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +depd@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + detect-browser@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.2.0.tgz#c9cd5afa96a6a19fda0bbe9e9be48a6b6e1e9c97" @@ -5086,6 +5180,17 @@ dotenv@^16.0.3: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== +downshift@^6.1.7: + version "6.1.12" + resolved "https://registry.yarnpkg.com/downshift/-/downshift-6.1.12.tgz#f14476b41a6f6fd080c340bad1ddf449f7143f6f" + integrity sha512-7XB/iaSJVS4T8wGFT3WRXmSF1UlBHAA40DshZtkrIscIN+VC+Lh363skLxFTvJwtNgHxAMDGEHT4xsyQFWL+UA== + dependencies: + "@babel/runtime" "^7.14.8" + compute-scroll-into-view "^1.0.17" + prop-types "^15.7.2" + react-is "^17.0.2" + tslib "^2.3.0" + draco3d@^1.4.1: version "1.5.5" resolved "https://registry.yarnpkg.com/draco3d/-/draco3d-1.5.5.tgz#6bf4bbdd65950e6153e991cb0dcb8a10323f610e" @@ -5131,6 +5236,11 @@ elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emoji-regex@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.2.1.tgz#a41c330d957191efd3d9dfe6e1e8e1e9ab048b3f" + integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -6432,6 +6542,11 @@ ghost-testrpc@^0.0.2: chalk "^2.4.2" node-emoji "^1.10.0" +gl-matrix@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.3.tgz#fc1191e8320009fd4d20e9339595c6041ddc22c9" + integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA== + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -6851,6 +6966,13 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -8233,6 +8355,33 @@ mocha@^10.0.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" +mocha@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" + integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== + dependencies: + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + mocha@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" @@ -8295,7 +8444,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1, ms@^2.1.2: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -8389,14 +8538,7 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.1: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -"node-fetch@https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz": +node-fetch@2, node-fetch@2.6.7, node-fetch@^2.6.1, "node-fetch@https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz": version "2.6.7" resolved "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz#1b5d62978f2ed07b99444f64f0df39f960a6d34d" @@ -8961,6 +9103,18 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== +prettier-plugin-solidity@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0.tgz#5b23f48cc9c28a1246c6dd89af117234b813f48b" + integrity sha512-gRJCeZ7imbWtNYN2SudjJoPmka5r6jcd2cSTV6FC3pVCtY6LFZbeQQjpKufUEp88hXBAAnkOTOh7TA5xwj9M3A== + dependencies: + "@solidity-parser/parser" "^0.14.5" + emoji-regex "^10.2.1" + escape-string-regexp "^4.0.0" + semver "^7.3.8" + solidity-comments-extractor "^0.0.7" + string-width "^4.2.3" + prettier@^2.3.1, prettier@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" @@ -9143,6 +9297,11 @@ react-is@^16.13.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -9160,13 +9319,6 @@ react-native-fetch-api@^2.0.0: dependencies: p-defer "^3.0.0" -react-native-url-polyfill@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a" - integrity sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ== - dependencies: - whatwg-url-without-unicode "8.0.0-3" - react-reconciler@^0.27.0: version "0.27.0" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b" @@ -9376,6 +9528,11 @@ regenerator-runtime@^0.13.10: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-transform@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" @@ -9717,7 +9874,7 @@ scrypt-js@3.0.1, scrypt-js@^3.0.0: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== -secp256k1@^4.0.1, secp256k1@^4.0.2: +secp256k1@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== @@ -9736,7 +9893,7 @@ semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: +semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== @@ -9845,6 +10002,11 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" +solidity-comments-extractor@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" + integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== + solidity-coverage@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz#bc39604ab7ce0a3fa7767b126b44191830c07813" @@ -10396,11 +10558,6 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" @@ -10494,7 +10651,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== @@ -10625,6 +10782,11 @@ typescript@>=4.5.0, typescript@^4.6.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== +typescript@^4.7.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" @@ -10657,7 +10819,7 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -undici@5.5.1, undici@^5.4.0: +undici@^5.4.0: version "5.5.1" resolved "https://registry.yarnpkg.com/undici/-/undici-5.5.1.tgz#baaf25844a99eaa0b22e1ef8d205bffe587c8f43" integrity sha512-MEvryPLf18HvlCbLSzCW0U00IMftKGI5udnjrQbC5D4P0Hodwffhv+iGfWuJwg16Y/TK11ZFK8i+BPVW2z/eAw== @@ -10850,16 +11012,16 @@ w3name@^1.0.4: ts-standard "^11.0.0" uint8arrays "^3.0.0" -wagmi@^0.7.12: - version "0.7.12" - resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-0.7.12.tgz#d0f284955ea3d5c9c2a380e3f693ef688f8064ad" - integrity sha512-9z4GMpm+dJXMVpvvUpurXPvcosiWK2ivgF1BuO/wlCrvkwgBRVhtP+EbkJpJfP1Wrfl13S2UOfBESQIStDU3Mg== +wagmi@^0.8.10: + version "0.8.10" + resolved "https://registry.yarnpkg.com/wagmi/-/wagmi-0.8.10.tgz#941bebd3cf3f0366e54753040d2f2a141096ce6e" + integrity sha512-svS/Debs6maJX3TlPDXNo+UDfZn8VCHzN2xcMMjPjcu2Z9k2iwFwC0TMLxM1itluzrTcw9n8F8E+f6gb+Aw+EQ== dependencies: "@coinbase/wallet-sdk" "^3.5.3" - "@tanstack/query-sync-storage-persister" "^4.10.1" - "@tanstack/react-query" "^4.10.1" - "@tanstack/react-query-persist-client" "^4.10.1" - "@wagmi/core" "^0.6.11" + "@tanstack/query-sync-storage-persister" "^4.14.5" + "@tanstack/react-query" "^4.14.5" + "@tanstack/react-query-persist-client" "^4.14.5" + "@wagmi/core" "0.7.9" "@walletconnect/ethereum-provider" "^1.8.0" abitype "^0.1.7" use-sync-external-store "^1.2.0" @@ -10923,33 +11085,6 @@ webgl-sdf-generator@1.1.1: resolved "https://registry.yarnpkg.com/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz#3e1b422b3d87cd3cc77f2602c9db63bc0f6accbd" integrity sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -whatwg-url-without-unicode@8.0.0-3: - version "8.0.0-3" - resolved "https://registry.yarnpkg.com/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz#ab6df4bf6caaa6c85a59f6e82c026151d4bb376b" - integrity sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig== - dependencies: - buffer "^5.4.3" - punycode "^2.1.1" - webidl-conversions "^5.0.0" - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -11231,3 +11366,10 @@ zustand@^4.1.1: integrity sha512-k2jVOlWo8p4R83mQ+/uyB8ILPO2PCJOf+QVjcL+1PbMCk1w5OoPYpAIxy9zd93FSfmJqoH6lGdwzzjwqJIRU5A== dependencies: use-sync-external-store "1.2.0" + +zustand@^4.1.4: + version "4.1.5" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.5.tgz#7402b511f5b23ccb0f9ba6d20ae01ec817e16eb6" + integrity sha512-PsdRT8Bvq22Yyh1tvpgdHNE7OAeFKqJXUxtJvj1Ixw2B9O2YZ1M34ImQ+xyZah4wZrR4lENMoDUutKPpyXCQ/Q== + dependencies: + use-sync-external-store "1.2.0"