From da2ddf74c61934709ee50f032c223d2a81ea3bfc Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 31 May 2024 13:25:39 +0900 Subject: [PATCH 01/12] [vda-common] Support `vda-xp-reward` - Added contract address and abi of `vda-xp-reward` --- packages/vda-common/CHANGELOG.md | 4 + packages/vda-common/src/abi/VDAXPReward.json | 574 +++++++++++++++++++ packages/vda-common/src/contract.ts | 11 +- packages/vda-common/src/rpc.ts | 6 +- 4 files changed, 591 insertions(+), 4 deletions(-) create mode 100644 packages/vda-common/src/abi/VDAXPReward.json diff --git a/packages/vda-common/CHANGELOG.md b/packages/vda-common/CHANGELOG.md index 72bbeb09..03f5cf5d 100644 --- a/packages/vda-common/CHANGELOG.md +++ b/packages/vda-common/CHANGELOG.md @@ -1,3 +1,7 @@ +2024-05-31 (v) +------------------- +- Support `VDAXPReward` contract + 2024-03-22 (v3.0.2) ------------------- diff --git a/packages/vda-common/src/abi/VDAXPReward.json b/packages/vda-common/src/abi/VDAXPReward.json new file mode 100644 index 00000000..854bc2b8 --- /dev/null +++ b/packages/vda-common/src/abi/VDAXPReward.json @@ -0,0 +1,574 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "VDAXPReward", + "sourceName": "contracts/VDAXPReward.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "DuplicatedRequest", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "string", + "name": "uniqueId", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + } + ], + "name": "DuplicatedUniqueId", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyClaimData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentAmount", + "type": "uint256" + } + ], + "name": "InsufficientTokenAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidConversionRate", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "InvalidProof", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "uint16", + "name": "year", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "month", + "type": "uint8" + } + ], + "name": "InvalidProofTime", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + } + ], + "name": "InvalidXP", + "type": "error" + }, + { + "inputs": [], + "name": "NoSigners", + "type": "error" + }, + { + "inputs": [], + "name": "RegisteredSigner", + "type": "error" + }, + { + "inputs": [], + "name": "UnregisteredSigner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "signerAddress", + "type": "address" + } + ], + "name": "AddTrustedSigner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "conversionRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "name": "ClaimedXPReward", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "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": "address", + "name": "signerAddress", + "type": "address" + } + ], + "name": "RemoveTrustedSigner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "orgRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newRate", + "type": "uint256" + } + ], + "name": "UpdateConversionRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "orgVal", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newVal", + "type": "uint256" + } + ], + "name": "UpdateRateDenominator", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "__VDAXPReward_init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + } + ], + "name": "addTrustedSigner", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "components": [ + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "string", + "name": "uniqueId", + "type": "string" + }, + { + "internalType": "uint16", + "name": "issueYear", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "issueMonth", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct IVDAXPReward.ClaimInfo[]", + "name": "claims", + "type": "tuple[]" + }, + { + "internalType": "bytes", + "name": "requestSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "requestProof", + "type": "bytes" + } + ], + "name": "claimXPReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getConversionRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRateDenominator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + } + ], + "name": "isTrustedSigner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "did", + "type": "address" + } + ], + "name": "nonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + } + ], + "name": "removeTrustedSigner", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newRate", + "type": "uint256" + } + ], + "name": "setConversionRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "name": "setRateDenominator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} diff --git a/packages/vda-common/src/contract.ts b/packages/vda-common/src/contract.ts index b26fb30e..811c718c 100644 --- a/packages/vda-common/src/contract.ts +++ b/packages/vda-common/src/contract.ts @@ -7,7 +7,8 @@ export type CONTRACT_NAMES = "VeridaDIDLinkage" | "VeridaToken" | "VDARewardContract" | - "StorageNodeRegistry" + "StorageNodeRegistry" | + "VDAXPReward" ; @@ -66,6 +67,13 @@ export const CONTRACT_ADDRESS : Record = { "VeridaToken": require('./abi/VeridaToken.json'), "VDARewardContract": require('./abi/VDARewardContract.json'), "StorageNodeRegistry": require('./abi/StorageNodeRegistry.json'), + "VDAXPReward": require('./abi/VDAXPReward.json'), } export function getContractInfoForNetwork(name: CONTRACT_NAMES, chainNameOrId: string) : CONTRACT_INFO { diff --git a/packages/vda-common/src/rpc.ts b/packages/vda-common/src/rpc.ts index b4d0817a..2cd486ee 100644 --- a/packages/vda-common/src/rpc.ts +++ b/packages/vda-common/src/rpc.ts @@ -4,9 +4,9 @@ export const RPC_URLS: Record = { // https://rpc.ankr.com/polygon is the same as https://polygon-rpc.com as also provided by Ankr mainnet: "https://rpc.ankr.com/polygon", "0x89": "https://rpc.ankr.com/polygon", - testnet: "https://rpc.ankr.com/polygon_mumbai", - "0x13881": "https://rpc.ankr.com/polygon_mumbai", - devnet: "https://rpc.ankr.com/polygon_mumbai", + testnet: "https://rpc-amoy.polygon.technology", + "0x13881": "https://rpc-amoy.polygon.technology", + devnet: "https://rpc-amoy.polygon.technology", }; export function getDefaultRpcUrl(chainNameOrId: any) { From 73b67d1c8f2755361f4e13a16c537470e6ca733d Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 31 May 2024 13:42:33 +0900 Subject: [PATCH 02/12] [vda-xp-reward-client] Initial version --- packages/vda-xp-reward-client/.env.example | 7 + packages/vda-xp-reward-client/.gitignore | 11 + packages/vda-xp-reward-client/CHANGELOG.md | 4 + packages/vda-xp-reward-client/README.md | 99 ++++++++ packages/vda-xp-reward-client/package.json | 43 ++++ .../src/blockchain/ownerApi.ts | 95 ++++++++ .../src/blockchain/userApi.ts | 217 ++++++++++++++++++ packages/vda-xp-reward-client/src/index.ts | 2 + packages/vda-xp-reward-client/test/const.ts | 13 ++ packages/vda-xp-reward-client/test/helpers.ts | 48 ++++ .../vda-xp-reward-client/test/owner.test.ts | 95 ++++++++ .../test/user_read.test.ts | 45 ++++ .../test/user_write.test.ts | 139 +++++++++++ packages/vda-xp-reward-client/tsconfig.json | 22 ++ 14 files changed, 840 insertions(+) create mode 100644 packages/vda-xp-reward-client/.env.example create mode 100644 packages/vda-xp-reward-client/.gitignore create mode 100644 packages/vda-xp-reward-client/CHANGELOG.md create mode 100644 packages/vda-xp-reward-client/README.md create mode 100644 packages/vda-xp-reward-client/package.json create mode 100644 packages/vda-xp-reward-client/src/blockchain/ownerApi.ts create mode 100644 packages/vda-xp-reward-client/src/blockchain/userApi.ts create mode 100644 packages/vda-xp-reward-client/src/index.ts create mode 100644 packages/vda-xp-reward-client/test/const.ts create mode 100644 packages/vda-xp-reward-client/test/helpers.ts create mode 100644 packages/vda-xp-reward-client/test/owner.test.ts create mode 100644 packages/vda-xp-reward-client/test/user_read.test.ts create mode 100644 packages/vda-xp-reward-client/test/user_write.test.ts create mode 100644 packages/vda-xp-reward-client/tsconfig.json diff --git a/packages/vda-xp-reward-client/.env.example b/packages/vda-xp-reward-client/.env.example new file mode 100644 index 00000000..c4e41edb --- /dev/null +++ b/packages/vda-xp-reward-client/.env.example @@ -0,0 +1,7 @@ +## These are used for test only. +PRIVATE_KEY = "" +RPC_URL="" + +## If set `true`, the test code mint necessary tokens to the `RewardContract` +## If not set, the owenr of the `RewardContract` should mint or send enough tokens to the contract. +NEED_TOKEN_MINT="true"; \ No newline at end of file diff --git a/packages/vda-xp-reward-client/.gitignore b/packages/vda-xp-reward-client/.gitignore new file mode 100644 index 00000000..f6ca2e78 --- /dev/null +++ b/packages/vda-xp-reward-client/.gitignore @@ -0,0 +1,11 @@ +#Ignore all files starting with "." +.* +!/.gitignore + +# node_modules in all sub directories +**node_modules + +# build +dist/ +lib/ +build/ \ No newline at end of file diff --git a/packages/vda-xp-reward-client/CHANGELOG.md b/packages/vda-xp-reward-client/CHANGELOG.md new file mode 100644 index 00000000..013d6295 --- /dev/null +++ b/packages/vda-xp-reward-client/CHANGELOG.md @@ -0,0 +1,4 @@ +2024-05-31 (v0.1.0) +------------------- + +- Initial version \ No newline at end of file diff --git a/packages/vda-xp-reward-client/README.md b/packages/vda-xp-reward-client/README.md new file mode 100644 index 00000000..01bc82d5 --- /dev/null +++ b/packages/vda-xp-reward-client/README.md @@ -0,0 +1,99 @@ + +# VDA XP Reward Client + +A client library for claiming XP-Reward. + +Users can claim the Verida token as reward for their XP. XP to reward conversion rate is set by the Verida team. + +## Verida package dependency +This packages has dependency for following `verida-js` packages. +- `@verida/types` +- `@verida/vda-common` + +Also, has a dependency for following `verida-js` packages for test. +- `@verida/vda-common-test` +- `@verida/encryption-utils` + +## Installation + +``` +yarn add @verida/vda-xp-reward-client +``` + +## Usage + +This library can be in `read only` mode where it just calls the `view` functions or in `read and write` mode where it can also call the `claimXPReward()`. + +### Read Only + +Setup the library in `read only` mode for reading usernames and DIDs: + +```ts +import { VeridaXPRewardClient } from '@verida/vda-xp-reward-client' +import { EnvironmentType } from '@verida/types' + +// Create name Client +const xpRewardClient = new VeridaXPRewardClient({ + network: EnvironmentType.TESTNET +}) +``` + +#### Get conversion rate + +```ts +const rate = await xpRewardClient.getConversionRate(); +``` + +### Read and Write + +In order to write to the blockchain, you will require a Polygon private key with `MATIC` tokens. + +Setup the library in `read and write` mode to support the above get methods *and* claim reward: + +```ts +import { VeridaXPRewardClient } from '@verida/vda-name-client' +import { EnvironmentType, Web3CallType } from '@verida/types' + +// DID address that controls the proof of eligibility +const DID_ADDRESS = '0x...' +// DID private key that controls the proof of eligibility +const DID_PRIVATE_KEY = '0x...' +// Polygon private key that will fund blockchain transactions +const POLYGON_PRIVATE_KEY = '0x...' +// How to make blockchain requests. This should be 'web3' unless using Verida's meta transaction server. +const CALL_TYPE = 'web3' +// (Optional) Polygon RPC URL (Mumbai testnet) +const RPC_URL = 'https://rpc-mumbai.maticvigil.com' + +// Create name Client +const xpRewardClient = new VeridaXPRewardClient({ + network: EnvironmentType.TESTNET + callType: CALL_TYPE, + did: `did:vda:testnet:${DID_ADDRESS}`, + signKey: DID_PRIVATE_KEY, + web3Options: { + rpcUrl: RPC_URL, + privateKey: POLYGON_PRIVATE_KEY + } +}) +``` + +### Claim + +```ts +const claimData = +[ + { + typeId: `type-1`, + uniqueId: ``, // Optional + issueYear: 2024, + issueMonth: 4, + xp: 100, + signature: `${}` + }, + {...}, + {...} +] +const recipient = '0x...'; //Recipient wallet addresss +await xpRewardClient.claimXPReward(recipient, claimData); +``` \ No newline at end of file diff --git a/packages/vda-xp-reward-client/package.json b/packages/vda-xp-reward-client/package.json new file mode 100644 index 00000000..1402a656 --- /dev/null +++ b/packages/vda-xp-reward-client/package.json @@ -0,0 +1,43 @@ +{ + "name": "@verida/vda-xp-reward-client", + "version": "0.1.0", + "description": "Client to manage xp-reward", + "main": "build/index.js", + "types": "build/index.d.ts", + "files": [ + "build/" + ], + "directories": { + "src": "src", + "test": "tests" + }, + "license": "Apache-2.0", + "keywords": [], + "scripts": { + "tests": "ts-mocha './test/**/*.tests.ts'", + "test": "ts-mocha --timeout 200000", + "lint": "gts lint", + "clean": "gts clean", + "compile": "rm -rf build && tsc", + "build": "rm -rf build && tsc", + "fix": "gts fix", + "prepare": "npm run compile" + }, + "dependencies": { + "@ethersproject/providers": "^5.7.2", + "@verida/encryption-utils": "*", + "@verida/helpers": "^3.0.3", + "@verida/types": "^3.0.2", + "@verida/web3": "^3.0.3" + }, + "devDependencies": { + "@verida/vda-common-test": "^3.0.2", + "dotenv": "^16.0.3", + "ethers": "5.7.0", + "gts": "^3.1.0", + "mocha": "^10.1.0", + "ts-mocha": "^10.0.0", + "ts-node": "^10.7.0", + "typescript": "^4.6.4" + } +} diff --git a/packages/vda-xp-reward-client/src/blockchain/ownerApi.ts b/packages/vda-xp-reward-client/src/blockchain/ownerApi.ts new file mode 100644 index 00000000..57bfb726 --- /dev/null +++ b/packages/vda-xp-reward-client/src/blockchain/ownerApi.ts @@ -0,0 +1,95 @@ +import { VdaClientConfig } from "@verida/types"; +import { VeridaXPRewardClient } from "./userApi"; +import { VdaVerificationUtils } from "@verida/vda-common"; + +export class VeridaXPRewardOwnerApi extends VeridaXPRewardClient { + + public constructor(config: VdaClientConfig) { + super(config); + } + + /** + * Add a trusted signer + * @param didAddress DID address to be added + */ + public async addTrustedSigner( + didAddress: string + ) { + await VdaVerificationUtils.addTrustedSigner.call(this, didAddress); + } + + /** + * Remove a trusted signer + * @param didAddress DID address to be added + */ + public async removeTrustedSigner( + didAddress: string + ) { + await VdaVerificationUtils.removeTrustedSigner.call(this, didAddress); + } + + /** + * Check whether given address is a trusted signer + * @param didAddress DID address to be checked + * @returns true if trusted signer, otherwise false + */ + public async isTrustedSigner(didAddress: string) { + return await VdaVerificationUtils.isTrustedSigner.call(this, didAddress); + } + + /** + * @dev Set the denominator for rate values + * @param denominator - new value to be set + */ + public async setRateDenominator(denominator: bigint) { + if (this.readOnly || !this.vdaWeb3Client) { + throw new Error(`Unable to submit to blockchain. In read only mode.`); + } + + const response = await this.vdaWeb3Client!.setRateDenominator(denominator); + + if (response.success !== true) { + throw new Error(`Failed to set rate denominator : ${response.reason}`); + } + } + + /** + * @dev Update the XP-VDA conversion rate + * @param newRate - New value to be updated. This is the float value. + * Should be set in the contract after multiplied with the `rateDenominator` + */ + public async setConversionRate(newRate: number) { + if (this.readOnly || !this.vdaWeb3Client) { + throw new Error(`Unable to submit to blockchain. In read only mode.`); + } + + const denominator = await this.getRateDenominator(); + const rate: bigint = BigInt(Number(denominator) * newRate); + + const response = await this.vdaWeb3Client!.setConversionRate(rate); + + if (response.success !== true) { + throw new Error(`Failed to set conversion rate : ${response.reason}`); + } + } + + /** + * @dev Withdraw tokens + * @param to - Recipient wallet address + * @param amount - Token amount to be withdrawn + */ + public async withdraw( + to: string, + amount: bigint, + ) { + if (this.readOnly || !this.vdaWeb3Client) { + throw new Error(`Unable to submit to blockchain. In read only mode.`); + } + + const response = await this.vdaWeb3Client!.withdraw(to, amount); + + if (response.success !== true) { + throw new Error(`Failed to withdraw : ${response.reason}`); + } + } +} \ No newline at end of file diff --git a/packages/vda-xp-reward-client/src/blockchain/userApi.ts b/packages/vda-xp-reward-client/src/blockchain/userApi.ts new file mode 100644 index 00000000..c941a8e2 --- /dev/null +++ b/packages/vda-xp-reward-client/src/blockchain/userApi.ts @@ -0,0 +1,217 @@ +import { + getVeridaContract, + VeridaContract +} from "@verida/web3" +import { Web3SelfTransactionConfig, VdaClientConfig } from '@verida/types' +import { ethers, Contract, BytesLike } from "ethers"; +import { getContractInfoForNetwork, RPC_URLS, getVeridaSign, getVeridaSignWithNonce } from "@verida/vda-common"; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { explodeDID } from '@verida/helpers' +import EncryptionUtils from "@verida/encryption-utils"; + +/** + * This is the claim information + * + * @interface ClaimXPInfo + * @member {string} typeId - the Proof type. Ex : `gamer31` + * @member {string} uniqueId - Unique Id of the proof. + * @member {number} issueYear - Issued year of the proof + * @member {number} issueMonth - Issued month of the proof + * @member {bigint} xp - Value of xp + * @member {BytesLike} signature - Proof signed by a trusted signer + */ +export interface ClaimXPInfo { + typeId: string, + uniqueId?: string, + issueYear: number, + issueMonth: number, + xp: bigint, + signature: BytesLike, +} + +/** + * This is a client class that interacts with the `VDAXPReward` contract of Verida + */ +export class VeridaXPRewardClient { + + protected config: VdaClientConfig + protected network: string + protected didAddress?: string + + protected vdaWeb3Client? : VeridaContract + + protected readOnly: boolean + protected contract?: ethers.Contract + + public constructor(config: VdaClientConfig) { + if (!config.callType) { + config.callType = 'web3' + } + + this.config = config + this.readOnly = true + if (!config.web3Options) { + config.web3Options = {} + } + + this.network = config.network + + if (config.callType == 'web3' && !(config.web3Options).rpcUrl) { + ( config.web3Options).rpcUrl = RPC_URLS[this.network] + } + + const contractInfo = getContractInfoForNetwork("VDAXPReward", this.network) + + if (config.did) { + this.readOnly = false + const { address } = explodeDID(config.did) + this.didAddress = address.toLowerCase() + + this.vdaWeb3Client = getVeridaContract( + config.callType, + {...contractInfo, + ...config.web3Options}) + } else { + let rpcUrl = (config.web3Options).rpcUrl + if (!rpcUrl) { + rpcUrl = RPC_URLS[this.network] + } + + const provider = new JsonRpcProvider(rpcUrl) + + this.contract = new Contract(contractInfo.address, contractInfo.abi.abi, provider) + } + } + + /** + * Get a nonce from VDAXPReward contract + * @returns nonce of DID + */ + public async nonceFN() { + if (!this.vdaWeb3Client) { + throw new Error(`Config must specify 'did' or 'signKey'`) + } + + const response = await this.vdaWeb3Client.nonce(this.didAddress); + if (response.data === undefined) { + throw new Error('Error in getting nonce'); + } + return response.data; + } + + /** + * Return the Reward token address-Verida token associated with `VDAXPRewardContract` + * @returns The address of token contract + */ + public async getTokenAddress() { + let response + + try { + if (this.vdaWeb3Client) { + response = await this.vdaWeb3Client.getTokenAddress() + response = response.data + } else { + response = await this.contract!.callStatic.getTokenAddress() + } + + return response; + } catch (err:any ) { + throw new Error(`Failed to get token address (${err.message})`) + } + } + + /** + * Return the denominator of conversion rate for `Verida XP` to `Verida token` + * @returns The denominator for conversion rate + */ + public async getRateDenominator() : Promise { + let response + + try { + if (this.vdaWeb3Client) { + response = await this.vdaWeb3Client.getRateDenominator() + response = response.data + } else { + response = await this.contract!.callStatic.getRateDenominator() + } + + return BigInt(response); + } catch (err:any ) { + throw new Error(`Failed to get rate denominator (${err.message})`) + } + } + + /** + * Return the conversion rate for `Verida XP` to `Verida token` + * @returns The conversion rate divided by the rate denominator + */ + public async getConversionRate() : Promise { + let response + let denominator + + denominator = Number(await this.getRateDenominator()); + + try { + if (this.vdaWeb3Client) { + response = await this.vdaWeb3Client.getConversionRate() + response = response.data + + } else { + response = await this.contract!.callStatic.getConversionRate() + } + + response = Number(response / denominator); + + return response; + } catch (err:any ) { + throw new Error(`Failed to get covnersion rate (${err.message})`) + } + } + + /** + * Claim reward to a specified address + * @param to - Reward token recipient wallet address + * @param claims - Array of claim information + */ + public async claimXPReward( + to: string, + claims: ClaimXPInfo[] + ) { + if (this.readOnly) { + throw new Error(`Unable to submit to blockchain. No 'signKey' provided in config.`) + } + + // Create `requestSignature` and `requestProof` + const nonce = await this.nonceFN(); + let requestMsg = ethers.utils.solidityPack( + ['address', 'address'], + [this.didAddress, to] + ); + for (let i = 0; i < claims.length; i++) { + requestMsg = ethers.utils.solidityPack( + ['bytes', 'bytes'], + [requestMsg, claims[i].signature] + ); + } + requestMsg = ethers.utils.solidityPack( + ['bytes', 'uint'], + [requestMsg, nonce] + ); + + const requestSignature = getVeridaSign(requestMsg, this.config.signKey!); + const proofMsg = `${this.didAddress}${this.didAddress}`.toLowerCase(); + const requestProof = getVeridaSign(proofMsg, this.config.signKey!); + + const response = await this.vdaWeb3Client!.claimXPReward( + this.didAddress, + to, + claims, + requestSignature, + requestProof + ) + + if (response.success !== true) { + throw new Error(`Failed to claim xp: (${response.reason})`) + } + } +} \ No newline at end of file diff --git a/packages/vda-xp-reward-client/src/index.ts b/packages/vda-xp-reward-client/src/index.ts new file mode 100644 index 00000000..0c6eaf6f --- /dev/null +++ b/packages/vda-xp-reward-client/src/index.ts @@ -0,0 +1,2 @@ +export * from './blockchain/userApi' +export * from './blockchain/ownerApi' \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/const.ts b/packages/vda-xp-reward-client/test/const.ts new file mode 100644 index 00000000..94243438 --- /dev/null +++ b/packages/vda-xp-reward-client/test/const.ts @@ -0,0 +1,13 @@ +import { BytesLike } from "ethers" + +/** + * @notice This interface is part of `ClaimInfo` struct of `VDAXPReward` contract + */ +export interface ClaimInfo { + typeId: string + uniqueId: string + issueYear: number + issueMonth: number + xp: bigint + signature: BytesLike +} \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/helpers.ts b/packages/vda-xp-reward-client/test/helpers.ts new file mode 100644 index 00000000..4a290880 --- /dev/null +++ b/packages/vda-xp-reward-client/test/helpers.ts @@ -0,0 +1,48 @@ +import { DID_LIST, TRUSTED_SIGNER } from "@verida/vda-common-test"; +import { VeridaXPRewardOwnerApi } from "../src/blockchain/ownerApi"; +import { EnvironmentType } from "@verida/types"; +import { Wallet } from 'ethers'; +import EncryptionUtils from `@verida/encryption-utils`; + +/** + * Add initial data that is used in testing + * @param configuration Configuration to create ownerApi + * @param ownerApi Optional. If not provided, create with above `configuration` param + */ +export async function addInitialData( + configuration: Record, + ownerApi: VeridaXPRewardOwnerApi | undefined = undefined +) { + if (ownerApi === undefined) { + // ownerDID defined to ownerApi as write mode + const owenrDID = DID_LIST[0]; + + ownerApi = new VeridaXPRewardOwnerApi({ + did: owenrDID.address, + network: EnvironmentType.TESTNET, + ...configuration + }); + } + + console.log("Check and add trusted signer..."); + if ((await ownerApi.isTrustedSigner(TRUSTED_SIGNER.address)) !== true) { + await ownerApi.addTrustedSigner(TRUSTED_SIGNER.address); + } + + console.log("Set conversion rate..."); + if ((await ownerApi.getConversionRate()) === 0) { + await ownerApi.setConversionRate(0.05); + } +} + +/** + * Get a proof that will be used in `claim()` and `claimToStorage()` functions + * @param userAddress User wallet address that call the claiming functions + * @param trustedSigner A trusted signer that is added to the `VDARewardContract` + * @returns Bytearray + */ +export function generateProof(userAddress: string, trustedSigner: Wallet) { + const proofMsg = `${trustedSigner.address}${userAddress}`.toLowerCase(); + const privateKeyArray = new Uint8Array(Buffer.from(trustedSigner.privateKey.slice(2), 'hex')); + return EncryptionUtils.signData(proofMsg, privateKeyArray); +} \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/owner.test.ts b/packages/vda-xp-reward-client/test/owner.test.ts new file mode 100644 index 00000000..554a49a4 --- /dev/null +++ b/packages/vda-xp-reward-client/test/owner.test.ts @@ -0,0 +1,95 @@ +require('dotenv').config(); +import { DID_LIST, getBlockchainAPIConfiguration, ERC20Manager } from "@verida/vda-common-test" +import { VeridaXPRewardOwnerApi } from '../src/blockchain/ownerApi'; +import { EnvironmentType } from "@verida/types"; +import { addInitialData } from "./helpers"; +import { Wallet } from 'ethers'; + +const assert = require('assert') + +const privateKey = process.env.PRIVATE_KEY +if (!privateKey) { + throw new Error('No PRIVATE_KEY in the env file'); +} +const configuration = getBlockchainAPIConfiguration(privateKey); + + +const createOwnerAPI = () => { + const ownerDID = DID_LIST[0]; + + return new VeridaXPRewardOwnerApi({ + did: ownerDID.address, + network: EnvironmentType.TESTNET, + ...configuration + }) +} + +describe("Verida XPRewardOwnerApi Test", () => { + let ownerApi; + + before(async () => { + ownerApi = createOwnerAPI(); + await addInitialData(configuration, ownerApi); + }) + + describe("Trusted signer test", () => { + const newSigner = Wallet.createRandom(); + + it("Add a trusted signer",async () => { + let response = await ownerApi.isTrustedSigner(newSigner.address); + assert.equal(response, false); + + await ownerApi.addTrustedSigner(newSigner.address); + + response = await ownerApi.isTrustedSigner(newSigner.address); + assert.equal(response, true); + }) + + it("Remove a trusted signer",async () => { + await ownerApi.removeTrustedSigner(newSigner.address); + + const response = await ownerApi.isTrustedSigner(newSigner.address); + assert.equal(response, false); + }) + }) + + describe("Set rate denominator", () => { + let orgRateDenominator; + before(async () => { + orgRateDenominator = await ownerApi.getRateDenominator(); + }) + + it("Set rate denominator", async () => { + const newDenominator = orgRateDenominator * 100000000n; + await ownerApi.setRateDenominator(newDenominator); + + const curDenominator = await ownerApi.getRateDenominator(); + assert.equal(newDenominator, curDenominator); + + // Restore denominator + await ownerApi.setRateDenominator(orgRateDenominator); + }) + }) + + describe("Set conversion rate", () => { + let orgConversionRate; + before(async () => { + orgConversionRate = await ownerApi.getConversionRate(); + }) + + it("Set conversion rate", async () => { + const newRate = orgConversionRate * 10; + await ownerApi.setConversionRate(newRate); + + const curRate = await ownerApi.getConversionRate(); + assert.equal(newRate, curRate); + + // Restore rate + await ownerApi.setConversionRate(orgConversionRate); + }) + }) + + describe("Withdraw", () => { + + }) +}) \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/user_read.test.ts b/packages/vda-xp-reward-client/test/user_read.test.ts new file mode 100644 index 00000000..01c0fb22 --- /dev/null +++ b/packages/vda-xp-reward-client/test/user_read.test.ts @@ -0,0 +1,45 @@ +require('dotenv').config(); +import { DID_LIST, getBlockchainAPIConfiguration, ERC20Manager } from "@verida/vda-common-test" +import { EnvironmentType } from "@verida/types"; +import { addInitialData } from "./helpers"; +import { Wallet } from 'ethers'; +import { VeridaXPRewardClient } from "../src/blockchain/userApi"; + +const assert = require('assert') + +const privateKey = process.env.PRIVATE_KEY +if (!privateKey) { + throw new Error('No PRIVATE_KEY in the env file'); +} +const configuration = getBlockchainAPIConfiguration(privateKey); + + +const createXPRewardClientAPI = () => { + return new VeridaXPRewardClient({ + network: EnvironmentType.TESTNET, + }) +} + +describe("Verida RewardOwnerApi Test in Read mode", () => { + let xpRewardClientApi; + + before(async () => { + await addInitialData(configuration); + xpRewardClientApi = createXPRewardClientAPI(); + }) + + it("Get token address",async () => { + const response = await xpRewardClientApi.getTokenAddress(); + assert.ok(response && typeof(response) === `string`, 'Get the result'); + }) + + it("Get rate denominator",async () => { + const response = await xpRewardClientApi.getRateDenominator(); + assert.ok(response && response > 0, 'Get the result'); + }) + + it("Get conversion rate",async () => { + const response = await xpRewardClientApi.getConversionRate(); + assert.ok(response && response > 0, 'Get the result'); + }) +}) \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/user_write.test.ts b/packages/vda-xp-reward-client/test/user_write.test.ts new file mode 100644 index 00000000..3daf4727 --- /dev/null +++ b/packages/vda-xp-reward-client/test/user_write.test.ts @@ -0,0 +1,139 @@ +require('dotenv').config(); +import { getBlockchainAPIConfiguration, ERC20Manager, TRUSTED_SIGNER } from "@verida/vda-common-test" +import { RECIPIENT_WALLET } from "@verida/vda-common-test" +import { EnvironmentType } from "@verida/types"; +import { addInitialData, generateProof } from "./helpers"; +import { Wallet, BigNumber, ethers } from 'ethers'; +import { ClaimInfo } from "./const"; +import { VeridaXPRewardClient } from "../src/blockchain/userApi"; + +import { CONTRACT_ADDRESS } from "@verida/vda-common"; +import EncryptionUtils from "@verida/encryption-utils"; + +const assert = require('assert') + +const privateKey = process.env.PRIVATE_KEY +if (!privateKey) { + throw new Error('No PRIVATE_KEY in the env file'); +} +const configuration = getBlockchainAPIConfiguration(privateKey); + +// Create `VeridaXPRewardClient` in write mode +const createXPRewardClientAPI = (did:Wallet, configuration: any) => { + return new VeridaXPRewardClient({ + did: `did:vda:testnet:${did.address}`, + signKey: did.privateKey, + network: EnvironmentType.TESTNET, + ...configuration + }) +} + +describe("Verida XPRewardOwnerApi Test in read/write mode", () => { + let xpRewardClientApi: VeridaXPRewardClient; + let tokenAPI: ERC20Manager; + + const userDID = Wallet.createRandom(); + const xpAmount = 100; + const xpAmountWithUniqueId = 200; + + before(async () => { + await addInitialData(configuration); + xpRewardClientApi = createXPRewardClientAPI(new Wallet(userDID.privateKey), configuration); + + const TOKEN_ADDRESS = await xpRewardClientApi.getTokenAddress(); + const rewardXPContractAddress = CONTRACT_ADDRESS["VDAXPReward"].testnet!; + + // Create tokenAPI for claiming test + tokenAPI = new ERC20Manager( + TOKEN_ADDRESS, + process.env.RPC_URL, + privateKey + ) + + // Calculate necessary amount of token for reward + const totalXPAmount = xpAmount + xpAmountWithUniqueId; + const conversionRate = Number(await xpRewardClientApi.getConversionRate()); + const tokenAmount = BigInt(totalXPAmount * conversionRate); + + // Check out token amount of xp reward contract + if (await tokenAPI.balanceOf(rewardXPContractAddress) < tokenAmount) { + if (process.env.NEED_TOKEN_MINT === `true`) { + const mintAmount = tokenAmount; + tokenAPI.mint(rewardXPContractAddress, mintAmount); + } else { + throw new Error(`Not enough token in the 'VDAXPReward' contract at ${rewardXPContractAddress}`); + } + } + }) + + describe("Get function tests", () => { + it("Get token address",async () => { + const response = await xpRewardClientApi.getTokenAddress(); + assert.ok(response && typeof(response) === `string`, 'Get the result'); + }) + + it("Get rate denominator",async () => { + const response = await xpRewardClientApi.getRateDenominator(); + assert.ok(response && response > 0, 'Get the result'); + }) + + it("Get conversion rate",async () => { + const response = await xpRewardClientApi.getConversionRate(); + assert.ok(response && response > 0, 'Get the result'); + }) + }) + + describe("Claim XP Reward", () => { + const claimData: ClaimInfo = { + typeId: `type-${new Date().getTime()}`, + uniqueId: ``, + issueYear: 0, + issueMonth: 0, + xp: 50n, + signature: '' + } + + const getProof = (didWallet:Wallet, data: ClaimInfo, trustedSigner: Wallet) => { + const rawMsg = ethers.utils.solidityPack( + ['address', 'string', 'uint16', 'uint8', 'uint'], + [didWallet.address, `${data.typeId}${data.uniqueId}`, data.issueYear, data.issueMonth, data.xp] + ); + const privateKeyArray = new Uint8Array(Buffer.from(trustedSigner.privateKey.slice(2), 'hex')) + return EncryptionUtils.signData(rawMsg, privateKeyArray); + } + + before(async () => { + // Set issueYear and month + const issueTime = new Date(); + issueTime.setMonth(issueTime.getMonth() - 1); + claimData.issueYear = issueTime.getFullYear(); + claimData.issueMonth = issueTime.getMonth()+1; + }) + + it("Claim without `uniqueId`", async () => { + claimData.xp = BigInt(xpAmount); + claimData.uniqueId = ''; + claimData.signature = getProof(userDID, claimData, new Wallet(TRUSTED_SIGNER.privateKey)); + + const orgBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); + await xpRewardClientApi.claimXPReward(RECIPIENT_WALLET.address, [claimData]) + + const newBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); + + assert.ok(newBalance > orgBalance); + }) + + it("Claim with `uniqueId`", async () => { + claimData.typeId = `${claimData.typeId}-1` + claimData.uniqueId = 'uniqueid-1'; + claimData.xp = BigInt(xpAmount); + claimData.signature = getProof(userDID, claimData, new Wallet(TRUSTED_SIGNER.privateKey)); + + const orgBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); + await xpRewardClientApi.claimXPReward(RECIPIENT_WALLET.address, [claimData]) + + const newBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); + assert.ok(newBalance > orgBalance); + }) + }) +}) \ No newline at end of file diff --git a/packages/vda-xp-reward-client/tsconfig.json b/packages/vda-xp-reward-client/tsconfig.json new file mode 100644 index 00000000..6b8e551e --- /dev/null +++ b/packages/vda-xp-reward-client/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "rootDir": ".", + "outDir": "build", + "sourceMap": true, + "strict": true, + "declaration": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": [ + "src/**/*.ts", + ], + "exclude": ["node_modules", "test", "build"], + "lib": [ + "es5", + "es2015" + ] + } + \ No newline at end of file From 1cc00b9c5f184edb8aa9b60edb9cb7e14270dddd Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 31 May 2024 13:43:13 +0900 Subject: [PATCH 03/12] [vda-reward-client] Update the ReadMe - Fix typos --- packages/vda-reward-client/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vda-reward-client/README.md b/packages/vda-reward-client/README.md index 8e478fe6..db3c5f94 100644 --- a/packages/vda-reward-client/README.md +++ b/packages/vda-reward-client/README.md @@ -35,7 +35,7 @@ import { EnvironmentType } from '@verida/types' // Create name Client const rewardClient = new VeridaRewardClient({ - environment: EnvironmentType.TESTNET + network: EnvironmentType.TESTNET }) ``` @@ -56,9 +56,9 @@ Setup the library in `read and write` mode to support the above get methods *and import { VeridaRewardClient } from '@verida/vda-name-client' import { EnvironmentType, Web3CallType } from '@verida/types' -// DID address that controls the proof of eligibility to mint the SBT +// DID address that controls the proof const DID_ADDRESS = '0x...' -// DID private key that controls the proof of eligibility to mint the SBT +// DID private key that controls the proof const DID_PRIVATE_KEY = '0x...' // Polygon private key that will fund blockchain transactions const POLYGON_PRIVATE_KEY = '0x...' @@ -69,9 +69,9 @@ const RPC_URL = 'https://rpc-mumbai.maticvigil.com' // Create name Client const rewardClient = new VeridaRewardClient({ - environment: EnvironmentType.TESTNET + network: EnvironmentType.TESTNET callType: CALL_TYPE, - did: DID_ADDRESS, + did: `did:vda:testnet:${DID_ADDRESS}`, signKey: DID_PRIVATE_KEY, web3Options: { rpcUrl: RPC_URL, From b4b3d3655760214b43299fb64d5995225cd677d9 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 31 May 2024 13:25:39 +0900 Subject: [PATCH 04/12] [vda-common] Support `vda-xp-reward` - Added contract address and abi of `vda-xp-reward` --- packages/vda-common/CHANGELOG.md | 5 +- packages/vda-common/src/abi/VDAXPReward.json | 574 +++++++++++++++++++ 2 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 packages/vda-common/src/abi/VDAXPReward.json diff --git a/packages/vda-common/CHANGELOG.md b/packages/vda-common/CHANGELOG.md index a7991f58..36b3e6c2 100644 --- a/packages/vda-common/CHANGELOG.md +++ b/packages/vda-common/CHANGELOG.md @@ -3,6 +3,10 @@ - Support BlockchainAnchor and Verida Network refactor +2024-05-31 (v) +------------------- +- Support `VDAXPReward` contract + 2024-03-22 (v3.0.2) ------------------- @@ -10,7 +14,6 @@ - Update the abi & address of `StorageNodeRegistry` contract (locking feature added) - Replaced the testenet RPC_URL from `Mumbai` to `Amoy` - 2024-03-22 (v3.0.2) ------------------- diff --git a/packages/vda-common/src/abi/VDAXPReward.json b/packages/vda-common/src/abi/VDAXPReward.json new file mode 100644 index 00000000..854bc2b8 --- /dev/null +++ b/packages/vda-common/src/abi/VDAXPReward.json @@ -0,0 +1,574 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "VDAXPReward", + "sourceName": "contracts/VDAXPReward.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "DuplicatedRequest", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "string", + "name": "uniqueId", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + } + ], + "name": "DuplicatedUniqueId", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyClaimData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentAmount", + "type": "uint256" + } + ], + "name": "InsufficientTokenAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidConversionRate", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "InvalidProof", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "uint16", + "name": "year", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "month", + "type": "uint8" + } + ], + "name": "InvalidProofTime", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + } + ], + "name": "InvalidXP", + "type": "error" + }, + { + "inputs": [], + "name": "NoSigners", + "type": "error" + }, + { + "inputs": [], + "name": "RegisteredSigner", + "type": "error" + }, + { + "inputs": [], + "name": "UnregisteredSigner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "signerAddress", + "type": "address" + } + ], + "name": "AddTrustedSigner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "rewardAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "conversionRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "name": "ClaimedXPReward", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "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": "address", + "name": "signerAddress", + "type": "address" + } + ], + "name": "RemoveTrustedSigner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "orgRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newRate", + "type": "uint256" + } + ], + "name": "UpdateConversionRate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "orgVal", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newVal", + "type": "uint256" + } + ], + "name": "UpdateRateDenominator", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "contract IERC20Upgradeable", + "name": "token", + "type": "address" + } + ], + "name": "__VDAXPReward_init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + } + ], + "name": "addTrustedSigner", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "components": [ + { + "internalType": "string", + "name": "typeId", + "type": "string" + }, + { + "internalType": "string", + "name": "uniqueId", + "type": "string" + }, + { + "internalType": "uint16", + "name": "issueYear", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "issueMonth", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "xp", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct IVDAXPReward.ClaimInfo[]", + "name": "claims", + "type": "tuple[]" + }, + { + "internalType": "bytes", + "name": "requestSignature", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "requestProof", + "type": "bytes" + } + ], + "name": "claimXPReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getConversionRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRateDenominator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + } + ], + "name": "isTrustedSigner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "did", + "type": "address" + } + ], + "name": "nonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "didAddress", + "type": "address" + } + ], + "name": "removeTrustedSigner", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newRate", + "type": "uint256" + } + ], + "name": "setConversionRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "name": "setRateDenominator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} From 5e759a7eda0b54315066a4a6dc236c312a6639b6 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 31 May 2024 13:42:33 +0900 Subject: [PATCH 05/12] [vda-xp-reward-client] Initial version --- packages/vda-xp-reward-client/.env.example | 7 + packages/vda-xp-reward-client/.gitignore | 11 + packages/vda-xp-reward-client/CHANGELOG.md | 4 + packages/vda-xp-reward-client/README.md | 99 ++++++++ packages/vda-xp-reward-client/package.json | 43 ++++ .../src/blockchain/ownerApi.ts | 95 ++++++++ .../src/blockchain/userApi.ts | 217 ++++++++++++++++++ packages/vda-xp-reward-client/src/index.ts | 2 + packages/vda-xp-reward-client/test/const.ts | 13 ++ packages/vda-xp-reward-client/test/helpers.ts | 48 ++++ .../vda-xp-reward-client/test/owner.test.ts | 95 ++++++++ .../test/user_read.test.ts | 45 ++++ .../test/user_write.test.ts | 139 +++++++++++ packages/vda-xp-reward-client/tsconfig.json | 22 ++ 14 files changed, 840 insertions(+) create mode 100644 packages/vda-xp-reward-client/.env.example create mode 100644 packages/vda-xp-reward-client/.gitignore create mode 100644 packages/vda-xp-reward-client/CHANGELOG.md create mode 100644 packages/vda-xp-reward-client/README.md create mode 100644 packages/vda-xp-reward-client/package.json create mode 100644 packages/vda-xp-reward-client/src/blockchain/ownerApi.ts create mode 100644 packages/vda-xp-reward-client/src/blockchain/userApi.ts create mode 100644 packages/vda-xp-reward-client/src/index.ts create mode 100644 packages/vda-xp-reward-client/test/const.ts create mode 100644 packages/vda-xp-reward-client/test/helpers.ts create mode 100644 packages/vda-xp-reward-client/test/owner.test.ts create mode 100644 packages/vda-xp-reward-client/test/user_read.test.ts create mode 100644 packages/vda-xp-reward-client/test/user_write.test.ts create mode 100644 packages/vda-xp-reward-client/tsconfig.json diff --git a/packages/vda-xp-reward-client/.env.example b/packages/vda-xp-reward-client/.env.example new file mode 100644 index 00000000..c4e41edb --- /dev/null +++ b/packages/vda-xp-reward-client/.env.example @@ -0,0 +1,7 @@ +## These are used for test only. +PRIVATE_KEY = "" +RPC_URL="" + +## If set `true`, the test code mint necessary tokens to the `RewardContract` +## If not set, the owenr of the `RewardContract` should mint or send enough tokens to the contract. +NEED_TOKEN_MINT="true"; \ No newline at end of file diff --git a/packages/vda-xp-reward-client/.gitignore b/packages/vda-xp-reward-client/.gitignore new file mode 100644 index 00000000..f6ca2e78 --- /dev/null +++ b/packages/vda-xp-reward-client/.gitignore @@ -0,0 +1,11 @@ +#Ignore all files starting with "." +.* +!/.gitignore + +# node_modules in all sub directories +**node_modules + +# build +dist/ +lib/ +build/ \ No newline at end of file diff --git a/packages/vda-xp-reward-client/CHANGELOG.md b/packages/vda-xp-reward-client/CHANGELOG.md new file mode 100644 index 00000000..013d6295 --- /dev/null +++ b/packages/vda-xp-reward-client/CHANGELOG.md @@ -0,0 +1,4 @@ +2024-05-31 (v0.1.0) +------------------- + +- Initial version \ No newline at end of file diff --git a/packages/vda-xp-reward-client/README.md b/packages/vda-xp-reward-client/README.md new file mode 100644 index 00000000..01bc82d5 --- /dev/null +++ b/packages/vda-xp-reward-client/README.md @@ -0,0 +1,99 @@ + +# VDA XP Reward Client + +A client library for claiming XP-Reward. + +Users can claim the Verida token as reward for their XP. XP to reward conversion rate is set by the Verida team. + +## Verida package dependency +This packages has dependency for following `verida-js` packages. +- `@verida/types` +- `@verida/vda-common` + +Also, has a dependency for following `verida-js` packages for test. +- `@verida/vda-common-test` +- `@verida/encryption-utils` + +## Installation + +``` +yarn add @verida/vda-xp-reward-client +``` + +## Usage + +This library can be in `read only` mode where it just calls the `view` functions or in `read and write` mode where it can also call the `claimXPReward()`. + +### Read Only + +Setup the library in `read only` mode for reading usernames and DIDs: + +```ts +import { VeridaXPRewardClient } from '@verida/vda-xp-reward-client' +import { EnvironmentType } from '@verida/types' + +// Create name Client +const xpRewardClient = new VeridaXPRewardClient({ + network: EnvironmentType.TESTNET +}) +``` + +#### Get conversion rate + +```ts +const rate = await xpRewardClient.getConversionRate(); +``` + +### Read and Write + +In order to write to the blockchain, you will require a Polygon private key with `MATIC` tokens. + +Setup the library in `read and write` mode to support the above get methods *and* claim reward: + +```ts +import { VeridaXPRewardClient } from '@verida/vda-name-client' +import { EnvironmentType, Web3CallType } from '@verida/types' + +// DID address that controls the proof of eligibility +const DID_ADDRESS = '0x...' +// DID private key that controls the proof of eligibility +const DID_PRIVATE_KEY = '0x...' +// Polygon private key that will fund blockchain transactions +const POLYGON_PRIVATE_KEY = '0x...' +// How to make blockchain requests. This should be 'web3' unless using Verida's meta transaction server. +const CALL_TYPE = 'web3' +// (Optional) Polygon RPC URL (Mumbai testnet) +const RPC_URL = 'https://rpc-mumbai.maticvigil.com' + +// Create name Client +const xpRewardClient = new VeridaXPRewardClient({ + network: EnvironmentType.TESTNET + callType: CALL_TYPE, + did: `did:vda:testnet:${DID_ADDRESS}`, + signKey: DID_PRIVATE_KEY, + web3Options: { + rpcUrl: RPC_URL, + privateKey: POLYGON_PRIVATE_KEY + } +}) +``` + +### Claim + +```ts +const claimData = +[ + { + typeId: `type-1`, + uniqueId: ``, // Optional + issueYear: 2024, + issueMonth: 4, + xp: 100, + signature: `${}` + }, + {...}, + {...} +] +const recipient = '0x...'; //Recipient wallet addresss +await xpRewardClient.claimXPReward(recipient, claimData); +``` \ No newline at end of file diff --git a/packages/vda-xp-reward-client/package.json b/packages/vda-xp-reward-client/package.json new file mode 100644 index 00000000..1402a656 --- /dev/null +++ b/packages/vda-xp-reward-client/package.json @@ -0,0 +1,43 @@ +{ + "name": "@verida/vda-xp-reward-client", + "version": "0.1.0", + "description": "Client to manage xp-reward", + "main": "build/index.js", + "types": "build/index.d.ts", + "files": [ + "build/" + ], + "directories": { + "src": "src", + "test": "tests" + }, + "license": "Apache-2.0", + "keywords": [], + "scripts": { + "tests": "ts-mocha './test/**/*.tests.ts'", + "test": "ts-mocha --timeout 200000", + "lint": "gts lint", + "clean": "gts clean", + "compile": "rm -rf build && tsc", + "build": "rm -rf build && tsc", + "fix": "gts fix", + "prepare": "npm run compile" + }, + "dependencies": { + "@ethersproject/providers": "^5.7.2", + "@verida/encryption-utils": "*", + "@verida/helpers": "^3.0.3", + "@verida/types": "^3.0.2", + "@verida/web3": "^3.0.3" + }, + "devDependencies": { + "@verida/vda-common-test": "^3.0.2", + "dotenv": "^16.0.3", + "ethers": "5.7.0", + "gts": "^3.1.0", + "mocha": "^10.1.0", + "ts-mocha": "^10.0.0", + "ts-node": "^10.7.0", + "typescript": "^4.6.4" + } +} diff --git a/packages/vda-xp-reward-client/src/blockchain/ownerApi.ts b/packages/vda-xp-reward-client/src/blockchain/ownerApi.ts new file mode 100644 index 00000000..57bfb726 --- /dev/null +++ b/packages/vda-xp-reward-client/src/blockchain/ownerApi.ts @@ -0,0 +1,95 @@ +import { VdaClientConfig } from "@verida/types"; +import { VeridaXPRewardClient } from "./userApi"; +import { VdaVerificationUtils } from "@verida/vda-common"; + +export class VeridaXPRewardOwnerApi extends VeridaXPRewardClient { + + public constructor(config: VdaClientConfig) { + super(config); + } + + /** + * Add a trusted signer + * @param didAddress DID address to be added + */ + public async addTrustedSigner( + didAddress: string + ) { + await VdaVerificationUtils.addTrustedSigner.call(this, didAddress); + } + + /** + * Remove a trusted signer + * @param didAddress DID address to be added + */ + public async removeTrustedSigner( + didAddress: string + ) { + await VdaVerificationUtils.removeTrustedSigner.call(this, didAddress); + } + + /** + * Check whether given address is a trusted signer + * @param didAddress DID address to be checked + * @returns true if trusted signer, otherwise false + */ + public async isTrustedSigner(didAddress: string) { + return await VdaVerificationUtils.isTrustedSigner.call(this, didAddress); + } + + /** + * @dev Set the denominator for rate values + * @param denominator - new value to be set + */ + public async setRateDenominator(denominator: bigint) { + if (this.readOnly || !this.vdaWeb3Client) { + throw new Error(`Unable to submit to blockchain. In read only mode.`); + } + + const response = await this.vdaWeb3Client!.setRateDenominator(denominator); + + if (response.success !== true) { + throw new Error(`Failed to set rate denominator : ${response.reason}`); + } + } + + /** + * @dev Update the XP-VDA conversion rate + * @param newRate - New value to be updated. This is the float value. + * Should be set in the contract after multiplied with the `rateDenominator` + */ + public async setConversionRate(newRate: number) { + if (this.readOnly || !this.vdaWeb3Client) { + throw new Error(`Unable to submit to blockchain. In read only mode.`); + } + + const denominator = await this.getRateDenominator(); + const rate: bigint = BigInt(Number(denominator) * newRate); + + const response = await this.vdaWeb3Client!.setConversionRate(rate); + + if (response.success !== true) { + throw new Error(`Failed to set conversion rate : ${response.reason}`); + } + } + + /** + * @dev Withdraw tokens + * @param to - Recipient wallet address + * @param amount - Token amount to be withdrawn + */ + public async withdraw( + to: string, + amount: bigint, + ) { + if (this.readOnly || !this.vdaWeb3Client) { + throw new Error(`Unable to submit to blockchain. In read only mode.`); + } + + const response = await this.vdaWeb3Client!.withdraw(to, amount); + + if (response.success !== true) { + throw new Error(`Failed to withdraw : ${response.reason}`); + } + } +} \ No newline at end of file diff --git a/packages/vda-xp-reward-client/src/blockchain/userApi.ts b/packages/vda-xp-reward-client/src/blockchain/userApi.ts new file mode 100644 index 00000000..c941a8e2 --- /dev/null +++ b/packages/vda-xp-reward-client/src/blockchain/userApi.ts @@ -0,0 +1,217 @@ +import { + getVeridaContract, + VeridaContract +} from "@verida/web3" +import { Web3SelfTransactionConfig, VdaClientConfig } from '@verida/types' +import { ethers, Contract, BytesLike } from "ethers"; +import { getContractInfoForNetwork, RPC_URLS, getVeridaSign, getVeridaSignWithNonce } from "@verida/vda-common"; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { explodeDID } from '@verida/helpers' +import EncryptionUtils from "@verida/encryption-utils"; + +/** + * This is the claim information + * + * @interface ClaimXPInfo + * @member {string} typeId - the Proof type. Ex : `gamer31` + * @member {string} uniqueId - Unique Id of the proof. + * @member {number} issueYear - Issued year of the proof + * @member {number} issueMonth - Issued month of the proof + * @member {bigint} xp - Value of xp + * @member {BytesLike} signature - Proof signed by a trusted signer + */ +export interface ClaimXPInfo { + typeId: string, + uniqueId?: string, + issueYear: number, + issueMonth: number, + xp: bigint, + signature: BytesLike, +} + +/** + * This is a client class that interacts with the `VDAXPReward` contract of Verida + */ +export class VeridaXPRewardClient { + + protected config: VdaClientConfig + protected network: string + protected didAddress?: string + + protected vdaWeb3Client? : VeridaContract + + protected readOnly: boolean + protected contract?: ethers.Contract + + public constructor(config: VdaClientConfig) { + if (!config.callType) { + config.callType = 'web3' + } + + this.config = config + this.readOnly = true + if (!config.web3Options) { + config.web3Options = {} + } + + this.network = config.network + + if (config.callType == 'web3' && !(config.web3Options).rpcUrl) { + ( config.web3Options).rpcUrl = RPC_URLS[this.network] + } + + const contractInfo = getContractInfoForNetwork("VDAXPReward", this.network) + + if (config.did) { + this.readOnly = false + const { address } = explodeDID(config.did) + this.didAddress = address.toLowerCase() + + this.vdaWeb3Client = getVeridaContract( + config.callType, + {...contractInfo, + ...config.web3Options}) + } else { + let rpcUrl = (config.web3Options).rpcUrl + if (!rpcUrl) { + rpcUrl = RPC_URLS[this.network] + } + + const provider = new JsonRpcProvider(rpcUrl) + + this.contract = new Contract(contractInfo.address, contractInfo.abi.abi, provider) + } + } + + /** + * Get a nonce from VDAXPReward contract + * @returns nonce of DID + */ + public async nonceFN() { + if (!this.vdaWeb3Client) { + throw new Error(`Config must specify 'did' or 'signKey'`) + } + + const response = await this.vdaWeb3Client.nonce(this.didAddress); + if (response.data === undefined) { + throw new Error('Error in getting nonce'); + } + return response.data; + } + + /** + * Return the Reward token address-Verida token associated with `VDAXPRewardContract` + * @returns The address of token contract + */ + public async getTokenAddress() { + let response + + try { + if (this.vdaWeb3Client) { + response = await this.vdaWeb3Client.getTokenAddress() + response = response.data + } else { + response = await this.contract!.callStatic.getTokenAddress() + } + + return response; + } catch (err:any ) { + throw new Error(`Failed to get token address (${err.message})`) + } + } + + /** + * Return the denominator of conversion rate for `Verida XP` to `Verida token` + * @returns The denominator for conversion rate + */ + public async getRateDenominator() : Promise { + let response + + try { + if (this.vdaWeb3Client) { + response = await this.vdaWeb3Client.getRateDenominator() + response = response.data + } else { + response = await this.contract!.callStatic.getRateDenominator() + } + + return BigInt(response); + } catch (err:any ) { + throw new Error(`Failed to get rate denominator (${err.message})`) + } + } + + /** + * Return the conversion rate for `Verida XP` to `Verida token` + * @returns The conversion rate divided by the rate denominator + */ + public async getConversionRate() : Promise { + let response + let denominator + + denominator = Number(await this.getRateDenominator()); + + try { + if (this.vdaWeb3Client) { + response = await this.vdaWeb3Client.getConversionRate() + response = response.data + + } else { + response = await this.contract!.callStatic.getConversionRate() + } + + response = Number(response / denominator); + + return response; + } catch (err:any ) { + throw new Error(`Failed to get covnersion rate (${err.message})`) + } + } + + /** + * Claim reward to a specified address + * @param to - Reward token recipient wallet address + * @param claims - Array of claim information + */ + public async claimXPReward( + to: string, + claims: ClaimXPInfo[] + ) { + if (this.readOnly) { + throw new Error(`Unable to submit to blockchain. No 'signKey' provided in config.`) + } + + // Create `requestSignature` and `requestProof` + const nonce = await this.nonceFN(); + let requestMsg = ethers.utils.solidityPack( + ['address', 'address'], + [this.didAddress, to] + ); + for (let i = 0; i < claims.length; i++) { + requestMsg = ethers.utils.solidityPack( + ['bytes', 'bytes'], + [requestMsg, claims[i].signature] + ); + } + requestMsg = ethers.utils.solidityPack( + ['bytes', 'uint'], + [requestMsg, nonce] + ); + + const requestSignature = getVeridaSign(requestMsg, this.config.signKey!); + const proofMsg = `${this.didAddress}${this.didAddress}`.toLowerCase(); + const requestProof = getVeridaSign(proofMsg, this.config.signKey!); + + const response = await this.vdaWeb3Client!.claimXPReward( + this.didAddress, + to, + claims, + requestSignature, + requestProof + ) + + if (response.success !== true) { + throw new Error(`Failed to claim xp: (${response.reason})`) + } + } +} \ No newline at end of file diff --git a/packages/vda-xp-reward-client/src/index.ts b/packages/vda-xp-reward-client/src/index.ts new file mode 100644 index 00000000..0c6eaf6f --- /dev/null +++ b/packages/vda-xp-reward-client/src/index.ts @@ -0,0 +1,2 @@ +export * from './blockchain/userApi' +export * from './blockchain/ownerApi' \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/const.ts b/packages/vda-xp-reward-client/test/const.ts new file mode 100644 index 00000000..94243438 --- /dev/null +++ b/packages/vda-xp-reward-client/test/const.ts @@ -0,0 +1,13 @@ +import { BytesLike } from "ethers" + +/** + * @notice This interface is part of `ClaimInfo` struct of `VDAXPReward` contract + */ +export interface ClaimInfo { + typeId: string + uniqueId: string + issueYear: number + issueMonth: number + xp: bigint + signature: BytesLike +} \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/helpers.ts b/packages/vda-xp-reward-client/test/helpers.ts new file mode 100644 index 00000000..4a290880 --- /dev/null +++ b/packages/vda-xp-reward-client/test/helpers.ts @@ -0,0 +1,48 @@ +import { DID_LIST, TRUSTED_SIGNER } from "@verida/vda-common-test"; +import { VeridaXPRewardOwnerApi } from "../src/blockchain/ownerApi"; +import { EnvironmentType } from "@verida/types"; +import { Wallet } from 'ethers'; +import EncryptionUtils from `@verida/encryption-utils`; + +/** + * Add initial data that is used in testing + * @param configuration Configuration to create ownerApi + * @param ownerApi Optional. If not provided, create with above `configuration` param + */ +export async function addInitialData( + configuration: Record, + ownerApi: VeridaXPRewardOwnerApi | undefined = undefined +) { + if (ownerApi === undefined) { + // ownerDID defined to ownerApi as write mode + const owenrDID = DID_LIST[0]; + + ownerApi = new VeridaXPRewardOwnerApi({ + did: owenrDID.address, + network: EnvironmentType.TESTNET, + ...configuration + }); + } + + console.log("Check and add trusted signer..."); + if ((await ownerApi.isTrustedSigner(TRUSTED_SIGNER.address)) !== true) { + await ownerApi.addTrustedSigner(TRUSTED_SIGNER.address); + } + + console.log("Set conversion rate..."); + if ((await ownerApi.getConversionRate()) === 0) { + await ownerApi.setConversionRate(0.05); + } +} + +/** + * Get a proof that will be used in `claim()` and `claimToStorage()` functions + * @param userAddress User wallet address that call the claiming functions + * @param trustedSigner A trusted signer that is added to the `VDARewardContract` + * @returns Bytearray + */ +export function generateProof(userAddress: string, trustedSigner: Wallet) { + const proofMsg = `${trustedSigner.address}${userAddress}`.toLowerCase(); + const privateKeyArray = new Uint8Array(Buffer.from(trustedSigner.privateKey.slice(2), 'hex')); + return EncryptionUtils.signData(proofMsg, privateKeyArray); +} \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/owner.test.ts b/packages/vda-xp-reward-client/test/owner.test.ts new file mode 100644 index 00000000..554a49a4 --- /dev/null +++ b/packages/vda-xp-reward-client/test/owner.test.ts @@ -0,0 +1,95 @@ +require('dotenv').config(); +import { DID_LIST, getBlockchainAPIConfiguration, ERC20Manager } from "@verida/vda-common-test" +import { VeridaXPRewardOwnerApi } from '../src/blockchain/ownerApi'; +import { EnvironmentType } from "@verida/types"; +import { addInitialData } from "./helpers"; +import { Wallet } from 'ethers'; + +const assert = require('assert') + +const privateKey = process.env.PRIVATE_KEY +if (!privateKey) { + throw new Error('No PRIVATE_KEY in the env file'); +} +const configuration = getBlockchainAPIConfiguration(privateKey); + + +const createOwnerAPI = () => { + const ownerDID = DID_LIST[0]; + + return new VeridaXPRewardOwnerApi({ + did: ownerDID.address, + network: EnvironmentType.TESTNET, + ...configuration + }) +} + +describe("Verida XPRewardOwnerApi Test", () => { + let ownerApi; + + before(async () => { + ownerApi = createOwnerAPI(); + await addInitialData(configuration, ownerApi); + }) + + describe("Trusted signer test", () => { + const newSigner = Wallet.createRandom(); + + it("Add a trusted signer",async () => { + let response = await ownerApi.isTrustedSigner(newSigner.address); + assert.equal(response, false); + + await ownerApi.addTrustedSigner(newSigner.address); + + response = await ownerApi.isTrustedSigner(newSigner.address); + assert.equal(response, true); + }) + + it("Remove a trusted signer",async () => { + await ownerApi.removeTrustedSigner(newSigner.address); + + const response = await ownerApi.isTrustedSigner(newSigner.address); + assert.equal(response, false); + }) + }) + + describe("Set rate denominator", () => { + let orgRateDenominator; + before(async () => { + orgRateDenominator = await ownerApi.getRateDenominator(); + }) + + it("Set rate denominator", async () => { + const newDenominator = orgRateDenominator * 100000000n; + await ownerApi.setRateDenominator(newDenominator); + + const curDenominator = await ownerApi.getRateDenominator(); + assert.equal(newDenominator, curDenominator); + + // Restore denominator + await ownerApi.setRateDenominator(orgRateDenominator); + }) + }) + + describe("Set conversion rate", () => { + let orgConversionRate; + before(async () => { + orgConversionRate = await ownerApi.getConversionRate(); + }) + + it("Set conversion rate", async () => { + const newRate = orgConversionRate * 10; + await ownerApi.setConversionRate(newRate); + + const curRate = await ownerApi.getConversionRate(); + assert.equal(newRate, curRate); + + // Restore rate + await ownerApi.setConversionRate(orgConversionRate); + }) + }) + + describe("Withdraw", () => { + + }) +}) \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/user_read.test.ts b/packages/vda-xp-reward-client/test/user_read.test.ts new file mode 100644 index 00000000..01c0fb22 --- /dev/null +++ b/packages/vda-xp-reward-client/test/user_read.test.ts @@ -0,0 +1,45 @@ +require('dotenv').config(); +import { DID_LIST, getBlockchainAPIConfiguration, ERC20Manager } from "@verida/vda-common-test" +import { EnvironmentType } from "@verida/types"; +import { addInitialData } from "./helpers"; +import { Wallet } from 'ethers'; +import { VeridaXPRewardClient } from "../src/blockchain/userApi"; + +const assert = require('assert') + +const privateKey = process.env.PRIVATE_KEY +if (!privateKey) { + throw new Error('No PRIVATE_KEY in the env file'); +} +const configuration = getBlockchainAPIConfiguration(privateKey); + + +const createXPRewardClientAPI = () => { + return new VeridaXPRewardClient({ + network: EnvironmentType.TESTNET, + }) +} + +describe("Verida RewardOwnerApi Test in Read mode", () => { + let xpRewardClientApi; + + before(async () => { + await addInitialData(configuration); + xpRewardClientApi = createXPRewardClientAPI(); + }) + + it("Get token address",async () => { + const response = await xpRewardClientApi.getTokenAddress(); + assert.ok(response && typeof(response) === `string`, 'Get the result'); + }) + + it("Get rate denominator",async () => { + const response = await xpRewardClientApi.getRateDenominator(); + assert.ok(response && response > 0, 'Get the result'); + }) + + it("Get conversion rate",async () => { + const response = await xpRewardClientApi.getConversionRate(); + assert.ok(response && response > 0, 'Get the result'); + }) +}) \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/user_write.test.ts b/packages/vda-xp-reward-client/test/user_write.test.ts new file mode 100644 index 00000000..3daf4727 --- /dev/null +++ b/packages/vda-xp-reward-client/test/user_write.test.ts @@ -0,0 +1,139 @@ +require('dotenv').config(); +import { getBlockchainAPIConfiguration, ERC20Manager, TRUSTED_SIGNER } from "@verida/vda-common-test" +import { RECIPIENT_WALLET } from "@verida/vda-common-test" +import { EnvironmentType } from "@verida/types"; +import { addInitialData, generateProof } from "./helpers"; +import { Wallet, BigNumber, ethers } from 'ethers'; +import { ClaimInfo } from "./const"; +import { VeridaXPRewardClient } from "../src/blockchain/userApi"; + +import { CONTRACT_ADDRESS } from "@verida/vda-common"; +import EncryptionUtils from "@verida/encryption-utils"; + +const assert = require('assert') + +const privateKey = process.env.PRIVATE_KEY +if (!privateKey) { + throw new Error('No PRIVATE_KEY in the env file'); +} +const configuration = getBlockchainAPIConfiguration(privateKey); + +// Create `VeridaXPRewardClient` in write mode +const createXPRewardClientAPI = (did:Wallet, configuration: any) => { + return new VeridaXPRewardClient({ + did: `did:vda:testnet:${did.address}`, + signKey: did.privateKey, + network: EnvironmentType.TESTNET, + ...configuration + }) +} + +describe("Verida XPRewardOwnerApi Test in read/write mode", () => { + let xpRewardClientApi: VeridaXPRewardClient; + let tokenAPI: ERC20Manager; + + const userDID = Wallet.createRandom(); + const xpAmount = 100; + const xpAmountWithUniqueId = 200; + + before(async () => { + await addInitialData(configuration); + xpRewardClientApi = createXPRewardClientAPI(new Wallet(userDID.privateKey), configuration); + + const TOKEN_ADDRESS = await xpRewardClientApi.getTokenAddress(); + const rewardXPContractAddress = CONTRACT_ADDRESS["VDAXPReward"].testnet!; + + // Create tokenAPI for claiming test + tokenAPI = new ERC20Manager( + TOKEN_ADDRESS, + process.env.RPC_URL, + privateKey + ) + + // Calculate necessary amount of token for reward + const totalXPAmount = xpAmount + xpAmountWithUniqueId; + const conversionRate = Number(await xpRewardClientApi.getConversionRate()); + const tokenAmount = BigInt(totalXPAmount * conversionRate); + + // Check out token amount of xp reward contract + if (await tokenAPI.balanceOf(rewardXPContractAddress) < tokenAmount) { + if (process.env.NEED_TOKEN_MINT === `true`) { + const mintAmount = tokenAmount; + tokenAPI.mint(rewardXPContractAddress, mintAmount); + } else { + throw new Error(`Not enough token in the 'VDAXPReward' contract at ${rewardXPContractAddress}`); + } + } + }) + + describe("Get function tests", () => { + it("Get token address",async () => { + const response = await xpRewardClientApi.getTokenAddress(); + assert.ok(response && typeof(response) === `string`, 'Get the result'); + }) + + it("Get rate denominator",async () => { + const response = await xpRewardClientApi.getRateDenominator(); + assert.ok(response && response > 0, 'Get the result'); + }) + + it("Get conversion rate",async () => { + const response = await xpRewardClientApi.getConversionRate(); + assert.ok(response && response > 0, 'Get the result'); + }) + }) + + describe("Claim XP Reward", () => { + const claimData: ClaimInfo = { + typeId: `type-${new Date().getTime()}`, + uniqueId: ``, + issueYear: 0, + issueMonth: 0, + xp: 50n, + signature: '' + } + + const getProof = (didWallet:Wallet, data: ClaimInfo, trustedSigner: Wallet) => { + const rawMsg = ethers.utils.solidityPack( + ['address', 'string', 'uint16', 'uint8', 'uint'], + [didWallet.address, `${data.typeId}${data.uniqueId}`, data.issueYear, data.issueMonth, data.xp] + ); + const privateKeyArray = new Uint8Array(Buffer.from(trustedSigner.privateKey.slice(2), 'hex')) + return EncryptionUtils.signData(rawMsg, privateKeyArray); + } + + before(async () => { + // Set issueYear and month + const issueTime = new Date(); + issueTime.setMonth(issueTime.getMonth() - 1); + claimData.issueYear = issueTime.getFullYear(); + claimData.issueMonth = issueTime.getMonth()+1; + }) + + it("Claim without `uniqueId`", async () => { + claimData.xp = BigInt(xpAmount); + claimData.uniqueId = ''; + claimData.signature = getProof(userDID, claimData, new Wallet(TRUSTED_SIGNER.privateKey)); + + const orgBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); + await xpRewardClientApi.claimXPReward(RECIPIENT_WALLET.address, [claimData]) + + const newBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); + + assert.ok(newBalance > orgBalance); + }) + + it("Claim with `uniqueId`", async () => { + claimData.typeId = `${claimData.typeId}-1` + claimData.uniqueId = 'uniqueid-1'; + claimData.xp = BigInt(xpAmount); + claimData.signature = getProof(userDID, claimData, new Wallet(TRUSTED_SIGNER.privateKey)); + + const orgBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); + await xpRewardClientApi.claimXPReward(RECIPIENT_WALLET.address, [claimData]) + + const newBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); + assert.ok(newBalance > orgBalance); + }) + }) +}) \ No newline at end of file diff --git a/packages/vda-xp-reward-client/tsconfig.json b/packages/vda-xp-reward-client/tsconfig.json new file mode 100644 index 00000000..6b8e551e --- /dev/null +++ b/packages/vda-xp-reward-client/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "rootDir": ".", + "outDir": "build", + "sourceMap": true, + "strict": true, + "declaration": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": [ + "src/**/*.ts", + ], + "exclude": ["node_modules", "test", "build"], + "lib": [ + "es5", + "es2015" + ] + } + \ No newline at end of file From 23be996d5cf26d03d28292ae2296259395eef900 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 31 May 2024 13:43:13 +0900 Subject: [PATCH 06/12] [vda-reward-client] Update the ReadMe - Fix typos --- packages/vda-reward-client/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vda-reward-client/README.md b/packages/vda-reward-client/README.md index a2b33f1d..ee3d3fc4 100644 --- a/packages/vda-reward-client/README.md +++ b/packages/vda-reward-client/README.md @@ -36,7 +36,7 @@ import { EnvironmentType } from '@verida/types' // Create name Client const rewardClient = new VeridaRewardClient({ - environment: EnvironmentType.TESTNET + network: EnvironmentType.TESTNET }) ``` @@ -57,9 +57,9 @@ Setup the library in `read and write` mode to support the above get methods *and import { VeridaRewardClient } from '@verida/vda-name-client' import { EnvironmentType, Web3CallType } from '@verida/types' -// DID address that controls the proof of eligibility to mint the SBT +// DID address that controls the proof const DID_ADDRESS = '0x...' -// DID private key that controls the proof of eligibility to mint the SBT +// DID private key that controls the proof const DID_PRIVATE_KEY = '0x...' // Polygon private key that will fund blockchain transactions const POLYGON_PRIVATE_KEY = '0x...' @@ -70,9 +70,9 @@ const RPC_URL = 'https://rpc-mumbai.maticvigil.com' // Create name Client const rewardClient = new VeridaRewardClient({ - environment: EnvironmentType.TESTNET + network: EnvironmentType.TESTNET callType: CALL_TYPE, - did: DID_ADDRESS, + did: `did:vda:testnet:${DID_ADDRESS}`, signKey: DID_PRIVATE_KEY, web3Options: { rpcUrl: RPC_URL, From 89c7aaf600baf1817c28a7d6d7ddbe2d362c04c0 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 25 Jul 2024 09:51:54 +0900 Subject: [PATCH 07/12] [types] v - Added `xpReward` to the `INetworkContracts` interface in the `NetworkInterfaces.ts` --- packages/types/CHANGELOG.md | 7 +++++++ packages/types/src/NetworkInterfaces.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index da2f0486..6f2153f3 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,12 @@ +2024-07-15 (v.) +------------------- + +- Added `xpReward` to the `INetworkContracts` interface in the `NetworkInterfaces.ts` + + 2024-07-15 (v.4.0.0) ------------------- + - Removed `web3` parameter in `Web3SelfTransactionConfig` - Support BlockchainAnchor and Verida Network refactor diff --git a/packages/types/src/NetworkInterfaces.ts b/packages/types/src/NetworkInterfaces.ts index 383f1936..15cae86c 100644 --- a/packages/types/src/NetworkInterfaces.ts +++ b/packages/types/src/NetworkInterfaces.ts @@ -37,6 +37,7 @@ export interface INetworkContracts { didLinkage: IContractInfo | null reward: IContractInfo | null solboundNFT: IContractInfo | null + xpReward: IContractInfo | null } export type TContractNames = keyof INetworkContracts; From 30231ba997806e6bc0a9a657b89268bca97210de Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 30 Jul 2024 16:22:39 +0900 Subject: [PATCH 08/12] [vda-common] v - Updated the `XPRewardContract` address and abi --- packages/vda-common/CHANGELOG.md | 5 ++++ packages/vda-common/src/abi/VDAXPReward.json | 31 ++++---------------- packages/vda-common/src/contract.ts | 8 ++++- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/vda-common/CHANGELOG.md b/packages/vda-common/CHANGELOG.md index 36b3e6c2..091c3985 100644 --- a/packages/vda-common/CHANGELOG.md +++ b/packages/vda-common/CHANGELOG.md @@ -1,3 +1,8 @@ +2024-07-30 (v) +------------------- + +- Updated `XPReward` contract address and abi + 2024-07-15 (v.4.0.0) ------------------- diff --git a/packages/vda-common/src/abi/VDAXPReward.json b/packages/vda-common/src/abi/VDAXPReward.json index 854bc2b8..96aca15d 100644 --- a/packages/vda-common/src/abi/VDAXPReward.json +++ b/packages/vda-common/src/abi/VDAXPReward.json @@ -81,32 +81,6 @@ "name": "InvalidConversionRate", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "didAddress", - "type": "address" - }, - { - "internalType": "string", - "name": "typeId", - "type": "string" - }, - { - "internalType": "uint256", - "name": "xp", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "name": "InvalidProof", - "type": "error" - }, { "inputs": [ { @@ -381,6 +355,11 @@ "internalType": "bytes", "name": "signature", "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" } ], "internalType": "struct IVDAXPReward.ClaimInfo[]", diff --git a/packages/vda-common/src/contract.ts b/packages/vda-common/src/contract.ts index 7f91696c..a6c78c07 100644 --- a/packages/vda-common/src/contract.ts +++ b/packages/vda-common/src/contract.ts @@ -10,6 +10,7 @@ export const CONTRACT_ABI : Record = { reward: require('./abi/VDARewardContract.json'), storageNodeRegistry: require('./abi/StorageNodeRegistry.json'), solboundNFT: require('./abi/SoulboundNFT.json'), + xpReward: require('./abi/VDAXPReward.json'), } /** @@ -35,7 +36,8 @@ export const CHAIN_CONTRACTS : Record = { abi: CONTRACT_ABI["didLinkage"] }, reward: null, - solboundNFT: null + solboundNFT: null, + xpReward: null }, polamoy: { token: { @@ -65,6 +67,10 @@ export const CHAIN_CONTRACTS : Record = { solboundNFT: { address: "0xa7D552ccc8E561164d26711516033bcdeD975ca3", abi: CONTRACT_ABI["solboundNFT"] + }, + xpReward: { + address: "0x6007b8e2DC179ea20c39Ccf6C4d500362E542667", + abi: CONTRACT_ABI["xpReward"] } } } From b694872c50b6a49664c00c1b58d5e95ad1c66cd7 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 30 Jul 2024 16:23:27 +0900 Subject: [PATCH 09/12] [vda-web3-client] Updated whitelist for `XPRewardContract` added --- packages/vda-web3-client/src/constants.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vda-web3-client/src/constants.ts b/packages/vda-web3-client/src/constants.ts index 4b9d8fde..62839e8f 100644 --- a/packages/vda-web3-client/src/constants.ts +++ b/packages/vda-web3-client/src/constants.ts @@ -18,7 +18,8 @@ function createContractWhiteList() { "reward": undefined, "storageNodeRegistry": undefined, "token": undefined, - "solboundNFT": undefined + "solboundNFT": undefined, + "xpReward": undefined }; const CONTRACT_NAMES = Object.keys(contractProperties) as (TContractNames)[]; From 2efc69fc4a2000efefee1d8760601260c9fae565 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 30 Jul 2024 16:24:19 +0900 Subject: [PATCH 10/12] [vda-token-client] Updated typos --- packages/vda-token-client/src/blockchain/VeridaTokenClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vda-token-client/src/blockchain/VeridaTokenClient.ts b/packages/vda-token-client/src/blockchain/VeridaTokenClient.ts index c282e4b1..2bef61f9 100644 --- a/packages/vda-token-client/src/blockchain/VeridaTokenClient.ts +++ b/packages/vda-token-client/src/blockchain/VeridaTokenClient.ts @@ -22,7 +22,7 @@ export class VeridaTokenClient { protected constructor(config: Web3SelfTransactionConfig, addr?: string) { this.config = config; if (!config.blockchainAnchor) { - throw new Error('Should provid "blockchainAnchor" in the configuration'); + throw new Error('Should provide "blockchainAnchor" in the configuration'); } this.blockchainAnchor = config.blockchainAnchor; From c2d5063e66a60b744490b239953de6ab6b55955e Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 30 Jul 2024 16:26:06 +0900 Subject: [PATCH 11/12] [vda-xp-reward-client] v - Update on review - Added `depositToke()` function --- packages/vda-xp-reward-client/.env.example | 6 +- packages/vda-xp-reward-client/CHANGELOG.md | 6 ++ packages/vda-xp-reward-client/package.json | 7 +- .../src/blockchain/userApi.ts | 102 ++++++++---------- packages/vda-xp-reward-client/test/const.ts | 9 +- packages/vda-xp-reward-client/test/helpers.ts | 4 +- .../vda-xp-reward-client/test/owner.test.ts | 60 ++++++++--- .../test/user_read.test.ts | 15 +-- .../test/user_write.test.ts | 47 +++++--- 9 files changed, 157 insertions(+), 99 deletions(-) diff --git a/packages/vda-xp-reward-client/.env.example b/packages/vda-xp-reward-client/.env.example index c4e41edb..c87d3c83 100644 --- a/packages/vda-xp-reward-client/.env.example +++ b/packages/vda-xp-reward-client/.env.example @@ -1,7 +1,11 @@ ## These are used for test only. +OWNER_PRIVATE_KEY = "" PRIVATE_KEY = "" RPC_URL="" ## If set `true`, the test code mint necessary tokens to the `RewardContract` ## If not set, the owenr of the `RewardContract` should mint or send enough tokens to the contract. -NEED_TOKEN_MINT="true"; \ No newline at end of file +NEED_TOKEN_MINT="true"; + +# Test chain : One of "POLAMOY", "POLPOS", "DEVNET" +BLOCKCHAIN_ANCHOR = "POLAMOY" \ No newline at end of file diff --git a/packages/vda-xp-reward-client/CHANGELOG.md b/packages/vda-xp-reward-client/CHANGELOG.md index 013d6295..e79e1c5b 100644 --- a/packages/vda-xp-reward-client/CHANGELOG.md +++ b/packages/vda-xp-reward-client/CHANGELOG.md @@ -1,3 +1,9 @@ +2024-07-30 (v) +------------------- + +- Update on review +- Added `depositToken()` feature + 2024-05-31 (v0.1.0) ------------------- diff --git a/packages/vda-xp-reward-client/package.json b/packages/vda-xp-reward-client/package.json index 1402a656..de602e5c 100644 --- a/packages/vda-xp-reward-client/package.json +++ b/packages/vda-xp-reward-client/package.json @@ -15,7 +15,7 @@ "keywords": [], "scripts": { "tests": "ts-mocha './test/**/*.tests.ts'", - "test": "ts-mocha --timeout 200000", + "test": "ts-mocha --timeout 300000", "lint": "gts lint", "clean": "gts clean", "compile": "rm -rf build && tsc", @@ -24,11 +24,14 @@ "prepare": "npm run compile" }, "dependencies": { + "@verida/vda-common": "^4.0.0", + "@verida/vda-token-client": "^0.1.0", "@ethersproject/providers": "^5.7.2", "@verida/encryption-utils": "*", "@verida/helpers": "^3.0.3", "@verida/types": "^3.0.2", - "@verida/web3": "^3.0.3" + "@verida/web3": "^3.0.3", + "@verida/vda-client-base": "4.0.0" }, "devDependencies": { "@verida/vda-common-test": "^3.0.2", diff --git a/packages/vda-xp-reward-client/src/blockchain/userApi.ts b/packages/vda-xp-reward-client/src/blockchain/userApi.ts index c941a8e2..c5de85be 100644 --- a/packages/vda-xp-reward-client/src/blockchain/userApi.ts +++ b/packages/vda-xp-reward-client/src/blockchain/userApi.ts @@ -1,13 +1,8 @@ -import { - getVeridaContract, - VeridaContract -} from "@verida/web3" -import { Web3SelfTransactionConfig, VdaClientConfig } from '@verida/types' -import { ethers, Contract, BytesLike } from "ethers"; -import { getContractInfoForNetwork, RPC_URLS, getVeridaSign, getVeridaSignWithNonce } from "@verida/vda-common"; -import { JsonRpcProvider } from '@ethersproject/providers'; -import { explodeDID } from '@verida/helpers' -import EncryptionUtils from "@verida/encryption-utils"; +import { VdaClientConfig, Web3SelfTransactionConfig } from '@verida/types' +import { ethers, BytesLike, BigNumber } from "ethers"; +import { getContractInfoForBlockchainAnchor, getVeridaSign } from "@verida/vda-common"; +import { VeridaClientBase } from "@verida/vda-client-base"; +import { VeridaTokenClient } from '@verida/vda-token-client'; /** * This is the claim information @@ -32,55 +27,10 @@ export interface ClaimXPInfo { /** * This is a client class that interacts with the `VDAXPReward` contract of Verida */ -export class VeridaXPRewardClient { - - protected config: VdaClientConfig - protected network: string - protected didAddress?: string - - protected vdaWeb3Client? : VeridaContract - - protected readOnly: boolean - protected contract?: ethers.Contract +export class VeridaXPRewardClient extends VeridaClientBase{ public constructor(config: VdaClientConfig) { - if (!config.callType) { - config.callType = 'web3' - } - - this.config = config - this.readOnly = true - if (!config.web3Options) { - config.web3Options = {} - } - - this.network = config.network - - if (config.callType == 'web3' && !(config.web3Options).rpcUrl) { - ( config.web3Options).rpcUrl = RPC_URLS[this.network] - } - - const contractInfo = getContractInfoForNetwork("VDAXPReward", this.network) - - if (config.did) { - this.readOnly = false - const { address } = explodeDID(config.did) - this.didAddress = address.toLowerCase() - - this.vdaWeb3Client = getVeridaContract( - config.callType, - {...contractInfo, - ...config.web3Options}) - } else { - let rpcUrl = (config.web3Options).rpcUrl - if (!rpcUrl) { - rpcUrl = RPC_URLS[this.network] - } - - const provider = new JsonRpcProvider(rpcUrl) - - this.contract = new Contract(contractInfo.address, contractInfo.abi.abi, provider) - } + super(config, "xpReward"); } /** @@ -208,10 +158,46 @@ export class VeridaXPRewardClient { claims, requestSignature, requestProof - ) + ); if (response.success !== true) { throw new Error(`Failed to claim xp: (${response.reason})`) } } + + /** + * Deposit verida token to the `XPRewardContract` for `claimXPReward()` function calls + * In face, this function is for owner to deposit token to the contract. + * By the way, any others also can deposit tokens instead of owner + * @param amount Amount of token to be deposited + */ + public async depositToken(amount: BigNumber) { + if (this.readOnly) { + throw new Error(`Unable to submit to blockchain. No 'signKey' provided in config.`) + } + + // Check the Token contract address is the same as the Verida token contract in the `vda-common` + const contractTokenAddress = await this.getTokenAddress(); + const vdaTokenAddress = getContractInfoForBlockchainAnchor(this.blockchainAnchor, "token").address; + + // if (contractTokenAddress.toLowerCase() !== vdaTokenAddress.toLowerCase()) { + // throw new Error(`Reward token address in the contract not matched with the Verida Token address`); + // } + + const xpRewardContractAddress = getContractInfoForBlockchainAnchor(this.blockchainAnchor, "xpReward").address; + + const web3Options: Web3SelfTransactionConfig = this.config.web3Options; + + const vdaTokenApi = await VeridaTokenClient.CreateAsync({ + blockchainAnchor: this.blockchainAnchor, + privateKey: web3Options.privateKey, + rpcUrl: web3Options.rpcUrl + }); + + try { + await vdaTokenApi.transfer(xpRewardContractAddress, amount); + } catch (e: any) { + console.log("Errr"); + } + } } \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/const.ts b/packages/vda-xp-reward-client/test/const.ts index 94243438..22b916eb 100644 --- a/packages/vda-xp-reward-client/test/const.ts +++ b/packages/vda-xp-reward-client/test/const.ts @@ -1,3 +1,5 @@ +import { BlockchainAnchor } from "@verida/types" +import { DID_LIST } from "@verida/vda-common-test" import { BytesLike } from "ethers" /** @@ -10,4 +12,9 @@ export interface ClaimInfo { issueMonth: number xp: bigint signature: BytesLike -} \ No newline at end of file + proof: BytesLike +} + +export const Test_BlockchainAnchor = process.env.BLOCKCHAIN_ANCHOR !== undefined ? BlockchainAnchor[process.env.BLOCKCHAIN_ANCHOR] : BlockchainAnchor.POLAMOY; + +export const CONTEXT_SIGNER = DID_LIST[1]; \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/helpers.ts b/packages/vda-xp-reward-client/test/helpers.ts index 4a290880..b9f111c8 100644 --- a/packages/vda-xp-reward-client/test/helpers.ts +++ b/packages/vda-xp-reward-client/test/helpers.ts @@ -1,8 +1,8 @@ import { DID_LIST, TRUSTED_SIGNER } from "@verida/vda-common-test"; import { VeridaXPRewardOwnerApi } from "../src/blockchain/ownerApi"; -import { EnvironmentType } from "@verida/types"; import { Wallet } from 'ethers'; import EncryptionUtils from `@verida/encryption-utils`; +import { Test_BlockchainAnchor } from "./const"; /** * Add initial data that is used in testing @@ -19,7 +19,7 @@ export async function addInitialData( ownerApi = new VeridaXPRewardOwnerApi({ did: owenrDID.address, - network: EnvironmentType.TESTNET, + blockchainAnchor: Test_BlockchainAnchor, ...configuration }); } diff --git a/packages/vda-xp-reward-client/test/owner.test.ts b/packages/vda-xp-reward-client/test/owner.test.ts index 554a49a4..26642a04 100644 --- a/packages/vda-xp-reward-client/test/owner.test.ts +++ b/packages/vda-xp-reward-client/test/owner.test.ts @@ -1,41 +1,49 @@ require('dotenv').config(); -import { DID_LIST, getBlockchainAPIConfiguration, ERC20Manager } from "@verida/vda-common-test" +import { getBlockchainAPIConfiguration, ERC20Manager, DID_LIST, sleep } from "@verida/vda-common-test" import { VeridaXPRewardOwnerApi } from '../src/blockchain/ownerApi'; -import { EnvironmentType } from "@verida/types"; import { addInitialData } from "./helpers"; -import { Wallet } from 'ethers'; +import { BigNumber, Wallet } from 'ethers'; +import { Test_BlockchainAnchor } from "./const"; +import { VeridaTokenClient } from "@verida/vda-token-client"; const assert = require('assert') -const privateKey = process.env.PRIVATE_KEY -if (!privateKey) { - throw new Error('No PRIVATE_KEY in the env file'); +const ownerPrivateKey = process.env.OWNER_PRIVATE_KEY; +if (!ownerPrivateKey) { + throw new Error('No OWNER_PRIVATE_KEY in the env file'); } -const configuration = getBlockchainAPIConfiguration(privateKey); +const configuration = getBlockchainAPIConfiguration(ownerPrivateKey); +const ownerWallet = new Wallet(ownerPrivateKey); +const ownerDID = `did:vda:${Test_BlockchainAnchor}:${ownerWallet.address}`; const createOwnerAPI = () => { - const ownerDID = DID_LIST[0]; - return new VeridaXPRewardOwnerApi({ - did: ownerDID.address, - network: EnvironmentType.TESTNET, + blockchainAnchor: Test_BlockchainAnchor, + did: ownerDID, // In fact, not used for owner functions. Used for read functions ...configuration }) } describe("Verida XPRewardOwnerApi Test", () => { let ownerApi; + let tokenApi: VeridaTokenClient; + + const DEPOSIT_AMOUNT = 10; before(async () => { ownerApi = createOwnerAPI(); await addInitialData(configuration, ownerApi); + + tokenApi = await VeridaTokenClient.CreateAsync({ + blockchainAnchor: Test_BlockchainAnchor, + }) }) describe("Trusted signer test", () => { const newSigner = Wallet.createRandom(); - it("Add a trusted signer",async () => { + it.only("Add a trusted signer",async () => { let response = await ownerApi.isTrustedSigner(newSigner.address); assert.equal(response, false); @@ -45,7 +53,7 @@ describe("Verida XPRewardOwnerApi Test", () => { assert.equal(response, true); }) - it("Remove a trusted signer",async () => { + it.only("Remove a trusted signer",async () => { await ownerApi.removeTrustedSigner(newSigner.address); const response = await ownerApi.isTrustedSigner(newSigner.address); @@ -89,7 +97,33 @@ describe("Verida XPRewardOwnerApi Test", () => { }) }) + describe("Deposit token", () => { + it("Deposit successfully", async () => { + const ownerBal: BigNumber = await tokenApi.balanceOf(ownerWallet.address); + + if (ownerBal.lt(DEPOSIT_AMOUNT)) { + throw new Error("Not enough Token in the wallet"); + } + + await ownerApi.depositToken(DEPOSIT_AMOUNT); + const ownerNewBal: BigNumber = await tokenApi.balanceOf(ownerWallet.address); + + assert.ok((ownerBal.sub(ownerNewBal)).eq(DEPOSIT_AMOUNT), "Deposited"); + }) + }) + describe("Withdraw", () => { + const recipient = new Wallet(DID_LIST[0].privateKey); + /** + * Might be failed if the token address of the `XPRewardContract` is different from the `VDAToken` contract address + */ + it("Withdraw successfully", async () => { + const orgBalance: BigNumber = await tokenApi.balanceOf(recipient.address); + + await ownerApi.withdraw(recipient.address, DEPOSIT_AMOUNT); + const newBalance: BigNumber = await tokenApi.balanceOf(recipient.address); + assert.ok((newBalance.sub(orgBalance)).eq(DEPOSIT_AMOUNT), "Withdrawn"); + }) }) }) \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/user_read.test.ts b/packages/vda-xp-reward-client/test/user_read.test.ts index 01c0fb22..9d724617 100644 --- a/packages/vda-xp-reward-client/test/user_read.test.ts +++ b/packages/vda-xp-reward-client/test/user_read.test.ts @@ -1,9 +1,8 @@ require('dotenv').config(); -import { DID_LIST, getBlockchainAPIConfiguration, ERC20Manager } from "@verida/vda-common-test" -import { EnvironmentType } from "@verida/types"; +import { getBlockchainAPIConfiguration } from "@verida/vda-common-test" import { addInitialData } from "./helpers"; -import { Wallet } from 'ethers'; import { VeridaXPRewardClient } from "../src/blockchain/userApi"; +import { Test_BlockchainAnchor } from "./const"; const assert = require('assert') @@ -16,15 +15,19 @@ const configuration = getBlockchainAPIConfiguration(privateKey); const createXPRewardClientAPI = () => { return new VeridaXPRewardClient({ - network: EnvironmentType.TESTNET, + blockchainAnchor: Test_BlockchainAnchor }) } -describe("Verida RewardOwnerApi Test in Read mode", () => { +describe("Verida RewardClientApi Test in Read mode", () => { let xpRewardClientApi; before(async () => { - await addInitialData(configuration); + const ownerPrivateKey = process.env.OWNER_PRIVATE_KEY; + if (ownerPrivateKey !== undefined) { + const ownerConfiguration = getBlockchainAPIConfiguration(ownerPrivateKey); + await addInitialData(ownerConfiguration); + } xpRewardClientApi = createXPRewardClientAPI(); }) diff --git a/packages/vda-xp-reward-client/test/user_write.test.ts b/packages/vda-xp-reward-client/test/user_write.test.ts index 3daf4727..262801ca 100644 --- a/packages/vda-xp-reward-client/test/user_write.test.ts +++ b/packages/vda-xp-reward-client/test/user_write.test.ts @@ -1,13 +1,12 @@ require('dotenv').config(); -import { getBlockchainAPIConfiguration, ERC20Manager, TRUSTED_SIGNER } from "@verida/vda-common-test" +import { getBlockchainAPIConfiguration, ERC20Manager, TRUSTED_SIGNER, DID_LIST } from "@verida/vda-common-test" import { RECIPIENT_WALLET } from "@verida/vda-common-test" -import { EnvironmentType } from "@verida/types"; -import { addInitialData, generateProof } from "./helpers"; -import { Wallet, BigNumber, ethers } from 'ethers'; -import { ClaimInfo } from "./const"; +import { addInitialData } from "./helpers"; +import { BigNumber, Wallet, ethers } from 'ethers'; +import { ClaimInfo, CONTEXT_SIGNER, Test_BlockchainAnchor } from "./const"; import { VeridaXPRewardClient } from "../src/blockchain/userApi"; -import { CONTRACT_ADDRESS } from "@verida/vda-common"; +import { getContractInfoForBlockchainAnchor } from "@verida/vda-common"; import EncryptionUtils from "@verida/encryption-utils"; const assert = require('assert') @@ -23,12 +22,12 @@ const createXPRewardClientAPI = (did:Wallet, configuration: any) => { return new VeridaXPRewardClient({ did: `did:vda:testnet:${did.address}`, signKey: did.privateKey, - network: EnvironmentType.TESTNET, + blockchainAnchor: Test_BlockchainAnchor, ...configuration }) } -describe("Verida XPRewardOwnerApi Test in read/write mode", () => { +describe("Verida XPRewardClientApi Test in read/write mode", () => { let xpRewardClientApi: VeridaXPRewardClient; let tokenAPI: ERC20Manager; @@ -37,11 +36,19 @@ describe("Verida XPRewardOwnerApi Test in read/write mode", () => { const xpAmountWithUniqueId = 200; before(async () => { - await addInitialData(configuration); + const ownerPrivateKey = process.env.OWNER_PRIVATE_KEY; + if (ownerPrivateKey !== undefined) { + const ownerConfiguration = getBlockchainAPIConfiguration(ownerPrivateKey); + await addInitialData(ownerConfiguration); + } + xpRewardClientApi = createXPRewardClientAPI(new Wallet(userDID.privateKey), configuration); const TOKEN_ADDRESS = await xpRewardClientApi.getTokenAddress(); - const rewardXPContractAddress = CONTRACT_ADDRESS["VDAXPReward"].testnet!; + const rewardXPContractAddress = getContractInfoForBlockchainAnchor(Test_BlockchainAnchor, "xpReward").address; + + // console.log("Token: ", TOKEN_ADDRESS); + // console.log("xpReward:", rewardXPContractAddress); // Create tokenAPI for claiming test tokenAPI = new ERC20Manager( @@ -74,7 +81,7 @@ describe("Verida XPRewardOwnerApi Test in read/write mode", () => { it("Get rate denominator",async () => { const response = await xpRewardClientApi.getRateDenominator(); - assert.ok(response && response > 0, 'Get the result'); + assert.ok(response && BigNumber.from(response.toString()).gt(0), 'Get the result'); }) it("Get conversion rate",async () => { @@ -84,13 +91,16 @@ describe("Verida XPRewardOwnerApi Test in read/write mode", () => { }) describe("Claim XP Reward", () => { + const contextSigner = new Wallet(CONTEXT_SIGNER.privateKey); + const claimData: ClaimInfo = { typeId: `type-${new Date().getTime()}`, uniqueId: ``, issueYear: 0, issueMonth: 0, xp: 50n, - signature: '' + signature: '', + proof: '' } const getProof = (didWallet:Wallet, data: ClaimInfo, trustedSigner: Wallet) => { @@ -98,8 +108,13 @@ describe("Verida XPRewardOwnerApi Test in read/write mode", () => { ['address', 'string', 'uint16', 'uint8', 'uint'], [didWallet.address, `${data.typeId}${data.uniqueId}`, data.issueYear, data.issueMonth, data.xp] ); - const privateKeyArray = new Uint8Array(Buffer.from(trustedSigner.privateKey.slice(2), 'hex')) - return EncryptionUtils.signData(rawMsg, privateKeyArray); + let privateKeyArray = new Uint8Array(Buffer.from(contextSigner.privateKey.slice(2), 'hex')) + const signature = EncryptionUtils.signData(rawMsg, privateKeyArray); + + const proofMsg = `${trustedSigner.address}${contextSigner.address}`.toLowerCase(); + privateKeyArray = new Uint8Array(Buffer.from(trustedSigner.privateKey.slice(2), 'hex')); + const proof = EncryptionUtils.signData(proofMsg, privateKeyArray); + return [ signature, proof ]; } before(async () => { @@ -113,7 +128,7 @@ describe("Verida XPRewardOwnerApi Test in read/write mode", () => { it("Claim without `uniqueId`", async () => { claimData.xp = BigInt(xpAmount); claimData.uniqueId = ''; - claimData.signature = getProof(userDID, claimData, new Wallet(TRUSTED_SIGNER.privateKey)); + [ claimData.signature, claimData.proof ] = getProof(userDID, claimData, new Wallet(TRUSTED_SIGNER.privateKey)); const orgBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); await xpRewardClientApi.claimXPReward(RECIPIENT_WALLET.address, [claimData]) @@ -127,7 +142,7 @@ describe("Verida XPRewardOwnerApi Test in read/write mode", () => { claimData.typeId = `${claimData.typeId}-1` claimData.uniqueId = 'uniqueid-1'; claimData.xp = BigInt(xpAmount); - claimData.signature = getProof(userDID, claimData, new Wallet(TRUSTED_SIGNER.privateKey)); + [ claimData.signature, claimData.proof ] = getProof(userDID, claimData, new Wallet(TRUSTED_SIGNER.privateKey)); const orgBalance = await tokenAPI.balanceOf(RECIPIENT_WALLET.address); await xpRewardClientApi.claimXPReward(RECIPIENT_WALLET.address, [claimData]) From 3440fa907b970ac25ffb0521c2a26c5e2b341439 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 30 Jul 2024 16:44:47 +0900 Subject: [PATCH 12/12] [vda-xp-reward-client] Minor updates --- .../src/blockchain/userApi.ts | 72 ------------------- .../vda-xp-reward-client/test/owner.test.ts | 4 +- .../test/user_read.test.ts | 15 ---- 3 files changed, 2 insertions(+), 89 deletions(-) diff --git a/packages/vda-xp-reward-client/src/blockchain/userApi.ts b/packages/vda-xp-reward-client/src/blockchain/userApi.ts index ee65bf1e..c5de85be 100644 --- a/packages/vda-xp-reward-client/src/blockchain/userApi.ts +++ b/packages/vda-xp-reward-client/src/blockchain/userApi.ts @@ -1,21 +1,8 @@ -<<<<<<< HEAD import { VdaClientConfig, Web3SelfTransactionConfig } from '@verida/types' import { ethers, BytesLike, BigNumber } from "ethers"; import { getContractInfoForBlockchainAnchor, getVeridaSign } from "@verida/vda-common"; import { VeridaClientBase } from "@verida/vda-client-base"; import { VeridaTokenClient } from '@verida/vda-token-client'; -======= -import { - getVeridaContract, - VeridaContract -} from "@verida/web3" -import { Web3SelfTransactionConfig, VdaClientConfig } from '@verida/types' -import { ethers, Contract, BytesLike } from "ethers"; -import { getContractInfoForNetwork, RPC_URLS, getVeridaSign, getVeridaSignWithNonce } from "@verida/vda-common"; -import { JsonRpcProvider } from '@ethersproject/providers'; -import { explodeDID } from '@verida/helpers' -import EncryptionUtils from "@verida/encryption-utils"; ->>>>>>> 3b0946c30b18bb8815a5a89aeac04b595b2d6beb /** * This is the claim information @@ -40,62 +27,10 @@ export interface ClaimXPInfo { /** * This is a client class that interacts with the `VDAXPReward` contract of Verida */ -<<<<<<< HEAD export class VeridaXPRewardClient extends VeridaClientBase{ public constructor(config: VdaClientConfig) { super(config, "xpReward"); -======= -export class VeridaXPRewardClient { - - protected config: VdaClientConfig - protected network: string - protected didAddress?: string - - protected vdaWeb3Client? : VeridaContract - - protected readOnly: boolean - protected contract?: ethers.Contract - - public constructor(config: VdaClientConfig) { - if (!config.callType) { - config.callType = 'web3' - } - - this.config = config - this.readOnly = true - if (!config.web3Options) { - config.web3Options = {} - } - - this.network = config.network - - if (config.callType == 'web3' && !(config.web3Options).rpcUrl) { - ( config.web3Options).rpcUrl = RPC_URLS[this.network] - } - - const contractInfo = getContractInfoForNetwork("VDAXPReward", this.network) - - if (config.did) { - this.readOnly = false - const { address } = explodeDID(config.did) - this.didAddress = address.toLowerCase() - - this.vdaWeb3Client = getVeridaContract( - config.callType, - {...contractInfo, - ...config.web3Options}) - } else { - let rpcUrl = (config.web3Options).rpcUrl - if (!rpcUrl) { - rpcUrl = RPC_URLS[this.network] - } - - const provider = new JsonRpcProvider(rpcUrl) - - this.contract = new Contract(contractInfo.address, contractInfo.abi.abi, provider) - } ->>>>>>> 3b0946c30b18bb8815a5a89aeac04b595b2d6beb } /** @@ -223,17 +158,12 @@ export class VeridaXPRewardClient { claims, requestSignature, requestProof -<<<<<<< HEAD ); -======= - ) ->>>>>>> 3b0946c30b18bb8815a5a89aeac04b595b2d6beb if (response.success !== true) { throw new Error(`Failed to claim xp: (${response.reason})`) } } -<<<<<<< HEAD /** * Deposit verida token to the `XPRewardContract` for `claimXPReward()` function calls @@ -270,6 +200,4 @@ export class VeridaXPRewardClient { console.log("Errr"); } } -======= ->>>>>>> 3b0946c30b18bb8815a5a89aeac04b595b2d6beb } \ No newline at end of file diff --git a/packages/vda-xp-reward-client/test/owner.test.ts b/packages/vda-xp-reward-client/test/owner.test.ts index 26642a04..29987bf4 100644 --- a/packages/vda-xp-reward-client/test/owner.test.ts +++ b/packages/vda-xp-reward-client/test/owner.test.ts @@ -43,7 +43,7 @@ describe("Verida XPRewardOwnerApi Test", () => { describe("Trusted signer test", () => { const newSigner = Wallet.createRandom(); - it.only("Add a trusted signer",async () => { + it("Add a trusted signer",async () => { let response = await ownerApi.isTrustedSigner(newSigner.address); assert.equal(response, false); @@ -53,7 +53,7 @@ describe("Verida XPRewardOwnerApi Test", () => { assert.equal(response, true); }) - it.only("Remove a trusted signer",async () => { + it("Remove a trusted signer",async () => { await ownerApi.removeTrustedSigner(newSigner.address); const response = await ownerApi.isTrustedSigner(newSigner.address); diff --git a/packages/vda-xp-reward-client/test/user_read.test.ts b/packages/vda-xp-reward-client/test/user_read.test.ts index fbc82e48..142e32be 100644 --- a/packages/vda-xp-reward-client/test/user_read.test.ts +++ b/packages/vda-xp-reward-client/test/user_read.test.ts @@ -1,26 +1,11 @@ require('dotenv').config(); -<<<<<<< HEAD import { getBlockchainAPIConfiguration } from "@verida/vda-common-test" import { addInitialData } from "./helpers"; import { VeridaXPRewardClient } from "../src/blockchain/userApi"; import { Test_BlockchainAnchor } from "./const"; -======= -import { DID_LIST, getBlockchainAPIConfiguration, ERC20Manager } from "@verida/vda-common-test" -import { EnvironmentType } from "@verida/types"; -import { addInitialData } from "./helpers"; -import { Wallet } from 'ethers'; -import { VeridaXPRewardClient } from "../src/blockchain/userApi"; ->>>>>>> 3b0946c30b18bb8815a5a89aeac04b595b2d6beb const assert = require('assert') -const privateKey = process.env.PRIVATE_KEY -if (!privateKey) { - throw new Error('No PRIVATE_KEY in the env file'); -} -const configuration = getBlockchainAPIConfiguration(privateKey); - - const createXPRewardClientAPI = () => { return new VeridaXPRewardClient({ blockchainAnchor: Test_BlockchainAnchor