From 5b00bbb4f2def2c5b962cf9879dc627f72137a17 Mon Sep 17 00:00:00 2001 From: Iredia Ebikade <40021650+irediaes@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:32:58 +0100 Subject: [PATCH 1/2] feat: added get real did submission example using the contract --- .../PeaqDidGetRealCampaignClass.ts | 463 ++++++++++++++++++ examples/js/package-lock.json | 100 ++++ examples/js/package.json | 1 + 3 files changed, 564 insertions(+) create mode 100644 examples/js/getRealCampaignExamples/PeaqDidGetRealCampaignClass.ts diff --git a/examples/js/getRealCampaignExamples/PeaqDidGetRealCampaignClass.ts b/examples/js/getRealCampaignExamples/PeaqDidGetRealCampaignClass.ts new file mode 100644 index 0000000..3b0f69d --- /dev/null +++ b/examples/js/getRealCampaignExamples/PeaqDidGetRealCampaignClass.ts @@ -0,0 +1,463 @@ +import { AbiItem } from 'web3-utils'; +import { AbiCoder, ethers } from 'ethers'; +import { Sdk } from '@peaq-network/sdk'; +import { Keyring } from '@polkadot/keyring' +import { u8aToHex, stringToU8a } from '@polkadot/util'; +import { mnemonicGenerate, cryptoWaitReady } from '@polkadot/util-crypto'; +import axios from 'axios'; + +// Import the contract ABI +import {abi} from '../MachineStationFactoryABI.json'; +import { CustomDocumentFields } from '@peaq-network/sdk/src/modules/did'; + +// peaq RPC URL: https://peaq.api.onfinality.io/public +const rpcURL = "https://erpc-async.agung.peaq.network"; // replace with peaq RPC URL during mainnet deployment. +const chainID = 3338; + +// Contract details +// Replace with the your dedicated machine station factory contract address during deployment +const MachineStationFactoryContractAddress: string = '0x31B80DbA6806E0335Bfac6D12A5A820C32D73d68'; +const contract = new ethers.ContractFactory(abi as AbiItem[], MachineStationFactoryContractAddress); +const abiCoder = new AbiCoder(); + +// Wallet details +const ownerPrivateKey: string | undefined = process.env.CONTRACT_OWNER_PRIVATE_KEY??""; // Replace with owner wallet's private key +const machineOwnerPrivateKey: string | undefined = process.env.MACHINE_OWNER_PRIVATE_KEY??""; // Replace with machine owner wallet's private key + +const provider = new ethers.JsonRpcProvider(rpcURL); +const ownerAccount = new ethers.Wallet(ownerPrivateKey, provider); +const machineOwnerAccount = new ethers.Wallet(machineOwnerPrivateKey, provider); + +const DEPIN_SEED = ""; // The seed phrase for DePIN Project, used for signing the DID + +const PEAQ_SERVICE_URL = ""; // URL to the peaq campaign service +// dev URL: https://lift-off-campaign-service-jx-devbr.jx.peaq.network + +const API_KEY = ""; // peaq campaign service APIKEY value added to the header of all requests +// dev APIKEY: aa69cb8e92b2e27eb26996fc9b02f6df24 +// production APIKEY will be sent to you after deployment + +const PROJECT_API_KEY = ""; // peaq campaign service unique APIKEY value for specific project added to the header of all requests +// dev P-APIKEY: all_0821fcaa69 +// your production P-APIKEY will be sent to you after deployment + + +class PeaqGetRealCampaignClass { + + async deployMachineSmartAccount( + machineOwner: string, + nonce: BigInt, + signature: string + ): Promise { + try { + // Encode the method call data + const methodData = contract.interface.encodeFunctionData( + "deployMachineSmartAccount", + [machineOwner, nonce, signature] + ); + + // Send the transaction and get the receipt + const txResponse = await this.sendTransaction(methodData); + + let receipt = await txResponse.wait().finally(); + + const logs = receipt?.logs; + + // Compute the event signature + const eventSignature = ethers.id("MachineSmartAccountDeployed(address)"); + console.log("eventSignature: ", eventSignature); + + // Find the relevant log + const log = logs?.find((log) => log.topics[0] === eventSignature); + + console.log("raw log: ", log); + + if (!log) { + throw new Error("MachineSmartAccountDeployed event not found in logs"); + } + + + // The deployed address is stored as the second topic (topics[1]) in a 32-byte format + const rawDeployedAddress = log.topics[1]; + const deployedAddress = ethers.getAddress(`0x${rawDeployedAddress.slice(26)}`); // Extract last 20 bytes + + console.log('Machine Deploy Tx executed:', receipt?.hash); + console.log("Machine Deployed Address:", deployedAddress); + return deployedAddress; + + } catch (error: any) { + console.error("Transaction failed. Error:", error); + + // Check if the error is a revert error with data + if (error.data) { + try { + // Decode the revert error using the contract's ABI + const iface = new ethers.Interface(contract.interface.fragments); + const decodedError = iface.parseError(error.data); + + console.log("Decoded Error:", decodedError); + + // Extract error name and arguments + // const { name, args } = decodedError; + // console.log("Error Name:", name); + // console.log("Arguments:", args); + + // if (name === "InvalidSignature") { + // console.error("InvalidSignature Error Details:"); + // console.error("structHash:", args.structHash); + // console.error("nonce:", args.nonce.toString()); + // } + } catch (decodeError) { + console.error("Failed to decode error data:", decodeError); + } + } else { + console.error("Transaction failed without revert data:", error); + } + } + return ""; + } + + async submitDIDTx() { + try { + const machineOwner = machineOwnerAccount.address; + const nonce = this.getRandomNonce(); + const target = "0x0000000000000000000000000000000000000800"; + + // deploy a Machine Smart Account + const deploySignature = await this.ownerSignTypedDataDeployMachineSmartAccount(machineOwner, nonce) + const machineAddress = await this.deployMachineSmartAccount(machineOwner, nonce, deploySignature); + + if (machineAddress.length < 42) throw new Error("invalid machine address"); + + const addAttributeFunctionSignature = + "addAttribute(address,bytes,bytes,uint32)"; + const createDidFunctionSelector = ethers + .keccak256(ethers.toUtf8Bytes(addAttributeFunctionSignature)) + .substring(0, 10); + + // Creating key pair for the subject of the DID from seed + const {keyPair} = await this.generateNewDidAddress(); + const DIDSubjectPair = keyPair; + // Address derived from DIDSubjectPair + const DIDAddress = DIDSubjectPair.address; + + + // Email address signature will be created and did address will be used to track the creator + const postdata = { + email: "", + did_address: DIDAddress, + tag: "" // replace with your unique custom task tag + }; + + // Creating email signature + const emailSignature = await this.createEmailSignature(postdata); + + const didName = `did:peaq:${machineAddress}#test`; + const name = ethers.hexlify(ethers.toUtf8Bytes(didName)); + + const value = await this.generateDIDHash(machineOwner, DIDAddress, emailSignature); + + // converted the original did value to bytes and then hex to retain the original value during decoding + const didVal = ethers.hexlify(ethers.toUtf8Bytes(value)); + + const validityFor = 0; + + const params = abiCoder.encode( + ["address", "bytes", "bytes", "uint32"], + [machineAddress, name, didVal, validityFor] + ); + + const calldata = params.replace("0x", createDidFunctionSelector); + + const machineOwnerSignature = await this.machineOwnerSignTypedDataExecuteMachine(machineAddress, target, calldata, nonce); + const ownerSignature = await this.ownerSignTypedDataExecuteMachineTransaction(machineAddress, target, calldata, nonce); + + await this.executeMachineTransaction( + machineAddress, + target, + calldata, + nonce, + ownerSignature, + machineOwnerSignature + ); + } catch (error) { + console.error("Error:", error); + + } + } + + async executeMachineTransaction( + machineAddress: string, + target: string, + data: string, + nonce: BigInt, + signature: string, + machineOwnerSignature: string + ): Promise { + try { + + const methodData = contract.interface.encodeFunctionData( + "executeMachineTransaction", + [machineAddress, target, data, nonce, signature, machineOwnerSignature] + ); + + // Send the transaction and get the receipt + const txResponse = await this.sendTransaction(methodData); + + let receipt = await txResponse.wait().finally(); + + console.log('Machine Tx executed:', receipt?.hash); + + } catch (error: any) { + console.error("Transaction failed. Error:", error); + + // Check if the error is a revert error with data + if (error.data) { + try { + // Decode the revert error using the contract's ABI + const iface = new ethers.Interface(contract.interface.fragments); + const decodedError = iface.parseError(error.data); + + console.log("Decoded Error:", decodedError); + + // Extract error name and arguments + // const { name, args } = decodedError; + // console.log("Error Name:", name); + // console.log("Arguments:", args); + + // if (name === "InvalidSignature") { + // console.error("InvalidSignature Error Details:"); + // console.error("structHash:", args.structHash); + // console.error("nonce:", args.nonce.toString()); + // } + } catch (decodeError) { + console.error("Failed to decode error data:", decodeError); + } + } else { + console.error("Transaction failed without revert data:", error); + } + } + } + + async ownerSignTypedDataDeployMachineSmartAccount( + machineOwner: string, + nonce: BigInt + ): Promise { + const domain = { + name: "MachineStationFactory", + version: "1", + chainId: chainID, + verifyingContract: MachineStationFactoryContractAddress, + }; + + // Define the type definition for the data + const types = { + DeployMachineSmartAccount: [ + { name: "machineOwner", type: "address" }, + { name: "nonce", type: "uint256" }, + ], + }; + + // Define the data to be signed + const message = { + machineOwner: machineOwner, + nonce: nonce, + }; + + // Sign the typed data + const signature = await ownerAccount.signTypedData(domain, types, message); + + return signature; + } + + async machineOwnerSignTypedDataExecuteMachine( + machineAddress: string, + target: string, + data: string, + nonce: BigInt, + ): Promise { + const domain = { + name: "MachineSmartAccount", + version: "1", + chainId: chainID, + verifyingContract: machineAddress, + }; + + const types = { + Execute: [ + { name: "target", type: "address" }, + { name: "data", type: "bytes" }, + { name: "nonce", type: "uint256" }, + ], + }; + + const message = { + target: target, + data: data, + nonce: nonce, + }; + + const signature = await machineOwnerAccount.signTypedData(domain, types, message); + + return signature; + } + + async ownerSignTypedDataExecuteMachineTransaction( + machineAddress: string, + target: string, + data: string, + nonce: BigInt, + ): Promise { + // Step 1: Define the EIP-712 Domain + const domain = { + name: "MachineStationFactory", + version: "1", + chainId: chainID, + verifyingContract: MachineStationFactoryContractAddress, + }; + + const types = { + ExecuteMachineTransaction: [ + { name: "machineAddress", type: "address" }, + { name: "target", type: "address" }, + { name: "data", type: "bytes" }, + { name: "nonce", type: "uint256" }, + ], + }; + + const message = { + machineAddress: machineAddress, + target: target, + data: data, + nonce: nonce, + }; + + + const signature = await ownerAccount.signTypedData(domain, types, message); + + return signature; + } + + // Helper function to sign and send transactions + async sendTransaction( + methodData: string + ): Promise { + + + const tx = { + to: MachineStationFactoryContractAddress, + data: methodData, + }; + + return await ownerAccount.sendTransaction(tx); + } + + getRandomNonce(): BigInt { + const now = BigInt(Date.now()); + const randomPart = BigInt(Math.floor(Math.random() * 1e18)); + return now * randomPart; + } + + async generateDIDHash(machineOwnerAddress: string, didAddress: string, emailSignature: string): Promise { + const keyring = new Keyring({ type: "sr25519" }); + + // Creating key pair for the DePin from seed + const DePinPair = keyring.addFromUri(DEPIN_SEED); + + // Generating signature using DePinSeed and DIDSubjectPair's address as data + const issuerSignature = u8aToHex(DePinPair.sign(stringToU8a(didAddress))); + + + const customFields: CustomDocumentFields = { + prefix: 'peaq', + controller: '5FEw7aWmqcnWDaMcwjKyGtJMjQfqYGxXmDWKVfcpnEPmUM7q', + signature: { + type: 'Ed25519VerificationKey2020', + issuer: DePinPair?.address, + hash: issuerSignature + }, + services: [ + { + id: '#emailSignature', + type: 'emailSignature', + data: emailSignature + }, + { + id: '#owner', + type: 'owner', + data: machineOwnerAddress + }, + ] + } + + const did_hash = await Sdk.generateDidDocument({ address: didAddress, customDocumentFields: customFields }); + return did_hash.value; + }; + + async generateNewDidAddress() { + await cryptoWaitReady(); + // Generate a new mnemonic + const mnemonic = mnemonicGenerate(); + + // Create a keyring instance + const keyring = new Keyring({ type: 'sr25519' }); + + // Add a new account to the keyring + const keyPair = keyring.addFromMnemonic(mnemonic); + + console.log('Generated Address:', keyPair.address); + + return { + keyPair, + mnemonic, + address: keyPair.address, + }; + } + + // Function to create email signature + async createEmailSignature(data:any) { + try { + + + const response = await axios.post(`${PEAQ_SERVICE_URL}/v1/sign`, data, { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'APIKEY': API_KEY, + 'P-APIKEY': PROJECT_API_KEY + } + }) + .then((response:any) => { + return response.data; + }) + .catch((err:any) => { + console.error(err) + throw err; + }); + + // Note: You may need to adjust the response handling based on the service's response structure + return response.data.signature; + + } catch (error) { + console.error("Error creating email signature", error); + throw error; + } + }; + +} + + + +// Main function to create DID +const createDid = async () => { + const campaignClass = new PeaqGetRealCampaignClass(); + + try { + await campaignClass.submitDIDTx(); + } catch (error) { + console.error("DID Creation Error:", error); + } +}; +createDid().catch(console.error); + + diff --git a/examples/js/package-lock.json b/examples/js/package-lock.json index d9db818..e39316e 100644 --- a/examples/js/package-lock.json +++ b/examples/js/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@openzeppelin/contracts": "^5.1.0", "@peaq-network/sdk": "^0.7.2", + "axios": "^1.7.9", "dotenv": "^10.0.0", "ethers": "^6.13.4", "ts-node": "^10.9.2", @@ -1118,6 +1119,12 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1133,6 +1140,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -1186,6 +1204,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -1276,6 +1306,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1497,6 +1536,26 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1506,6 +1565,20 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -1748,6 +1821,27 @@ "node": ">= 0.4" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mock-socket": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", @@ -1841,6 +1935,12 @@ "node": ">= 8" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", diff --git a/examples/js/package.json b/examples/js/package.json index 4035853..62265d2 100644 --- a/examples/js/package.json +++ b/examples/js/package.json @@ -38,6 +38,7 @@ "dependencies": { "@openzeppelin/contracts": "^5.1.0", "@peaq-network/sdk": "^0.7.2", + "axios": "^1.7.9", "dotenv": "^10.0.0", "ethers": "^6.13.4", "ts-node": "^10.9.2", From fabc38b35deecf4d3237d9a316fd1340e70ecb76 Mon Sep 17 00:00:00 2001 From: Iredia Ebikade <40021650+irediaes@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:35:05 +0100 Subject: [PATCH 2/2] feat: added get real storage submission example using the contract --- .../PeaqDidGetRealCampaignClass.ts | 2 - .../PeaqStorageGetRealCampaignClass.ts | 232 ++++++++++++++++++ examples/js/usingWeb3/index.ts | 14 +- 3 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 examples/js/getRealCampaignExamples/PeaqStorageGetRealCampaignClass.ts diff --git a/examples/js/getRealCampaignExamples/PeaqDidGetRealCampaignClass.ts b/examples/js/getRealCampaignExamples/PeaqDidGetRealCampaignClass.ts index 3b0f69d..9a9d2df 100644 --- a/examples/js/getRealCampaignExamples/PeaqDidGetRealCampaignClass.ts +++ b/examples/js/getRealCampaignExamples/PeaqDidGetRealCampaignClass.ts @@ -446,8 +446,6 @@ class PeaqGetRealCampaignClass { } - - // Main function to create DID const createDid = async () => { const campaignClass = new PeaqGetRealCampaignClass(); diff --git a/examples/js/getRealCampaignExamples/PeaqStorageGetRealCampaignClass.ts b/examples/js/getRealCampaignExamples/PeaqStorageGetRealCampaignClass.ts new file mode 100644 index 0000000..90f873d --- /dev/null +++ b/examples/js/getRealCampaignExamples/PeaqStorageGetRealCampaignClass.ts @@ -0,0 +1,232 @@ +import { AbiItem } from 'web3-utils'; +import { AbiCoder, ethers } from 'ethers'; +import axios from 'axios'; + +// Import the contract ABI +import {abi} from '../MachineStationFactoryABI.json'; + +// peaq RPC URL: https://peaq.api.onfinality.io/public +const rpcURL = "https://erpc-async.agung.peaq.network"; // replace with peaq RPC URL during mainnet deployment. +const chainID = 3338; + +// Contract details +// Replace with the your dedicated machine station factory contract address during deployment +const MachineStationFactoryContractAddress: string = '0x31B80DbA6806E0335Bfac6D12A5A820C32D73d68'; +const contract = new ethers.ContractFactory(abi as AbiItem[], MachineStationFactoryContractAddress); +const abiCoder = new AbiCoder(); + +// Wallet details +const ownerPrivateKey: string | undefined = process.env.CONTRACT_OWNER_PRIVATE_KEY??""; // Replace with owner wallet's private key + +const provider = new ethers.JsonRpcProvider(rpcURL); +const ownerAccount = new ethers.Wallet(ownerPrivateKey, provider); + +const PEAQ_SERVICE_URL = ""; // URL to the peaq campaign service +// dev URL: https://lift-off-campaign-service-jx-devbr.jx.peaq.network + +const API_KEY = ""; // peaq campaign service APIKEY value added to the header of all requests +// dev APIKEY: aa69cb8e92b2e27eb26996fc9b02f6df24 +// production APIKEY will be sent to you after deployment + +const PROJECT_API_KEY = ""; // peaq campaign service unique APIKEY value for specific project added to the header of all requests +// dev P-APIKEY: all_0821fcaa69 +// your production P-APIKEY will be sent to you after deployment + + +class PeaqGetRealCampaignClass { + + + async submitGetRealStorageTx() { + try { + const nonce = this.getRandomNonce(); + const target = '0x0000000000000000000000000000000000000801'; + + const addItemFunctionSignature = "addItem(bytes,bytes)"; + const addItemFunctionSelector = ethers.keccak256(ethers.toUtf8Bytes(addItemFunctionSignature)).substring(0, 10); + + let now = new Date().getTime(); + + // Send data to peaq data storage service + // replace with your unique task identity tag used to track your tasks on-chain. + // all item types are required to be constructed in this format + // [] + [-] + [a-zA-Z0-9-_] + // we use the dash [-] to split the item type when the event parser receives the chain events + // ItemType has to be unique on every requests + const itemType = "-"+now; // e.g "GET-REAL-CAMPAIGN-ITEM-TYPE-001", "GET-REAL-CAMPAIGN-ITEM-TYPE-002" + const postData = { + item_type: itemType, + email: 'user@example.com', // replace this email with the user email address + tag: "", // replace with your unique custom task tag + tags: ["", "<20_YOUR-CUSTOM-TASK-TAG>", "<30_YOUR-CUSTOM-TASK-TAG>"] // replace with your unique custom task tags + }; + + // register the itemType and tag or tags + await this.registerItemTypeAndTags(postData) + + // encode the item storage data for submission to peaq network + const itemTypeHex = ethers.hexlify(ethers.toUtf8Bytes(itemType)); + const item = "TASK-COMPLETED" + const itemHex = ethers.hexlify(ethers.toUtf8Bytes(item)); + + const params = abiCoder.encode( + ["bytes", "bytes"], + [itemTypeHex, itemHex] + ); + + const calldata = params.replace("0x", addItemFunctionSelector); + const ownerSignature = await this.ownerSignTypedDataExecuteTransaction(target, calldata, nonce) + + await this.executeTransaction(target, calldata, nonce, ownerSignature,); + } catch (error) { + console.error('Error:', error); + } + } + + async executeTransaction( + target: string, + data: string, + nonce: BigInt, + signature: string, + ): Promise { + try { + // Encode the method call data + const methodData = contract.interface.encodeFunctionData( + "executeTransaction", + [target, data, nonce, signature] + ); + + // Send the transaction and get the receipt + const txResponse = await this.sendTransaction(methodData); + + let receipt = await txResponse.wait().finally(); + + console.log('Normal Tx executed:', receipt?.hash); + + } catch (error: any) { + console.error("Transaction failed. Error:", error); + + // Check if the error is a revert error with data + if (error.data) { + try { + // Decode the revert error using the contract's ABI + const iface = new ethers.Interface(contract.interface.fragments); + const decodedError = iface.parseError(error.data); + + console.log("Decoded Error:", decodedError); + + // Extract error name and arguments + // const { name, args } = decodedError; + // console.log("Error Name:", name); + // console.log("Arguments:", args); + + // if (name === "InvalidSignature") { + // console.error("InvalidSignature Error Details:"); + // console.error("structHash:", args.structHash); + // console.error("nonce:", args.nonce.toString()); + // } + } catch (decodeError) { + console.error("Failed to decode error data:", decodeError); + } + } else { + console.error("Transaction failed without revert data:", error); + } + } + } + + async ownerSignTypedDataExecuteTransaction( + target: string, + data: string, + nonce: BigInt, + ): Promise { + // Step 1: Define the EIP-712 Domain + const domain = { + name: "MachineStationFactory", + version: "1", + chainId: chainID, + verifyingContract: MachineStationFactoryContractAddress, + }; + + const types = { + ExecuteTransaction: [ + { name: "target", type: "address" }, + { name: "data", type: "bytes" }, + { name: "nonce", type: "uint256" }, + ], + }; + + const message = { + target: target, + data: data, + nonce: nonce, + }; + + const signature = await ownerAccount.signTypedData(domain, types, message); + + return signature; + } + + // Helper function to sign and send transactions + async sendTransaction( + methodData: string + ): Promise { + + + const tx = { + to: MachineStationFactoryContractAddress, + data: methodData, + }; + + return await ownerAccount.sendTransaction(tx); + } + + getRandomNonce(): BigInt { + const now = BigInt(Date.now()); + const randomPart = BigInt(Math.floor(Math.random() * 1e18)); + return now * randomPart; + } + + // Function to register your item type and tags on campaign verification service + async registerItemTypeAndTags(data:any) { + try { + + + const response = await axios.post(`${PEAQ_SERVICE_URL}/v1/data/store`, data, { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'APIKEY': API_KEY, + 'P-APIKEY': PROJECT_API_KEY + } + }) + .then((response:any) => { + return response.data; + }) + .catch((err:any) => { + console.error(err) + throw err; + }); + + // Note: You may need to adjust the response handling based on the service's response structure + return response.data; + + } catch (error) { + console.error("Error registering itemType and tags on campaign verification service", error); + throw error; + } + }; + +} + +// Main function to submit get real storage tx +const submitGetRealStorageTx = async () => { + const campaignClass = new PeaqGetRealCampaignClass(); + + try { + await campaignClass.submitGetRealStorageTx(); + } catch (error) { + console.error(" storage submission failed: Error:", error); + } +}; +submitGetRealStorageTx().catch(console.error); + + diff --git a/examples/js/usingWeb3/index.ts b/examples/js/usingWeb3/index.ts index 8c4db62..87641dd 100644 --- a/examples/js/usingWeb3/index.ts +++ b/examples/js/usingWeb3/index.ts @@ -7,8 +7,8 @@ import { Keyring } from '@polkadot/keyring'; import {abi} from '../MachineStationFactoryABI.json'; import { AbiCoder, ethers } from 'ethers'; -let golabalMachineAddress = "0x8D5fd26b338d9f40b48eD21bBd517E0944a12D48"; // to be replaced after submitDeployMachineSmartAccountTx is triggered -let golabalMachineAddress2 = "0x17420815062B03917bd89430a7eea8bFC84AC1B8"; // to be replaced after submitDeployMachineSmartAccountTx is triggered +let globalMachineAddress = "0x8D5fd26b338d9f40b48eD21bBd517E0944a12D48"; // to be replaced after submitDeployMachineSmartAccountTx is triggered +let globalMachineAddress2 = "0x17420815062B03917bd89430a7eea8bFC84AC1B8"; // to be replaced after submitDeployMachineSmartAccountTx is triggered // Web3 setup // for agung: https://erpc-async.agung.peaq.network @@ -45,11 +45,11 @@ class MachineStationFactoryExample { const deploySignature = await this.ownerSignTypedDataDeployMachineSmartAccount(machineOwner, nonce) // call the deploy smart account tx and update the global machine address var - golabalMachineAddress = await this.deployMachineSmartAccount(machineOwner, nonce, deploySignature); + globalMachineAddress = await this.deployMachineSmartAccount(machineOwner, nonce, deploySignature); } async submitMachineTransferBalanceTx() { - let machineAddress = golabalMachineAddress; + let machineAddress = globalMachineAddress; const recipientAddress = machineOwnerAccount.address; const nonce = this.getRandomNonce(); @@ -95,7 +95,7 @@ class MachineStationFactoryExample { async submitMachineStorageTx() { try { const machineOwner = machineOwnerAccount.address; - let machineAddress = golabalMachineAddress; + let machineAddress = globalMachineAddress; const nonce = this.getRandomNonce(); const target = '0x0000000000000000000000000000000000000801'; @@ -138,7 +138,7 @@ class MachineStationFactoryExample { const machineNonces: BigInt[] = []; const machineOwnerSignatures: string[] = []; - const machineAddresses: string[] = [golabalMachineAddress, golabalMachineAddress2]; + const machineAddresses: string[] = [globalMachineAddress, globalMachineAddress2]; const targets = ['0x0000000000000000000000000000000000000801','0x0000000000000000000000000000000000000801']; const calldata: string[] = []; @@ -178,7 +178,7 @@ class MachineStationFactoryExample { try { const nonce = this.getRandomNonce(); // Example nonce const target = "0x0000000000000000000000000000000000000800"; // target contract address - DID contract address - const machineAddress = golabalMachineAddress; + const machineAddress = globalMachineAddress; const abiCoder = new AbiCoder(); const addAttributeFunctionSignature =