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 (