diff --git a/README.md b/README.md index 931ceb8..2f84da6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,23 @@ # Metapro Plugin for Construct 3 +Metapro is a powerful plugin for Construct 3 that enables developers to integrate blockchain functionality, user management, and leaderboard systems into their games and applications. + +## Table of Contents + +- [Features](#features) +- [Installation](#installation) +- [Getting Started](#getting-started) +- [Properties](#properties) +- [Supported API Calls](#supported-api-calls) + - [Actions](#actions) + - [Conditions](#conditions) + - [Expressions](#expressions) +- [Usage](#usage) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [Author](#author) +- [License](#license) + ## Features - **Account Management**: @@ -44,24 +62,38 @@ ## Installation -The Metapro Plugin can be added through either the Construct store or as a dev addon in the editor. +The Metapro Plugin can be added to your Construct 3 project in two ways: + +1. Through the Construct store: + + - Open Construct 3 + - Go to Menu -> View -> Addons + - Search for "Metapro" + - Click "Add" to install the plugin + +2. As a dev addon in the editor: + - Download the Metapro Plugin addon from the `builds` directory in this repository + - In Construct 3, go to Menu -> View -> Addons + - Click "Install new addon" and select the downloaded file -## Downloads +## Getting Started -The Metapro Plugin addons are available in builds directory. +1. After installation, add the Metapro object to your project layout. +2. Configure the plugin properties (see [Properties](#properties) section). +3. Use the provided actions, conditions, and expressions in your event sheets to interact with Metapro functionality. -### Properties +## Properties -| **Property Name** | **Description** | -| --------------------------- | -------------------------------------------------------------- | -| **Users Service API URL** | The URL of the Users Service API used by the Metapro system. | -| **Project ID** | The unique identifier for your project in Metapro. | -| **Leaderboard ID** | The ID of the leaderboard used in the Metapro system. | -| **Referral Leaderboard ID** | The ID of the referral leaderboard used in the Metapro system. | -| **Leaderboard API Key** | The API key required to access the leaderboard. | -| **Leaderboard API URL** | The URL of the Leaderboard API used in the Metapro system. | -| **Referral API URL** | The URL of the Referral API used in the Metapro system. | -| **Platform ID** | The ID of the platform associated with the project. | +| Property Name | Description | +| ----------------------- | -------------------------------------------------------------- | +| Users Service API URL | The URL of the Users Service API used by the Metapro system. | +| Project ID | The unique identifier for your project in Metapro. | +| Leaderboard ID | The ID of the leaderboard used in the Metapro system. | +| Referral Leaderboard ID | The ID of the referral leaderboard used in the Metapro system. | +| Leaderboard API Key | The API key required to access the leaderboard. | +| Leaderboard API URL | The URL of the Leaderboard API used in the Metapro system. | +| Referral API URL | The URL of the Referral API used in the Metapro system. | +| Platform ID | The ID of the platform associated with the project. | ## Supported API Calls @@ -90,6 +122,7 @@ The Metapro Plugin addons are available in builds directory. | **Request User NFTs** | Retrieves the NFTs owned by a user based on a provided query as stringified JSON. | **Query**: A stringified JSON representation of the query params. Example format: `{'tokens': [{'contractAddress': '0x3203c9e46ca618c8c1ce5dc67e7e9d75f5da2377', 'tokenId': 123}], 'sort': {'sortKey': 'token.creationBlock', 'sortDirection': 'desc'}}`. | | **Set Transaction Status** | Sets the internal transaction status variable to the provided status value. | **Status**: The status value to set for the internal transaction status variable. | | **Multiple Read Contract** | Reads data from a smart contract at the specified address using the provided ABI, multiple function names, input data, and RPC URL. | **Contract Address**: The address of the smart contract.
**ABI**: A JSON stringified representation of the ABI.
**Function Names**: A JSON stringified representation of the array of function names to be called.
**Inputs Data**: A JSON stringified representation of the array of input data for each function call.
**RPC URL**: The URL of the Remote Procedure Call (RPC) endpoint for interacting with the blockchain. | +| **Send Crypto** | Send a transaction to transfer cryptocurrency or tokens from one address to another, based on the specified token contract. | **Token Contract Address**: The address of the token contract to send cryptocurrency from. If empty, sends native cryptocurrency (e.g., ETH, BNB).
**Amount**: The amount of cryptocurrency to send, provided in proper unit. Unit converter: https://etherscan.io/unitconverter.
**Receiver Address**: The address of the receiver.
**Chain ID**: The target blockchain network ID (e.g., 1 for Ethereum Mainnet, 56 for Binance Smart Chain). | ### Conditions @@ -149,6 +182,35 @@ The Metapro Plugin addons are available in builds directory. | **Get NFT API URL** | Retrieve the URL for accessing the NFT API. | | **Get Transaction Status** | Retrieves the current transaction status from the internal status variable. | +## Usage + +Here's a simple example of how to use the Metapro plugin to request a user's account: + +1. Add the Metapro object to your layout. +2. In your event sheet, add a new event. +3. For the event's condition, you might use a button click or a system start. +4. Add an action to this event: Metapro -> Request Account. +5. Add another event with the condition: Metapro -> On Account Received. +6. In this event, you can add actions to use the received account, such as displaying it or using it for further authentication. + +## Troubleshooting + +- If you encounter issues with API calls, ensure that all required properties are correctly set in the Metapro object. +- Check the browser console for any error messages related to Metapro. +- Use the "Get Last Error" expression to retrieve detailed error information when an operation fails. + +## Contributing + +We welcome contributions to improve the Metapro plugin! If you'd like to contribute: + +1. Fork the repository +2. Create a new branch for your feature or bug fix +3. Make your changes and commit them with clear, descriptive messages +4. Push your changes to your fork +5. Submit a pull request with a description of your changes + +Please ensure your code adheres to the existing style and includes appropriate tests. + ## Author metapro diff --git a/aces.json b/aces.json index 7c5c3cf..ef0690b 100644 --- a/aces.json +++ b/aces.json @@ -284,6 +284,17 @@ "id": "set-transaction-status", "scriptName": "SetTransactionStatus", "params": [{ "id": "status", "type": "string" }] + }, + { + "id": "send-crypto", + "scriptName": "SendCrypto", + "isAsync": true, + "params": [ + { "id": "token-address", "type": "string" }, + { "id": "amount", "type": "string" }, + { "id": "receiver", "type": "string" }, + { "id": "chain-id", "type": "number" } + ] } ], "expressions": [ diff --git a/addon.json b/addon.json index fb38d97..e4f2d7b 100644 --- a/addon.json +++ b/addon.json @@ -3,7 +3,7 @@ "type": "plugin", "name": "Metapro Plugin", "id": "MetaproPlugin", - "version": "3.11.0.0", + "version": "3.12.0.0", "author": "metapro", "website": "https://metapro.one/", "documentation": "https://metapro.one/", diff --git a/builds/metapro-c3-plugin-v3.12.0.0.c3addon b/builds/metapro-c3-plugin-v3.12.0.0.c3addon new file mode 100644 index 0000000..d966dd1 Binary files /dev/null and b/builds/metapro-c3-plugin-v3.12.0.0.c3addon differ diff --git a/c3runtime/actions.js b/c3runtime/actions.js index 7a1658c..e2ef93f 100644 --- a/c3runtime/actions.js +++ b/c3runtime/actions.js @@ -113,4 +113,7 @@ self.C3.Plugins.MetaproPlugin.Acts = { SetTransactionStatus(status) { this._SetTransactionStatus(status); }, + async SendCrypto(token_address, amount, receiver, chain_id) { + await this._SendCrypto(token_address, amount, receiver, chain_id); + }, }; diff --git a/c3runtime/instance.js b/c3runtime/instance.js index 95a0d43..d6ce480 100644 --- a/c3runtime/instance.js +++ b/c3runtime/instance.js @@ -1216,7 +1216,7 @@ C3.Plugins.MetaproPlugin.Instance = class MetaproPluginInstance extends ( } catch (error) { console.log(error); this._transactionStatus = "error"; - this.HandleError(error.message); + this.HandleError(error.data?.message || error.message); } } @@ -1260,7 +1260,7 @@ C3.Plugins.MetaproPlugin.Instance = class MetaproPluginInstance extends ( this.OnReadContractDataReceived(); } catch (error) { console.log(error); - this.HandleError("Failed to send transaction: " + error.message); + this.HandleError(error.data?.message || error.message); } } @@ -1308,7 +1308,7 @@ C3.Plugins.MetaproPlugin.Instance = class MetaproPluginInstance extends ( this.OnMultipleReadContractDataReceived(); } catch (error) { console.log(error); - this.HandleError("Failed to send transaction: " + error.message); + this.HandleError(error.data?.message || error.message); } } @@ -1401,6 +1401,107 @@ C3.Plugins.MetaproPlugin.Instance = class MetaproPluginInstance extends ( } } + async _SendCrypto(token_address, amount, receiver, chain_id) { + const abi = [ + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "transfer", + outputs: [{ name: "", type: "bool" }], + type: "function", + }, + { + constant: true, + inputs: [{ name: "_owner", type: "address" }], + name: "balanceOf", + outputs: [{ name: "balance", type: "uint256" }], + type: "function", + }, + ]; + + this._transactionStatus = "pending"; + try { + if (!this._account) { + throw new Error("Account information is missing or not initialized."); + } + + await this.PostToDOMAsync("switch-chain", chain_id); + + const amountInBigInt = BigInt(amount); + const currentGasPrice = await web3.eth.getGasPrice(); + let transaction; + + if (!token_address) { + const tx = { + from: this._account, + to: receiver, + value: amountInBigInt, + }; + const estimatedGas = await web3.eth.estimateGas(tx); + + transaction = await web3.eth.sendTransaction({ + ...tx, + gas: estimatedGas, + gasPrice: currentGasPrice, + }); + } else { + const contract = new web3.eth.Contract(abi, token_address); + const estimatedGas = await contract.methods + .transfer(receiver, amountInBigInt) + .estimateGas({ + from: this._account, + }); + + transaction = await contract.methods + .transfer(receiver, amountInBigInt) + .send({ + from: this._account, + gas: estimatedGas, + gasPrice: currentGasPrice, + }); + } + + this._lastTransactionHash = transaction.transactionHash; + + // Wait for transaction confirmation + const timeout = 120000; // 120sec + const startTime = Date.now(); + let receipt = null; + + while (receipt === null) { + receipt = await web3.eth.getTransactionReceipt( + transaction.transactionHash + ); + + if (receipt === null) { + const elapsedTime = Date.now() - startTime; + + if (elapsedTime > timeout) { + throw new Error("Transaction confirmation timed out."); + } + + // Wait for a short time before polling again to avoid spamming the node + await new Promise((resolve) => setTimeout(resolve, 3000)); + } + } + + if (!!receipt && this.SerializeData(receipt.status)) { + this._transactionStatus = "success"; + } else { + throw new Error("Transaction failed or was reverted."); + } + + this.OnTransactionSent(); + } catch (error) { + console.log(error); + this._transactionStatus = "error"; + this.HandleError(error.data?.message || error.message); + } + } + // Conditions OnAccountReceived() { this._triggerAccountReceived = true; diff --git a/lang/en-US.json b/lang/en-US.json index dd8db3a..ae5f88a 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -448,6 +448,29 @@ "desc": "The status value to set for the internal transaction status variable." } } + }, + "send-crypto": { + "list-name": "Send Crypto", + "display-text": "Send {1} amount of tokens from contract address {0} to receiver {2} on chain ID {3}.", + "description": "Sends cryptocurrency from the specified contract address to a receiver, including the amount and target chain ID.", + "params": { + "token-address": { + "name": "Token Contract Address", + "desc": "Address of the token contract to send cryptocurrency from." + }, + "amount": { + "name": "Amount", + "desc": "The amount of cryptocurrency to send." + }, + "receiver": { + "name": "Receiver Address", + "desc": "The address of the receiver to whom the cryptocurrency will be sent." + }, + "chain-id": { + "name": "Chain ID", + "desc": "The target blockchain network ID, provided as a decimal." + } + } } }, "expressions": { diff --git a/package.json b/package.json index d8f2a88..e9b1097 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plugin", - "version": "3.11.0.0", + "version": "3.12.0.0", "description": "", "main": "plugin.js", "scripts": { diff --git a/plugin.js b/plugin.js index 5aa455a..34864c3 100644 --- a/plugin.js +++ b/plugin.js @@ -11,7 +11,7 @@ const SDK = self.SDK; const PLUGIN_ID = "MetaproPlugin"; //////////////////////////////////////////// -const PLUGIN_VERSION = "3.11.0.0"; +const PLUGIN_VERSION = "3.12.0.0"; const PLUGIN_CATEGORY = "platform-specific"; const PLUGIN_CLASS = (SDK.Plugins.MetaproPlugin = class MyCustomPlugin extends (