diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index a1041f6..c2aa8a2 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -20,3 +20,4 @@ | Stargate S\*USDT | [task](https://app.gelato.network/functions/task/0x37ab785e9a1200fb8bac63b431e36da085b177841b24aa0dfab0a4981122da0a:1) | | GM Strategy Harvester | [task](https://app.gelato.network/functions/task/0x40d7aadde626b52e7df27bcab3f92c42faf3f137d50fc98dffc79d20c9119314:42161) | | Reward Distributor | [task](https://app.gelato.network/functions/task/0x1b62e611e8e3d87ec8c7ced57230d341802c0ac6c611afb9a9fb4a3c53dc6ac1:42161) | +| MagicUSD0pp Claimer | [task](https://app.gelato.network/functions/task/0x746fd28a88217d74f693dd3ec738ce699a174b01a875bce3c023262020048d3e:1) | diff --git a/package.json b/package.json index e1c8358..ce45fd7 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "create-task:gm": "hardhat run ./scripts/create-gm-task.ts --network ethereum", "create-task:spell-staking": "hardhat run ./scripts/create-spell-staking-task.ts", "create-task:magiclvl": "hardhat run ./scripts/create-magiclvl-task.ts --network anvil", + "create-task:magicusd0pp": "hardhat run ./scripts/create-magicusd0pp-task.ts --network ethereum", "create-task:process-locks": "hardhat run ./scripts/create-process-locks-task.ts --network arbitrum", "create-task:spell-swapper": "hardhat run ./scripts/create-spell-swapper-task.ts --network ethereum", "create-task:negative-interests": "hardhat run ./scripts/create-negative-interests-task.ts --network anvil", @@ -35,6 +36,7 @@ "run-task:spell-staking:kava": "hardhat w3f-run spell-staking --logs --network kava", "run-task:gm": "hardhat w3f-run gm --logs --network arbitrum", "run-task:magiclvl": "hardhat w3f-run magiclvl --logs --network bsc", + "run-task:magicusd0pp": "hardhat w3f-run magicusd0pp --logs --network ethereum", "run-task:process-locks": "concurrently --kill-others-on-fail -m 1 bun:run-task:process-locks:*", "run-task:process-locks:arb-msr": "cp web3-functions/process-locks/userArgs.arb-msr.json web3-functions/process-locks/userArgs.json && hardhat w3f-run process-locks --logs --network arbitrum", "run-task:process-locks:blast-founders": "cp web3-functions/process-locks/userArgs.blast-founders.json web3-functions/process-locks/userArgs.json && hardhat w3f-run process-locks --logs --network arbitrum", diff --git a/scripts/create-magicusd0pp-task.ts b/scripts/create-magicusd0pp-task.ts new file mode 100644 index 0000000..cbc15e7 --- /dev/null +++ b/scripts/create-magicusd0pp-task.ts @@ -0,0 +1,52 @@ +import { AutomateSDK, TriggerType } from "@gelatonetwork/automate-sdk"; +import hre from "hardhat"; +import { DEVOPS_SAFE } from "../utils/constants"; + +const { ethers, w3f } = hre; + +const THIRTY_SECONDS = 30 * 1000; + +const main = async () => { + const magicUsd0pp = w3f.get("magicusd0pp"); + + const [deployer] = await ethers.getSigners(); + const chainId = (await ethers.provider.getNetwork()).chainId; + + const automate = new AutomateSDK(chainId, deployer); + + // Deploy Web3Function on IPFS + console.log("Deploying Web3Function on IPFS..."); + const cid = await magicUsd0pp.deploy(); + console.log(`Web3Function IPFS CID: ${cid}`); + + const { tx } = await automate.prepareBatchExecTask( + { + name: "MagicUSD0pp Off-Chain Distribution Claimer", + web3FunctionHash: cid, + trigger: { + type: TriggerType.TIME, + interval: THIRTY_SECONDS, + }, + web3FunctionArgs: { + execAddress: "0x75cC0C0DDD2Ccafe6EC415bE686267588011E36A", + usualApiEndpoint: "https://app.usual.money/api/rewards", + magicUsd0ppAddress: "0x73075fD1522893D9dC922991542f98F08F2c1C99", + }, + }, + {}, + DEVOPS_SAFE, + ); + console.log(`to: ${tx.to}`); + console.log(tx.data); + console.log("------------------"); + console.log(); +}; + +main() + .then(() => { + process.exit(); + }) + .catch((err) => { + console.error("Error:", err.message); + process.exit(1); + }); diff --git a/web3-functions/magicusd0pp/index.ts b/web3-functions/magicusd0pp/index.ts new file mode 100644 index 0000000..0860ddd --- /dev/null +++ b/web3-functions/magicusd0pp/index.ts @@ -0,0 +1,93 @@ +import { + Web3Function, + type Web3FunctionContext, + Web3FunctionResult, +} from "@gelatonetwork/web3-functions-sdk"; +import ky from "ky"; +import { type Address, type Hex, encodeFunctionData, parseAbi } from "viem"; +import { createJsonRpcPublicClient } from "../../utils/viem"; + +const DISTRIBUTION_ABI = parseAbi([ + "function getOffChainDistributionData() external view returns (uint256 timestamp, bytes32 merkleRoot)", + "function getOffChainTokensClaimed(address account) external view returns (uint256)", + "function claimOffChainDistribution(address account, uint256 amount, bytes32[] calldata proof) external", +]); + +type MagicUsd0ppUserArgs = { + execAddress: Address; + usualApiEndpoint: string; + magicUsd0ppAddress: Address; +}; + +type OffChainDistribution = { + blockHash: Hex; + merkleRoot: Hex; + timestamp: string; + value: `${bigint}`; + merkleProof: Array; +}; + +Web3Function.onRun( + async ({ userArgs, multiChainProvider }: Web3FunctionContext) => { + const { execAddress, usualApiEndpoint, magicUsd0ppAddress } = + userArgs as MagicUsd0ppUserArgs; + + const client = createJsonRpcPublicClient(multiChainProvider.default()); + const usualRewardsApi = ky.extend({ + prefixUrl: new URL("rewards", new URL(usualApiEndpoint)), + }); + + const [[, currentMerkleRoot], claimedTokens, distributions] = + await Promise.all([ + client.readContract({ + abi: DISTRIBUTION_ABI, + address: execAddress, + functionName: "getOffChainDistributionData", + }), + client.readContract({ + abi: DISTRIBUTION_ABI, + address: execAddress, + functionName: "getOffChainTokensClaimed", + args: [magicUsd0ppAddress], + }), + usualRewardsApi + .get(magicUsd0ppAddress) + .json>(), + ]); + + if (distributions.length === 0) { + return { canExec: false, message: "No distributions" }; + } + + const currentDistribution = distributions.find( + ({ merkleRoot }) => + merkleRoot.toLowerCase() === currentMerkleRoot.toLowerCase(), + ); + + if (currentDistribution === undefined) { + return { canExec: false, message: "No matching distribution" }; + } + + if (claimedTokens === BigInt(currentDistribution.value)) { + return { canExec: false, message: "Already claimed latest distribution" }; + } + + return { + canExec: true, + callData: [ + { + to: execAddress, + data: encodeFunctionData({ + abi: DISTRIBUTION_ABI, + functionName: "claimOffChainDistribution", + args: [ + magicUsd0ppAddress, + BigInt(currentDistribution.value), + currentDistribution.merkleProof, + ], + }), + }, + ], + }; + }, +); diff --git a/web3-functions/magicusd0pp/schema.json b/web3-functions/magicusd0pp/schema.json new file mode 100644 index 0000000..1f24c38 --- /dev/null +++ b/web3-functions/magicusd0pp/schema.json @@ -0,0 +1,11 @@ +{ + "web3FunctionVersion": "2.0.0", + "runtime": "js-1.0", + "memory": 128, + "timeout": 30, + "userArgs": { + "execAddress": "string", + "usualApiEndpoint": "string", + "magicUsd0ppAddress": "string" + } +} diff --git a/web3-functions/magicusd0pp/storage.json b/web3-functions/magicusd0pp/storage.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/web3-functions/magicusd0pp/storage.json @@ -0,0 +1 @@ +{} diff --git a/web3-functions/magicusd0pp/userArgs.json b/web3-functions/magicusd0pp/userArgs.json new file mode 100644 index 0000000..21827cb --- /dev/null +++ b/web3-functions/magicusd0pp/userArgs.json @@ -0,0 +1,5 @@ +{ + "execAddress": "0x75cC0C0DDD2Ccafe6EC415bE686267588011E36A", + "usualApiEndpoint": "https://app.usual.money/api/rewards", + "magicUsd0ppAddress": "0x73075fD1522893D9dC922991542f98F08F2c1C99" +}