From 72f8144485812ea3b93512fb76743552ba4e0f7f Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Wed, 23 Apr 2025 19:06:58 +0800 Subject: [PATCH 1/9] =?UTF-8?q?Implemented=20full=20TokenVesting=20contrac?= =?UTF-8?q?t=20with=20complete=20vesting=20logic,=20mappings,=20whitelist?= =?UTF-8?q?=20management,=20pause=20support,=20and=20removed=20every=20?= =?UTF-8?q?=E2=80=9CTODO:=20=E2=80=9D=20prefix=20while=20retaining=20origi?= =?UTF-8?q?nal=20comments.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contracts/TokenVesting.sol | 119 ++++++++++++++++-- 1 file changed, 106 insertions(+), 13 deletions(-) diff --git a/challenge-1-vesting/contracts/TokenVesting.sol b/challenge-1-vesting/contracts/TokenVesting.sol index 43d4c3a..3c388ed 100644 --- a/challenge-1-vesting/contracts/TokenVesting.sol +++ b/challenge-1-vesting/contracts/TokenVesting.sol @@ -16,8 +16,6 @@ Bonus challenges: - Add support for multiple token types - Implement a whitelist for beneficiaries - Add emergency pause functionality - -Here's your starter code: */ // SPDX-License-Identifier: MIT @@ -30,18 +28,26 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard { struct VestingSchedule { - // TODO: Define the vesting schedule struct + // Define the vesting schedule struct + uint256 totalAmount; + uint256 startTime; + uint256 cliffDuration; + uint256 vestingDuration; + uint256 amountClaimed; + bool revoked; } // Token being vested - // TODO: Add state variables - + // Add state variables + IERC20 public immutable token; // Mapping from beneficiary to vesting schedule - // TODO: Add state variables + // Add state variables + mapping(address => VestingSchedule) public vestingSchedules; // Whitelist of beneficiaries - // TODO: Add state variables + // Add state variables + mapping(address => bool) public whitelist; // Events event VestingScheduleCreated(address indexed beneficiary, uint256 amount); @@ -51,8 +57,9 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard { event BeneficiaryRemovedFromWhitelist(address indexed beneficiary); constructor(address tokenAddress) { - // TODO: Initialize the contract - + // Initialize the contract + require(tokenAddress != address(0), "Invalid token address"); + token = IERC20(tokenAddress); } // Modifier to check if beneficiary is whitelisted @@ -79,22 +86,95 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard { uint256 vestingDuration, uint256 startTime ) external onlyOwner onlyWhitelisted(beneficiary) whenNotPaused { - // TODO: Implement vesting schedule creation + // Implement vesting schedule creation + require(amount > 0, "Amount must be > 0"); + require(vestingDuration > 0, "Vesting duration must be > 0"); + require(cliffDuration <= vestingDuration, "Cliff > duration"); + VestingSchedule storage existing = vestingSchedules[beneficiary]; + require( + existing.totalAmount == 0 || existing.revoked, + "Schedule exists" + ); + + require( + token.transferFrom(_msgSender(), address(this), amount), + "Token transfer failed" + ); + + vestingSchedules[beneficiary] = VestingSchedule({ + totalAmount: amount, + startTime: startTime, + cliffDuration: cliffDuration, + vestingDuration: vestingDuration, + amountClaimed: 0, + revoked: false + }); + + emit VestingScheduleCreated(beneficiary, amount); } function calculateVestedAmount( address beneficiary ) public view returns (uint256) { - // TODO: Implement vested amount calculation + // Implement vested amount calculation + VestingSchedule memory schedule = vestingSchedules[beneficiary]; + if (schedule.totalAmount == 0) return 0; + + uint256 currentTime = block.timestamp; + + if (currentTime < schedule.startTime + schedule.cliffDuration) { + return 0; + } + + uint256 elapsed = currentTime - schedule.startTime; + uint256 vested; + + if (elapsed >= schedule.vestingDuration) { + vested = schedule.totalAmount; + } else { + vested = + (schedule.totalAmount * elapsed) / + schedule.vestingDuration; + } + + if (vested <= schedule.amountClaimed) { + return 0; + } + + return vested - schedule.amountClaimed; } function claimVestedTokens() external nonReentrant whenNotPaused { - // TODO: Implement token claiming + // Implement token claiming + uint256 claimable = calculateVestedAmount(_msgSender()); + require(claimable > 0, "No tokens to claim"); + + VestingSchedule storage schedule = vestingSchedules[_msgSender()]; + schedule.amountClaimed += claimable; + + require(token.transfer(_msgSender(), claimable), "Transfer failed"); + emit TokensClaimed(_msgSender(), claimable); } function revokeVesting(address beneficiary) external onlyOwner { - // TODO: Implement vesting revocation + // Implement vesting revocation + VestingSchedule storage schedule = vestingSchedules[beneficiary]; + require(schedule.totalAmount > 0, "No schedule"); + require(!schedule.revoked, "Already revoked"); + + uint256 vestedSoFar = _computeTotalVested(schedule); + uint256 unvested = schedule.totalAmount - vestedSoFar; + schedule.revoked = true; + schedule.vestingDuration = block.timestamp > schedule.startTime + ? block.timestamp - schedule.startTime + : 0; + + if (unvested > 0) { + require(token.transfer(owner(), unvested), "Return failed"); + } + + emit VestingRevoked(beneficiary); } function pause() external onlyOwner { @@ -104,6 +184,19 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard { function unpause() external onlyOwner { _unpause(); } + + function _computeTotalVested( + VestingSchedule memory schedule + ) internal view returns (uint256) { + if (block.timestamp < schedule.startTime + schedule.cliffDuration) { + return 0; + } + uint256 elapsed = block.timestamp - schedule.startTime; + if (elapsed >= schedule.vestingDuration) { + return schedule.totalAmount; + } + return (schedule.totalAmount * elapsed) / schedule.vestingDuration; + } } /* From 12a5f7c263d06147fa62bcd9b3900941b7586d42 Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Wed, 23 Apr 2025 19:12:16 +0800 Subject: [PATCH 2/9] Fix vesting revocation logic to correctly adjust schedule totals and prevent overclaiming after revocation. --- challenge-1-vesting/contracts/TokenVesting.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/challenge-1-vesting/contracts/TokenVesting.sol b/challenge-1-vesting/contracts/TokenVesting.sol index 3c388ed..fabb1ec 100644 --- a/challenge-1-vesting/contracts/TokenVesting.sol +++ b/challenge-1-vesting/contracts/TokenVesting.sol @@ -165,7 +165,10 @@ contract TokenVesting is Ownable(msg.sender), Pausable, ReentrancyGuard { uint256 vestedSoFar = _computeTotalVested(schedule); uint256 unvested = schedule.totalAmount - vestedSoFar; + // Mark as revoked and update schedule so that only the vested portion + // remains claimable by the beneficiary. schedule.revoked = true; + schedule.totalAmount = vestedSoFar; schedule.vestingDuration = block.timestamp > schedule.startTime ? block.timestamp - schedule.startTime : 0; From 36492d712f7b812e1f84afd690c71bb527472534 Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Wed, 23 Apr 2025 19:19:01 +0800 Subject: [PATCH 3/9] add Jade Laurence Empleo (syntaxsurge) as a self-employed participant in challenge-1 README --- challenge-1-vesting/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/challenge-1-vesting/README.md b/challenge-1-vesting/README.md index 9cc7a2c..4b3e267 100644 --- a/challenge-1-vesting/README.md +++ b/challenge-1-vesting/README.md @@ -10,7 +10,7 @@ Add your information to the below list to officially participate in the workshop | Emoji | Name | Github Username | Occupations | | ----- | ---- | ------------------------------------- | ----------- | -| 🎅 | Ippo | [NTP-996](https://github.com/NTP-996) | DevRel | +| ⚡ | Jade Laurence Empleo | [syntaxsurge](https://github.com/syntaxsurge) | Self-employed | ## 💻 Local development environment setup @@ -124,4 +124,4 @@ At the time writing this challenge, Hardhat haven't support deployment on Westen - [ ] ⭐ Star Open Guild repository
- [ ] 👥 Follow OpenGuild Lab Github
- [ ] 💬 Join OpenGuild Discord
-- [ ] 📝 Submit the proof-of-work (your challenge repository) to OpenGuild Discord
+- [ ] 📝 Submit the proof-of-work (your challenge repository) to OpenGuild Discord
\ No newline at end of file From 3569f71ef4d51cd6738b242d0523706e6c3e3063 Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Wed, 23 Apr 2025 19:21:26 +0800 Subject: [PATCH 4/9] Check TODO list for How to claim the bounty --- challenge-1-vesting/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/challenge-1-vesting/README.md b/challenge-1-vesting/README.md index 4b3e267..fc04a05 100644 --- a/challenge-1-vesting/README.md +++ b/challenge-1-vesting/README.md @@ -120,8 +120,8 @@ At the time writing this challenge, Hardhat haven't support deployment on Westen ### 🙋‍♂️ How to claim the bounty? -- [ ] Complete the challenge on your fork repository
-- [ ] ⭐ Star Open Guild repository
-- [ ] 👥 Follow OpenGuild Lab Github
-- [ ] 💬 Join OpenGuild Discord
-- [ ] 📝 Submit the proof-of-work (your challenge repository) to OpenGuild Discord
\ No newline at end of file +- [x] Complete the challenge on your fork repository
+- [x] ⭐ Star Open Guild repository
+- [x] 👥 Follow OpenGuild Lab Github
+- [x] 💬 Join OpenGuild Discord
+- [x] 📝 Submit the proof-of-work (your challenge repository) to OpenGuild Discord
\ No newline at end of file From 8671bf25761d059d7fb1048207acef22d59633be Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Wed, 23 Apr 2025 19:26:40 +0800 Subject: [PATCH 5/9] Add dotenv dependency to fix Hardhat config module resolution. --- challenge-2-yield-farm/package-lock.json | 15 ++++++++++++++- challenge-2-yield-farm/package.json | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/challenge-2-yield-farm/package-lock.json b/challenge-2-yield-farm/package-lock.json index 4cbdfb0..d60931b 100644 --- a/challenge-2-yield-farm/package-lock.json +++ b/challenge-2-yield-farm/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@openzeppelin/contracts": "^5.1.0" + "@openzeppelin/contracts": "^5.1.0", + "dotenv": "^16.5.0" }, "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^5.0.0", @@ -3383,6 +3384,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", diff --git a/challenge-2-yield-farm/package.json b/challenge-2-yield-farm/package.json index 5366ac8..1d307cb 100644 --- a/challenge-2-yield-farm/package.json +++ b/challenge-2-yield-farm/package.json @@ -17,6 +17,7 @@ "hardhat": "^2.22.17" }, "dependencies": { - "@openzeppelin/contracts": "^5.1.0" + "@openzeppelin/contracts": "^5.1.0", + "dotenv": "^16.5.0" } } From 019aa1946bd6fffc2fb283dbe739c7d6ff93e72c Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Wed, 23 Apr 2025 19:29:40 +0800 Subject: [PATCH 6/9] Implement YieldFarm logic with staking, rewards, boosts and remove TODO prefixes. --- challenge-2-yield-farm/contracts/yeild.sol | 111 +++++++++++++++++++-- 1 file changed, 100 insertions(+), 11 deletions(-) diff --git a/challenge-2-yield-farm/contracts/yeild.sol b/challenge-2-yield-farm/contracts/yeild.sol index 421496a..88dc445 100644 --- a/challenge-2-yield-farm/contracts/yeild.sol +++ b/challenge-2-yield-farm/contracts/yeild.sol @@ -57,7 +57,7 @@ contract YieldFarm is ReentrancyGuard, Ownable { event RewardsClaimed(address indexed user, uint256 amount); event EmergencyWithdrawn(address indexed user, uint256 amount); - // TODO: Implement the following functions + // Implement the following functions /** * @notice Initialize the contract with the LP token and reward token addresses @@ -70,9 +70,17 @@ contract YieldFarm is ReentrancyGuard, Ownable { address _rewardToken, uint256 _rewardRate ) Ownable(msg.sender) { - // TODO: Initialize contract state + // Initialize contract state + require(_lpToken != address(0) && _rewardToken != address(0), "Zero address"); + lpToken = IERC20(_lpToken); + rewardToken = IERC20(_rewardToken); + rewardRate = _rewardRate; + lastUpdateTime = block.timestamp; } + /** + * @dev Internal function to update global and user-specific reward accounting + */ function updateReward(address _user) internal { rewardPerTokenStored = rewardPerToken(); lastUpdateTime = block.timestamp; @@ -85,19 +93,30 @@ contract YieldFarm is ReentrancyGuard, Ownable { } function rewardPerToken() public view returns (uint256) { - // TODO: Implement pending rewards calculation + // Implement pending rewards calculation // Requirements: // 1. Calculate rewards since last update // 2. Apply boost multiplier // 3. Return total pending rewards + if (totalStaked == 0) { + return rewardPerTokenStored; + } + uint256 timeElapsed = block.timestamp - lastUpdateTime; + uint256 newRewards = (timeElapsed * rewardRate * 1e18) / totalStaked; + return rewardPerTokenStored + newRewards; } function earned(address _user) public view returns (uint256) { - // TODO: Implement pending rewards calculation + // Implement pending rewards calculation // Requirements: // 1. Calculate rewards since last update // 2. Apply boost multiplier // 3. Return total pending rewards + UserInfo storage user = userInfo[_user]; + uint256 accumulated = (user.amount * rewardPerToken()) / 1e18; + uint256 pending = accumulated - user.rewardDebt + user.pendingRewards; + uint256 boost = calculateBoostMultiplier(_user); + return (pending * boost) / 100; } /** @@ -105,12 +124,26 @@ contract YieldFarm is ReentrancyGuard, Ownable { * @param _amount Amount of LP tokens to stake */ function stake(uint256 _amount) external nonReentrant { - // TODO: Implement staking logic + // Implement staking logic // Requirements: // 1. Update rewards // 2. Transfer LP tokens from user // 3. Update user info and total staked amount // 4. Emit Staked event + require(_amount > 0, "Cannot stake 0"); + updateReward(msg.sender); + + lpToken.transferFrom(msg.sender, address(this), _amount); + + UserInfo storage user = userInfo[msg.sender]; + user.amount += _amount; + totalStaked += _amount; + + if (user.startTime == 0) { + user.startTime = block.timestamp; + } + + emit Staked(msg.sender, _amount); } /** @@ -118,35 +151,74 @@ contract YieldFarm is ReentrancyGuard, Ownable { * @param _amount Amount of LP tokens to withdraw */ function withdraw(uint256 _amount) external nonReentrant { - // TODO: Implement withdrawal logic + // Implement withdrawal logic // Requirements: // 1. Update rewards // 2. Transfer LP tokens to user // 3. Update user info and total staked amount // 4. Emit Withdrawn event + require(_amount > 0, "Cannot withdraw 0"); + UserInfo storage user = userInfo[msg.sender]; + require(user.amount >= _amount, "Insufficient balance"); + + updateReward(msg.sender); + + user.amount -= _amount; + totalStaked -= _amount; + lpToken.transfer(msg.sender, _amount); + + if (user.amount == 0) { + user.startTime = 0; + } + + emit Withdrawn(msg.sender, _amount); } /** * @notice Claim pending rewards */ function claimRewards() external nonReentrant { - // TODO: Implement reward claiming logic + // Implement reward claiming logic // Requirements: // 1. Calculate pending rewards with boost multiplier // 2. Transfer rewards to user // 3. Update user reward debt // 4. Emit RewardsClaimed event + updateReward(msg.sender); + + UserInfo storage user = userInfo[msg.sender]; + uint256 reward = user.pendingRewards; + require(reward > 0, "No rewards"); + + user.pendingRewards = 0; + rewardToken.transfer(msg.sender, reward); + + emit RewardsClaimed(msg.sender, reward); } /** * @notice Emergency withdraw without caring about rewards */ function emergencyWithdraw() external nonReentrant { - // TODO: Implement emergency withdrawal + // Implement emergency withdrawal // Requirements: // 1. Transfer all LP tokens back to user // 2. Reset user info // 3. Emit EmergencyWithdrawn event + UserInfo storage user = userInfo[msg.sender]; + uint256 amount = user.amount; + require(amount > 0, "Nothing to withdraw"); + + totalStaked -= amount; + + user.amount = 0; + user.rewardDebt = 0; + user.pendingRewards = 0; + user.startTime = 0; + + lpToken.transfer(msg.sender, amount); + + emit EmergencyWithdrawn(msg.sender, amount); } /** @@ -157,10 +229,25 @@ contract YieldFarm is ReentrancyGuard, Ownable { function calculateBoostMultiplier( address _user ) public view returns (uint256) { - // TODO: Implement boost multiplier calculation + // Implement boost multiplier calculation // Requirements: // 1. Calculate staking duration // 2. Return appropriate multiplier based on duration thresholds + UserInfo storage user = userInfo[_user]; + if (user.startTime == 0) { + return 100; + } + + uint256 duration = block.timestamp - user.startTime; + + if (duration >= BOOST_THRESHOLD_3) { + return 200; + } else if (duration >= BOOST_THRESHOLD_2) { + return 150; + } else if (duration >= BOOST_THRESHOLD_1) { + return 125; + } + return 100; } /** @@ -168,10 +255,12 @@ contract YieldFarm is ReentrancyGuard, Ownable { * @param _newRate New reward rate per second */ function updateRewardRate(uint256 _newRate) external onlyOwner { - // TODO: Implement reward rate update logic + // Implement reward rate update logic // Requirements: // 1. Update rewards before changing rate // 2. Set new reward rate + updateReward(address(0)); + rewardRate = _newRate; } /** @@ -182,4 +271,4 @@ contract YieldFarm is ReentrancyGuard, Ownable { function pendingRewards(address _user) external view returns (uint256) { return earned(_user); } -} +} \ No newline at end of file From aba6c9870b382ec156bf208f4396dbd56745f373 Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Thu, 24 Apr 2025 01:31:18 +0800 Subject: [PATCH 7/9] Build frontend for challenge #3 --- .DS_Store | Bin 0 -> 8196 bytes challenge-1-vesting/.DS_Store | Bin 0 -> 6148 bytes challenge-1-vesting/README.md | 2 +- challenge-3-frontend/app/layout.tsx | 26 +- .../app/mint-redeem-lst-bifrost/page.tsx | 28 +- challenge-3-frontend/app/page.tsx | 441 +- challenge-3-frontend/app/providers.tsx | 19 +- .../app/send-transaction/page.tsx | 27 +- challenge-3-frontend/app/vesting/page.tsx | 41 + .../app/write-contract/page.tsx | 30 +- .../app/yield-farm/loader.tsx | 17 + challenge-3-frontend/app/yield-farm/page.tsx | 37 + .../components/create-wallet-dialog.tsx | 139 + .../components/mint-redeem-lst-bifrost.tsx | 633 +- challenge-3-frontend/components/navbar.tsx | 250 +- .../components/send-transaction.tsx | 278 +- .../components/sigpasskit.tsx | 556 +- .../components/transaction-status.tsx | 85 + challenge-3-frontend/components/ui/button.tsx | 4 +- challenge-3-frontend/components/ui/card.tsx | 59 + .../components/vesting/create-form.tsx | 303 + .../components/vesting/index.tsx | 13 + .../components/vesting/schedule-card.tsx | 272 + .../components/write-contract.tsx | 426 +- .../components/yield-farm/index.tsx | 107 + .../components/yield-farm/mint-lp-token.tsx | 98 + .../components/yield-farm/stake.tsx | 487 ++ .../components/yield-farm/withdraw.tsx | 446 ++ challenge-3-frontend/lib/abi.ts | 6100 ++++++++++------- challenge-3-frontend/lib/atoms.ts | 7 + challenge-3-frontend/lib/config.ts | 8 + challenge-3-frontend/package-lock.json | 703 ++ challenge-3-frontend/package.json | 1 + 33 files changed, 8017 insertions(+), 3626 deletions(-) create mode 100644 .DS_Store create mode 100644 challenge-1-vesting/.DS_Store create mode 100644 challenge-3-frontend/app/vesting/page.tsx create mode 100644 challenge-3-frontend/app/yield-farm/loader.tsx create mode 100644 challenge-3-frontend/app/yield-farm/page.tsx create mode 100644 challenge-3-frontend/components/create-wallet-dialog.tsx create mode 100644 challenge-3-frontend/components/transaction-status.tsx create mode 100644 challenge-3-frontend/components/ui/card.tsx create mode 100644 challenge-3-frontend/components/vesting/create-form.tsx create mode 100644 challenge-3-frontend/components/vesting/index.tsx create mode 100644 challenge-3-frontend/components/vesting/schedule-card.tsx create mode 100644 challenge-3-frontend/components/yield-farm/index.tsx create mode 100644 challenge-3-frontend/components/yield-farm/mint-lp-token.tsx create mode 100644 challenge-3-frontend/components/yield-farm/stake.tsx create mode 100644 challenge-3-frontend/components/yield-farm/withdraw.tsx create mode 100644 challenge-3-frontend/lib/atoms.ts create mode 100644 challenge-3-frontend/lib/config.ts diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..42f9a04bfc87f2d4eb9450da68fc3483cf0f1b1b GIT binary patch literal 8196 zcmeHMU2GIp6u#fI&{;dmw53`Ywq0EnqHSoQK(zt4?SfznMYccN7AUj3BORH}l%3fv zP#PP9Mg!3|f1gY=8l(82M13$3G5)?7LP!V?sEL{wO?)yjQR2CCX9+Df@&ds)H@WxB zIrp4<=bSy?y?b^UW9Tg8S{SQijLFnFRV!)uhsNi5zoJQjq?{YLJ&hjWM07_si?|7OLTkRLQ%{VvUAY1bIxR^Q?PTcZ<`Vm5o)9AlBubd)h&&&mbSL(#@JL#^SafIvDVhs>FJ1E z8(-PJV|1T2ZaYVXhX_sycJmyMk6pdHmRsx+?v^D>?w$|al_NJ*D#|Td|G>atdT4l5 zRST}M!?1MEEE-AOGu%;ikxTw>$vXD-yip{-2eXb<7`JJrDyv)B0x26EmX$IO3GHm& zv8?TmYkH<*52a1A8F40Kj z_@cV#9e3Z`yypH5T@QBmo>8l+YZlik$}mOQ_RL32Bi}!1=De{X-OUx_?+D{dfrAfaiy`L;c}>-A0;Hoejp!QM%l8+OO^ywN*JX&6X1lhps*NY0Hm z=1k8iP4vlh7N4SHuNHmT^Pk4=y((k_9G7$hw(U$;0Zj5XYo8<#7lS? zui|YS$2<4{pW{n>g|Bf27w|2TQa)df5+53~0Odo&Pn|hweTERZELyL`N`RdUS(bfo|R4o&1Q?xB2s}M}PvOYq1Rm%liuXIErD)X6W zGP+47%&GFV1H)N%f&IXKWq%MDDxsnlccKYv(2kAhMh~U`cI?0ic49YyfJ0y(#$jUu z2XTmCFpbCX6rRRW9K$mNg%=16915@Db-aN$@h;xO3A~SwaT2HSDLx}`e1miN9zWr? zIVrzwPResATxH4^Q+eBR?7d`B0?_Qmt_4>n7Oms;|Hk=$|GySnhiirr2qSQVL;&T< zRI-y^X1=3`*V-}ad#Llq^=1V1O=$4TaiV@XPV~|rhV&dGRc@0A=!~Ewp?2MW2)ORw RyuG4R))}ng5CmN2d-@3z)Pv~3s}*E%53e>Vr``D++hrQ*DvH3@$>jz zl8R#!Jc+pX;N_RRKMnb%fV zWS2NPlQIdbeh{9=fW(T`a;f%W)sU-%8fkg)D zdT3+)Kl%RsznDZLVt^RwoRBvV_L7KgP~fZl+jU|gy3GX*-T6hkbQ;$2WB ZU>Dc{bPbjo!2&`T0Yw8f#K5mI@BxlBO~3#E literal 0 HcmV?d00001 diff --git a/challenge-1-vesting/README.md b/challenge-1-vesting/README.md index fc04a05..06b5c88 100644 --- a/challenge-1-vesting/README.md +++ b/challenge-1-vesting/README.md @@ -10,7 +10,7 @@ Add your information to the below list to officially participate in the workshop | Emoji | Name | Github Username | Occupations | | ----- | ---- | ------------------------------------- | ----------- | -| ⚡ | Jade Laurence Empleo | [syntaxsurge](https://github.com/syntaxsurge) | Self-employed | +| ⚡ | Jade Laurence Empleo | [syntaxsurge](https://github.com/syntaxsurge) | Freelancer | ## 💻 Local development environment setup diff --git a/challenge-3-frontend/app/layout.tsx b/challenge-3-frontend/app/layout.tsx index db98252..14cf443 100644 --- a/challenge-3-frontend/app/layout.tsx +++ b/challenge-3-frontend/app/layout.tsx @@ -1,14 +1,15 @@ import type { Metadata } from "next"; import { Unbounded } from "next/font/google"; import "./globals.css"; -import '@rainbow-me/rainbowkit/styles.css'; -import { Providers } from '@/app/providers'; +import "@rainbow-me/rainbowkit/styles.css"; +import { Providers } from "@/app/providers"; +import Navbar from "@/components/navbar"; const unbounded = Unbounded({ - subsets: ['latin'], - weight: ['400', '700'], - display: 'swap', -}) + subsets: ["latin"], + weight: ["400", "700"], + display: "swap", +}); export const metadata: Metadata = { title: "DOT UI kit", @@ -21,16 +22,13 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - + + -
- {children} -
+ +
{children}
); -} +} \ No newline at end of file diff --git a/challenge-3-frontend/app/mint-redeem-lst-bifrost/page.tsx b/challenge-3-frontend/app/mint-redeem-lst-bifrost/page.tsx index aa53c21..d9f4a62 100644 --- a/challenge-3-frontend/app/mint-redeem-lst-bifrost/page.tsx +++ b/challenge-3-frontend/app/mint-redeem-lst-bifrost/page.tsx @@ -1,15 +1,29 @@ "use client"; + import MintRedeemLstBifrost from "@/components/mint-redeem-lst-bifrost"; -import SigpassKit from "@/components/sigpasskit"; -import Navbar from "@/components/navbar"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; export default function MintRedeemLstBifrostPage() { return ( -
- - -

Mint/Redeem LST Bifrost

+
+ + + + Mint / Redeem LST Bifrost + + + Swap supported assets for liquid staking tokens or redeem them anytime through + cross-chain Bifrost orders with seamless approvals and progress tracking. + + + +
); -} +} \ No newline at end of file diff --git a/challenge-3-frontend/app/page.tsx b/challenge-3-frontend/app/page.tsx index fb01185..b0d4408 100644 --- a/challenge-3-frontend/app/page.tsx +++ b/challenge-3-frontend/app/page.tsx @@ -1,110 +1,351 @@ -import Image from "next/image"; +"use client"; + import Link from "next/link"; +import { + ArrowRight, + Github, + Layers, + Shield, + Zap, + ChevronRight, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +/* -------------------------------------------------------------------------- */ +/* Page View */ +/* -------------------------------------------------------------------------- */ -export default function Home() { +export default function HomePage() { return ( -
-
- OpenGuild logo -

Get started by checking out the demos

-
    -
  1. - Wallet -
  2. -
  3. - Send transaction -
  4. -
  5. - Write contract -
  6. -
  7. - Mint/Redeem LST Bifrost -
  8. -
-
- - Vercel logomark - Deploy now - - + + + + + + + + + + + + + ); +} + +/* -------------------------------------------------------------------------- */ +/* Hero */ +/* -------------------------------------------------------------------------- */ + +function Hero() { + return ( +
+ {/* background blur rings */} +
+ ); +} + +/* -------------------------------------------------------------------------- */ +/* Ecosystem Statistics */ +/* -------------------------------------------------------------------------- */ + +function EcosystemStats() { + const stats = [ + { label: "Parachains", value: "50+" }, + { label: "Active Wallets", value: "1.2 M" }, + { label: "Daily TXs", value: "3.5 M" }, + { label: "Relay Chain TPS", value: "10k+" }, + ]; + + return ( +
+
+ {stats.map(({ label, value }) => ( +
+ {value} + {label} +
+ ))} +
+
+ ); +} + +/* -------------------------------------------------------------------------- */ +/* Feature Showcase */ +/* -------------------------------------------------------------------------- */ + +function FeatureShowcase() { + const features = [ + { + title: "Passkey Wallets", + emoji: "🔑", + description: + "Password-less SigpassKit lets users create secure, on-device wallets in seconds.", + }, + { + title: "One-click Staking", + emoji: "📈", + description: + "Stake LP tokens and watch rewards accrue with lightning-fast updates.", + }, + { + title: "Token Vesting", + emoji: "⏳", + description: + "Schedule releases with cliffs & durations—no back-end required.", + }, + { + title: "Gas-less Send", + emoji: "🚀", + description: + "Smart UX with pending, confirming & confirmed states out of the box.", + }, + { + title: "Contract Studio", + emoji: "📝", + description: + "Read & write any smart contract instantly through auto-generated forms.", + }, + { + title: "Responsive Design", + emoji: "💎", + description: + "Tailwind + Radix ensure crystal-clear visuals on every device.", + }, + ]; + + return ( +
+
+ + +
+ {features.map((f) => ( + + ))}
- +
+
+ ); +} + +function FeatureCard({ + title, + emoji, + description, +}: { + title: string; + emoji: string; + description: string; +}) { + return ( +
+ {emoji} +

{title}

+

{description}

); } + +/* -------------------------------------------------------------------------- */ +/* Why Polkadot */ +/* -------------------------------------------------------------------------- */ + +function WhyPolkadot() { + const perks = [ + { + icon: Layers, + title: "True Interoperability", + body: "Seamlessly connect multiple blockchains and unlock cross-chain applications.", + }, + { + icon: Shield, + title: "Shared Security", + body: "Relay-chain validators secure every parachain so you can focus on UX.", + }, + { + icon: Zap, + title: "Instant Upgradeability", + body: "Upgrade your runtime without hard forks and stay ahead of the curve.", + }, + ]; + + return ( +
+
+ +
+ {perks.map(({ icon: Icon, title, body }) => ( +
+ +

{title}

+

{body}

+
+ ))} +
+
+
+ ); +} + +/* -------------------------------------------------------------------------- */ +/* Get Started Timeline */ +/* -------------------------------------------------------------------------- */ + +function GetStartedSteps() { + const steps = [ + { + title: "Install", + body: "Clone the repo & run pnpm install to fetch dependencies.", + }, + { + title: "Configure", + body: "Edit app/providers.tsx with your WalletConnect ID.", + }, + { + title: "Ship", + body: "Deploy to Vercel, IPFS, or your favorite infra—zero extra setup.", + }, + ]; + + return ( +
+
+ +
    + {steps.map((s, i) => ( +
  1. + + {i + 1} + +
    {s.title}
    +

    +

  2. + ))} + {/* connecting line */} +
    +
+
+
+ ); +} + +/* -------------------------------------------------------------------------- */ +/* Call to Action */ +/* -------------------------------------------------------------------------- */ + +function CallToAction() { + return ( +
+
+

+ Ready to craft the next killer Polkadot DApp? +

+

+ Fork the kit, drop in your chain config, and ship to mainnet today. +

+ +
+ ); +} + +/* -------------------------------------------------------------------------- */ +/* Reusable Helpers */ +/* -------------------------------------------------------------------------- */ + +function SectionHeader({ + title, + subtitle, + invert = false, +}: { + title: string; + subtitle: string; + invert?: boolean; +}) { + return ( +
+

+ {title} +

+

+ {subtitle} +

+
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/app/providers.tsx b/challenge-3-frontend/app/providers.tsx index 91243fe..b7b16a3 100644 --- a/challenge-3-frontend/app/providers.tsx +++ b/challenge-3-frontend/app/providers.tsx @@ -20,7 +20,11 @@ import { defineChain } from 'viem'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { WagmiProvider, http, createConfig } from 'wagmi'; import { Provider as JotaiProvider } from 'jotai'; -// import according to docs +import { + LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + REWARD_TOKEN_CONTRACT_ADDRESS, + YIELD_FARMING_CONTRACT_ADDRESS, +} from "@/lib/config"; export const westendAssetHub = defineChain({ id: 420420421, @@ -40,9 +44,14 @@ export const westendAssetHub = defineChain({ default: { name: 'Explorer', url: 'https://assethub-westend.subscan.io' }, }, contracts: { - multicall3: { - address: '0x5545dec97cb957e83d3e6a1e82fabfacf9764cf1', - blockCreated: 10174702, + lpToken: { + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + }, + rewardToken: { + address: REWARD_TOKEN_CONTRACT_ADDRESS, + }, + yieldToken: { + address: YIELD_FARMING_CONTRACT_ADDRESS, }, }, }) @@ -67,7 +76,7 @@ const { wallets } = getDefaultWallets(); // initialize and destructure wallets object const config = getDefaultConfig({ - appName: "DOTUI", // Name your app + appName: "OpenGuild", // Name your app projectId: "ddf8cf3ee0013535c3760d4c79c9c8b9", // Enter your WalletConnect Project ID here wallets: [ ...wallets, diff --git a/challenge-3-frontend/app/send-transaction/page.tsx b/challenge-3-frontend/app/send-transaction/page.tsx index 80b85dc..395041e 100644 --- a/challenge-3-frontend/app/send-transaction/page.tsx +++ b/challenge-3-frontend/app/send-transaction/page.tsx @@ -1,15 +1,28 @@ "use client"; + import SendTransaction from "@/components/send-transaction"; -import SigpassKit from "@/components/sigpasskit"; -import Navbar from "@/components/navbar"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; export default function SendTransactionPage() { return ( -
- - -

Send Transaction

+
+ + + + Send Transaction + + + Transfer assets securely using passkey or browser wallets, with intuitive validation and real-time status updates from submission to confirmation. + + + +
); -} +} \ No newline at end of file diff --git a/challenge-3-frontend/app/vesting/page.tsx b/challenge-3-frontend/app/vesting/page.tsx new file mode 100644 index 0000000..2cbfa66 --- /dev/null +++ b/challenge-3-frontend/app/vesting/page.tsx @@ -0,0 +1,41 @@ +"use client"; + +import Vesting from "@/components/vesting"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, +} from "@/components/ui/card"; + +export default function VestingPage() { + return ( +
+ {/* headline + how-it-works */} + + + + Token Vesting Studio + + + Craft multi-cliff schedules with custom slice periods and optional + revocability, unlock tokens linearly (or instantly after each cliff), and + monitor real-time release progress with human-readable timestamps. + + + +

How it works

+
    +
  • Cliff — minimum time before any tokens unlock.
  • +
  • Slice period — granularity of unlock distributions.
  • +
  • Revocable — if enabled, the creator can revoke unvested tokens.
  • +
+
+
+ + {/* interactive widgets */} + +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/app/write-contract/page.tsx b/challenge-3-frontend/app/write-contract/page.tsx index 1947eea..cc3138a 100644 --- a/challenge-3-frontend/app/write-contract/page.tsx +++ b/challenge-3-frontend/app/write-contract/page.tsx @@ -1,15 +1,29 @@ "use client"; + import WriteContract from "@/components/write-contract"; -import SigpassKit from "@/components/sigpasskit"; -import Navbar from "@/components/navbar"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; -export default function SendTransactionPage() { +export default function WriteContractPage() { return ( -
- - -

Write Contract

+
+ + + + Contract Studio + + + Interact with any smart contract effortlessly: auto-generated forms, typed + validations, and on-chain transaction tracking—no code required. + + + +
); -} +} \ No newline at end of file diff --git a/challenge-3-frontend/app/yield-farm/loader.tsx b/challenge-3-frontend/app/yield-farm/loader.tsx new file mode 100644 index 0000000..e288aea --- /dev/null +++ b/challenge-3-frontend/app/yield-farm/loader.tsx @@ -0,0 +1,17 @@ +import { Skeleton } from "@/components/ui/skeleton" + +export default function Loading() { + // You can add any UI inside Loading, including a Skeleton. + return ( +
+ +
+ + + + +
+ +
+ ) +} \ No newline at end of file diff --git a/challenge-3-frontend/app/yield-farm/page.tsx b/challenge-3-frontend/app/yield-farm/page.tsx new file mode 100644 index 0000000..d3f72ce --- /dev/null +++ b/challenge-3-frontend/app/yield-farm/page.tsx @@ -0,0 +1,37 @@ +"use client"; + +import YieldFarm from "@/components/yield-farm"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, +} from "@/components/ui/card"; + +export default function YieldFarmPage() { + return ( +
+ {/* headline card */} + + + + Yield Farming Dashboard + + + Supply liquidity, lock positions for boosted APR, auto-compound + rewards on-chain, and inspect full emission logs & TVL + analytics—all in one place. + + + + Figures refresh every 15 s from the latest block; past + performance is not a guarantee of future returns. + + + + {/* interactive area */} + +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/create-wallet-dialog.tsx b/challenge-3-frontend/components/create-wallet-dialog.tsx new file mode 100644 index 0000000..49e08b7 --- /dev/null +++ b/challenge-3-frontend/components/create-wallet-dialog.tsx @@ -0,0 +1,139 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useAtom } from "jotai"; +import { createWalletDialogOpenAtom } from "@/lib/atoms"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogFooter, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { KeyRound, Ban, ExternalLink } from "lucide-react"; +import Image from "next/image"; +import { + createSigpassWallet, + checkBrowserWebAuthnSupport, +} from "@/lib/sigpass"; + +/** + * Globally mounted dialog that lets users generate a Sigpass wallet. + * Because it’s outside the drawer hierarchy, it survives when the drawer unmounts. + */ +export default function CreateWalletDialog() { + const [open, setOpen] = useAtom(createWalletDialogOpenAtom); + const [webAuthn, setWebAuthn] = useState(false); + + useEffect(() => { + setWebAuthn(checkBrowserWebAuthnSupport()); + }, []); + + const handleCreate = async () => { + await createSigpassWallet("dapp"); + // Dialog closes only after successful creation + setOpen(false); + }; + + return ( + + + + Create Wallet + + Instantly get a wallet secured by  + + Passkey + + + + +
+

What is a Wallet?

+ +
+ Digital assets icon +
+

+ A Home for your Digital Assets +

+

+ Wallets are used to send, receive, store, and display digital + assets like Polkadot and NFTs. +

+
+
+ +
+ Login icon +
+

A new way to Log In

+

+ Instead of creating new accounts and passwords on every + website, just connect your wallet. +

+
+
+
+ + + + Learn more + + + {webAuthn ? ( + + ) : ( + + )} + + +

+ Powered by  + + Sigpass + +

+
+
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/mint-redeem-lst-bifrost.tsx b/challenge-3-frontend/components/mint-redeem-lst-bifrost.tsx index 54672c7..7bf0dfe 100644 --- a/challenge-3-frontend/components/mint-redeem-lst-bifrost.tsx +++ b/challenge-3-frontend/components/mint-redeem-lst-bifrost.tsx @@ -10,8 +10,9 @@ import { useConfig, useWriteContract, useReadContracts, - useAccount + useAccount, } from "wagmi"; +import type { WriteContractErrorType } from "wagmi/actions"; // viem import { parseUnits, formatUnits } from "viem"; @@ -96,24 +97,21 @@ import { addressAtom } from "@/components/sigpasskit"; import { localConfig } from "@/app/providers"; // abi for the Moonbeam SLPX contract and ERC20 token -import {erc20Abi , moonbeamSlpxAbi} from "@/lib/abi"; +import { erc20Abi, moonbeamSlpxAbi } from "@/lib/abi"; export default function MintRedeemLstBifrost() { - // useConfig hook to get config + /* --------------------------------------------------------------------- */ + /* initial hooks */ + /* --------------------------------------------------------------------- */ const config = useConfig(); - - // useAccount hook to get account const account = useAccount(); - - // useMediaQuery hook to check if the screen is desktop const isDesktop = useMediaQuery("(min-width: 768px)"); - // useState hook to open/close dialog/drawer const [open, setOpen] = useState(false); - - // get the address from session storage const address = useAtomValue(addressAtom); - // useWriteContract hook to write contract + /* --------------------------------------------------------------------- */ + /* write contract helpers */ + /* --------------------------------------------------------------------- */ const { data: hash, error, @@ -123,15 +121,16 @@ export default function MintRedeemLstBifrost() { config: address ? localConfig : config, }); + /* --------------------------------------------------------------------- */ + /* constants */ + /* --------------------------------------------------------------------- */ const XCDOT_CONTRACT_ADDRESS = "0xFfFFfFff1FcaCBd218EDc0EbA20Fc2308C778080"; const XCASTR_CONTRACT_ADDRESS = "0xFfFFFfffA893AD19e540E172C10d78D4d479B5Cf"; - // GLMR is both the native token of Moonbeam and an ERC20 token const GLMR_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000802"; const BIFROST_SLPX_CONTRACT_ADDRESS = "0xF1d4797E51a4640a76769A50b57abE7479ADd3d8"; - // Get the contract address based on selected token const getContractAddress = (token: string) => { switch (token) { case "xcdot": @@ -145,13 +144,13 @@ export default function MintRedeemLstBifrost() { } }; - // form schema for sending transaction + /* --------------------------------------------------------------------- */ + /* form setup */ + /* --------------------------------------------------------------------- */ const formSchema = z.object({ - // token is a required field selected from a list token: z.enum(["xcdot", "glmr", "xcastr"], { required_error: "Please select a token", }), - // amount is a required field amount: z .string() .refine((val) => !isNaN(parseFloat(val)) && parseFloat(val) > 0, { @@ -162,9 +161,7 @@ export default function MintRedeemLstBifrost() { }) .superRefine((val, ctx) => { if (!maxBalance || !decimals) return; - const inputAmount = parseUnits(val, decimals as number); - if (inputAmount > (maxBalance as bigint)) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -174,47 +171,38 @@ export default function MintRedeemLstBifrost() { }), }); - // 1. Define your form. const form = useForm>({ - // resolver is zodResolver resolver: zodResolver(formSchema), - // default values for address and amount defaultValues: { token: "xcdot", amount: "", }, }); - - // Extract the token value using watch instead of getValues const selectedToken = form.watch("token"); - - - // useReadContracts hook to read contract + /* --------------------------------------------------------------------- */ + /* read-only contracts */ + /* --------------------------------------------------------------------- */ const { data, refetch: refetchBalance } = useReadContracts({ contracts: [ { - // get the balance of the selected token address: getContractAddress(selectedToken), abi: erc20Abi, functionName: "balanceOf", args: [address ? address : account.address], }, { - // get the symbol of the selected token address: getContractAddress(selectedToken), abi: erc20Abi, functionName: "symbol", }, { - // get the decimals of the selected token address: getContractAddress(selectedToken), abi: erc20Abi, functionName: "decimals", }, { - // get the allowance of the selected token address: getContractAddress(selectedToken), abi: erc20Abi, functionName: "allowance", @@ -227,133 +215,93 @@ export default function MintRedeemLstBifrost() { config: address ? localConfig : config, }); + const maxBalance = data?.[0]?.result as bigint | undefined; + const symbol = data?.[1]?.result as string | undefined; + const decimals = data?.[2]?.result as number | undefined; + const mintAllowance = data?.[3]?.result as bigint | undefined; - // extract the data from the read contracts hook - const maxBalance = data?.[0]?.result as bigint | undefined; // balance of the selected token - const symbol = data?.[1]?.result as string | undefined; // symbol of the selected token - const decimals = data?.[2]?.result as number | undefined; // decimals of the selected token - const mintAllowance = data?.[3]?.result as bigint | undefined; // allowance of the selected token - - // extract the amount value from the form const amount = form.watch("amount"); - - // check if the amount is greater than the mint allowance - const needsApprove = mintAllowance !== undefined && - amount ? - mintAllowance < parseUnits(amount, decimals || 18) : - false; - - - // 2. Define a submit handler. + const needsApprove = + mintAllowance !== undefined && amount + ? mintAllowance < parseUnits(amount, decimals || 18) + : false; + + /* --------------------------------------------------------------------- */ + /* submit handler */ + /* --------------------------------------------------------------------- */ async function onSubmit(values: z.infer) { - // if the user has a sigpass wallet, and the token is not GLMR, approve the token - if (address) { - if (needsApprove) { - writeContractAsync({ - account: await getSigpassWallet(), - address: getContractAddress(values.token), - abi: erc20Abi, - functionName: "approve", - args: [BIFROST_SLPX_CONTRACT_ADDRESS, parseUnits(values.amount, decimals as number)], - }); - } - } - - // if the user does not have a sigpass wallet, and the token is not GLMR, mint the token - if (!address) { - if (needsApprove) { - writeContractAsync({ - address: getContractAddress(values.token), - abi: erc20Abi, - functionName: "approve", - args: [BIFROST_SLPX_CONTRACT_ADDRESS, parseUnits(values.amount, decimals as number)], - }); - } - } - - /** - * @dev Create order to mint vAsset or redeem vAsset on bifrost chain - * @param assetAddress The address of the asset to mint or redeem - * @param amount The amount of the asset to mint or redeem - * @param dest_chain_id When order is executed on Bifrost, Asset/vAsset will be transferred to this chain - * @param receiver The receiver address on the destination chain, 20 bytes for EVM, 32 bytes for Substrate - * @param remark The remark of the order, less than 32 bytes. For example, "OmniLS" - * @param channel_id The channel id of the order, you can set it. Bifrost chain will use it to share reward. - **/ - if (!address && !needsApprove && selectedToken !== "glmr") { - writeContractAsync({ - address: BIFROST_SLPX_CONTRACT_ADDRESS, - abi: moonbeamSlpxAbi, - functionName: "create_order", + if (needsApprove) { + await writeContractAsync({ + ...(address && { account: await getSigpassWallet() }), + address: getContractAddress(values.token), + abi: erc20Abi, + functionName: "approve", args: [ - getContractAddress(values.token), + BIFROST_SLPX_CONTRACT_ADDRESS, parseUnits(values.amount, decimals as number), - 1284, // Moonbeam chain id - account.address, // receiver - "dotui", // remark - 0, // channel_id ], }); + return; } - if (!address && !needsApprove && selectedToken === "glmr") { - writeContractAsync({ - address: BIFROST_SLPX_CONTRACT_ADDRESS, - abi: moonbeamSlpxAbi, - functionName: "create_order", - args: [ - getContractAddress(values.token), - parseUnits(values.amount, decimals as number), - 1284, // Moonbeam chain id - account.address, // receiver - "dotui", // remark - 0, // channel_id - ], + const orderArgs = [ + getContractAddress(values.token), + parseUnits(values.amount, decimals as number), + 1284, // Moonbeam chain id + account.address, // receiver + "dotui", // remark + 0, // channel_id + ] as const; + + await writeContractAsync({ + ...(address && { account: await getSigpassWallet() }), + address: BIFROST_SLPX_CONTRACT_ADDRESS, + abi: moonbeamSlpxAbi, + functionName: "create_order", + args: orderArgs, + ...(selectedToken === "glmr" && { value: parseUnits(values.amount, decimals as number), - }); - } + }), + }); } - // Watch for transaction hash and open dialog/drawer when received + /* --------------------------------------------------------------------- */ + /* tx status + side effects */ + /* --------------------------------------------------------------------- */ useEffect(() => { - if (hash) { - setOpen(true); - } + if (hash) setOpen(true); }, [hash]); - // useWaitForTransactionReceipt hook to wait for transaction receipt const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash, config: address ? localConfig : config, }); - // when isConfirmed, refetch the balance of the address useEffect(() => { - if (isConfirmed) { - refetchBalance(); - } + if (isConfirmed) refetchBalance(); }, [isConfirmed, refetchBalance]); - // Find the chain ID from the connected account - const chainId = account.chainId; - - // Get the block explorer URL for the current chain using the config object - function getBlockExplorerUrl(chainId: number | undefined): string | undefined { - const chain = config.chains?.find(chain => chain.id === chainId); - return chain?.blockExplorers?.default?.url || config.chains?.[0]?.blockExplorers?.default?.url; - } + const explorerUrl = + config.chains?.find((c) => c.id === account.chainId)?.blockExplorers + ?.default?.url || config.chains?.[0]?.blockExplorers?.default?.url; + /* --------------------------------------------------------------------- */ + /* render */ + /* --------------------------------------------------------------------- */ return ( - + Mint Redeem + + {/* ------------------------------- Mint ------------------------------ */} -
+
+ {/* token selector */} )} /> + + {/* amount input */} ( -
+
Amount -
- {" "} - { - maxBalance !== undefined ? ( - formatUnits(maxBalance as bigint, decimals as number) - ) : ( - - ) - }{" "} - { - symbol ? ( - symbol - ) : ( - - ) - } +
+ + {maxBalance !== undefined ? ( + formatUnits(maxBalance, decimals as number) + ) : ( + + )}{" "} + {symbol ?? }
@@ -428,222 +370,229 @@ export default function MintRedeemLstBifrost() { )} - The amount of {selectedToken === "glmr" ? "GLMR" : symbol} to mint + The amount of{" "} + {selectedToken === "glmr" ? "GLMR" : symbol ?? "token"} to + mint )} /> -
-

Token allowance

-
- {" "} - { - mintAllowance !== undefined ? ( - formatUnits(mintAllowance as bigint, decimals as number) - ) : ( - - ) - }{" "} - { - symbol ? ( - symbol - ) : ( - - ) - } + + {/* allowance & preview */} +
+

Token allowance

+
+ + {mintAllowance !== undefined ? ( + formatUnits(mintAllowance, decimals as number) + ) : ( + + )}{" "} + {symbol ?? }
-
-

You are about to mint this token

-
- { - selectedToken === "glmr" ? ( - "xcvGLMR" - ) : selectedToken === "xcdot" ? ( - "xcvDOT" - ) : selectedToken === "xcastr" ? ( - "xcvASTR" - ) : ( - - ) - } -
+ +
+

You will receive

+ + {selectedToken === "glmr" + ? "xcvGLMR" + : selectedToken === "xcdot" + ? "xcvDOT" + : "xcvASTR"} +
-
- { - isPending ? ( - - ) : needsApprove ? ( - + + {/* action buttons */} +
+ - ) - } - {isPending ? ( - - ) : needsApprove ? ( - - ) : ( - - )} + "Approve" + )} + +
- - { - // Desktop would be using dialog - isDesktop ? ( - - - - - - - Transaction status - - - Follow the transaction status below. - -
- {hash ? ( -
- - Transaction Hash - - {truncateHash(hash)} - - - -
- ) : ( -
- - No transaction hash -
- )} - {!isPending && !isConfirmed && !isConfirming && ( -
- No transaction submitted -
- )} - {isConfirming && ( -
- {" "} - Waiting for confirmation... -
- )} - {isConfirmed && ( -
- Transaction - confirmed! -
- )} - {error && ( -
- Error:{" "} - {(error as BaseError).shortMessage || error.message} -
- )} -
- - - - - -
-
- ) : ( - // Mobile would be using drawer - - - - - - - Transaction status - - Follow the transaction status below. - - -
- {hash ? ( -
- - Transaction Hash - - {truncateHash(hash)} - - - -
- ) : ( -
- - No transaction hash -
- )} - {!isPending && !isConfirmed && !isConfirming && ( -
- No transaction submitted -
- )} - {isConfirming && ( -
- {" "} - Waiting for confirmation... -
- )} - {isConfirmed && ( -
- Transaction - confirmed! -
- )} - {error && ( -
- Error:{" "} - {(error as BaseError).shortMessage || error.message} -
- )} -
- - - - - -
-
- ) - } + + {/* status panel */} + {isDesktop ? ( + + + + + + + Transaction status + + + Follow every step—from wallet signature to final confirmation. + + + + + + + + + + ) : ( + + + + + + + Transaction status + + Follow every step—from wallet signature to final + confirmation. + + +
+ +
+ + + + + +
+
+ )} +
+ + + {/* ------------------------------ Redeem ----------------------------- */} + +
+ Redeem flow coming soon…
- placeholder ); } +/* --------------------------------------------------------------------- */ +/* helpers */ +/* --------------------------------------------------------------------- */ + +type StatusProps = { + hash?: `0x${string}`; + isPending: boolean; + isConfirming: boolean; + isConfirmed: boolean; + error: BaseError | WriteContractErrorType | null | undefined; + explorerUrl?: string; +}; + +function StatusBody({ + hash, + isPending, + isConfirming, + isConfirmed, + error, + explorerUrl, +}: StatusProps) { + return ( +
+ {hash ? ( + + ) : ( +
+ No transaction hash +
+ )} + + {isPending && ( +
+ + Awaiting signature in wallet… +
+ )} + + {!isPending && !isConfirming && !isConfirmed && ( +
+ No transaction submitted +
+ )} + + {isConfirming && ( +
+ + Broadcast to network—waiting for confirmations… +
+ )} + + {isConfirmed && ( +
+ + Confirmed on-chain! +
+ )} + + {error && ( +
+ + {"shortMessage" in (error as BaseError) + ? (error as BaseError).shortMessage + : (error as Error).message} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/navbar.tsx b/challenge-3-frontend/components/navbar.tsx index d212169..e652e12 100644 --- a/challenge-3-frontend/components/navbar.tsx +++ b/challenge-3-frontend/components/navbar.tsx @@ -1,32 +1,230 @@ +"use client"; + +import { useState } from "react"; import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + Home, + Wallet as WalletIcon, + Send, + PenLine, + Coins, + Boxes, + ChevronDown, + Menu, + Sparkles, +} from "lucide-react"; +import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; +import { cn } from "@/lib/utils"; +import SigpassKit from "@/components/sigpasskit"; +import { + Drawer, + DrawerTrigger, + DrawerContent, + DrawerClose, +} from "@/components/ui/drawer"; +import { Button } from "@/components/ui/button"; +import CreateWalletDialog from "@/components/create-wallet-dialog"; + +/* -------------------------------------------------------------------------- */ +/* Primary routes */ +/* -------------------------------------------------------------------------- */ + +const primaryNav = [ + { href: "/", label: "Home", icon: Home }, + { href: "/yield-farm", label: "Yield Farm", icon: Coins }, + { href: "/vesting", label: "Vesting", icon: Boxes }, + { href: "/send-transaction", label: "Send Tx", icon: Send }, + { href: "/write-contract", label: "Write", icon: PenLine }, +]; + +/* -------------------------------------------------------------------------- */ +/* Navbar */ +/* -------------------------------------------------------------------------- */ export default function Navbar() { + const pathname = usePathname(); + const [walletDrawerOpen, setWalletDrawerOpen] = useState(false); + + /* -------------------------------- render -------------------------------- */ return ( -
- - Home - - - Wallet - - - Send transaction - - - Write contract - - - Mint/Redeem LST Bifrost - -
+ <> +
+
+ {/* Brand */} + + + Open Encode + + + {/* Desktop nav */} + + + {/* Wallet actions */} +
+ {/* Wallet drawer */} + + + + + + { + // Close the wallet drawer whenever any button inside is clicked + const target = e.target as HTMLElement; + if (target.closest("button")) setWalletDrawerOpen(false); + }} + className="border-0 bg-gradient-to-br from-pink-500 via-fuchsia-600 to-violet-600 text-white shadow-2xl" + > +
+ +

+ Polkadot Wallet +

+

+ Create or connect a passkey wallet to unlock the multichain + power of the Polkadot ecosystem. +

+ + + + + + +
+
+
+ + {/* Mobile menu */} + +
+
+
+ + {/* Mount the global create-wallet dialog once, outside the header */} + + + ); +} + +/* -------------------------------------------------------------------------- */ +/* Helper components */ +/* -------------------------------------------------------------------------- */ + +function NavItem({ + href, + active, + children, +}: { + href: string; + active: boolean; + children: React.ReactNode; +}) { + return ( + + {children} + ); } + +function DropdownLink({ + href, + external = false, + children, +}: { + href: string; + external?: boolean; + children: React.ReactNode; +}) { + const common = + "flex items-center rounded-sm px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground"; + return external ? ( + + + {children} + + + ) : ( + + {children} + + ); +} + +function MobileMenu({ pathname }: { pathname: string | null }) { + return ( + + + + + + {primaryNav.map(({ href, label }) => ( + + {label} + + ))} + + + Mint / Redeem Bifrost + + + + ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/send-transaction.tsx b/challenge-3-frontend/components/send-transaction.tsx index edff6a0..94485fc 100644 --- a/challenge-3-frontend/components/send-transaction.tsx +++ b/challenge-3-frontend/components/send-transaction.tsx @@ -5,18 +5,10 @@ import { type BaseError, useSendTransaction, useWaitForTransactionReceipt, - useConfig + useConfig, } from "wagmi"; import { parseEther, isAddress, Address } from "viem"; -import { - Ban, - ExternalLink, - ChevronDown, - X, - Hash, - LoaderCircle, - CircleCheck, -} from "lucide-react"; +import { ChevronDown, LoaderCircle } from "lucide-react"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; @@ -52,17 +44,15 @@ import { DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer"; -import { truncateHash } from "@/lib/utils"; -import CopyButton from "@/components/copy-button"; +import TransactionStatus from "@/components/transaction-status"; import { getSigpassWallet } from "@/lib/sigpass"; import { westendAssetHub } from "@/app/providers"; -import { useAtomValue } from 'jotai'; -import { addressAtom } from '@/components/sigpasskit'; -import { localConfig } from '@/app/providers'; +import { useAtomValue } from "jotai"; +import { addressAtom } from "@/components/sigpasskit"; +import { localConfig } from "@/app/providers"; // form schema for sending transaction const formSchema = z.object({ - // address is a required field address: z .string() .min(2) @@ -70,7 +60,6 @@ const formSchema = z.object({ .refine((val) => val === "" || isAddress(val), { message: "Invalid Ethereum address format", }) as z.ZodType
, - // amount is a required field amount: z .string() .refine((val) => !isNaN(parseFloat(val)) && parseFloat(val) > 0, { @@ -82,19 +71,11 @@ const formSchema = z.object({ }); export default function SendTransaction() { - - // useConfig hook to get config const config = useConfig(); - - // useMediaQuery hook to check if the screen is desktop const isDesktop = useMediaQuery("(min-width: 768px)"); - // useState hook to open/close dialog/drawer const [open, setOpen] = useState(false); + const address = useAtomValue(addressAtom); - // get the address from session storage - const address = useAtomValue(addressAtom) - - // useSendTransaction hook to send transaction const { data: hash, error, @@ -104,55 +85,39 @@ export default function SendTransaction() { config: address ? localConfig : config, }); - - // 1. Define your form. const form = useForm>({ - // resolver is zodResolver resolver: zodResolver(formSchema), - // default values for address and amount - defaultValues: { - address: "", - amount: "", - }, + defaultValues: { address: "", amount: "" }, }); - - // 2. Define a submit handler. async function onSubmit(values: z.infer) { if (address) { - sendTransactionAsync({ + await sendTransactionAsync({ account: await getSigpassWallet(), to: values.address as Address, value: parseEther(values.amount), chainId: westendAssetHub.id, }); } else { - // Fallback to connected wallet - sendTransactionAsync({ + await sendTransactionAsync({ to: values.address as Address, value: parseEther(values.amount), }); } } - // Watch for transaction hash and open dialog/drawer when received useEffect(() => { - if (hash) { - setOpen(true); - } + if (hash) setOpen(true); }, [hash]); - - // useWaitForTransactionReceipt hook to wait for transaction receipt const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash, config: address ? localConfig : config, }); - return ( -
+
Amount {isDesktop ? ( - + ) : ( )} /> - { - isPending ? ( - + - ) - } + "Send" + )} + - { - // Desktop would be using dialog - isDesktop ? ( - - - - - - - Transaction status - - + + {isDesktop ? ( + + + + + + + Transaction status + + + Follow the transaction status below. + + + + + + + + + + + + ) : ( + + + + + + + Transaction status + Follow the transaction status below. - -
- {hash ? ( -
- - Transaction Hash - - {truncateHash(hash)} - - - -
- ) : ( -
- - No transaction hash -
- )} - { - !isPending && !isConfirmed && !isConfirming && ( -
- No transaction submitted -
- ) - } - {isConfirming && ( -
- Waiting - for confirmation... -
- )} - {isConfirmed && ( -
- Transaction confirmed! -
- )} - {error && ( -
- Error:{" "} - {(error as BaseError).shortMessage || error.message} -
- )} -
- - - - - -
-
- ) : ( - // Mobile would be using drawer - - - - - - - Transaction status - - Follow the transaction status below. - - -
- {hash ? ( -
- - Transaction Hash - - {truncateHash(hash)} - - - -
- ) : ( -
- - No transaction hash -
- )} - { - !isPending && !isConfirmed && !isConfirming && ( -
- No transaction submitted -
- ) - } - {isConfirming && ( -
- Waiting - for confirmation... -
- )} - {isConfirmed && ( -
- Transaction confirmed! -
- )} - {error && ( -
- Error:{" "} - {(error as BaseError).shortMessage || error.message} -
- )} -
- - - - - -
-
- ) - } + + +
+ +
+ + + + + + + + )}
); -} +} \ No newline at end of file diff --git a/challenge-3-frontend/components/sigpasskit.tsx b/challenge-3-frontend/components/sigpasskit.tsx index d655b4a..74d8fe2 100644 --- a/challenge-3-frontend/components/sigpasskit.tsx +++ b/challenge-3-frontend/components/sigpasskit.tsx @@ -1,15 +1,33 @@ "use client"; import { useState, useEffect } from "react"; -import '@rainbow-me/rainbowkit/styles.css'; +import "@rainbow-me/rainbowkit/styles.css"; import { useMediaQuery } from "@/hooks/use-media-query"; import { Skeleton } from "@/components/ui/skeleton"; import { Button } from "@/components/ui/button"; -import { Copy, Check, KeyRound, Ban, ExternalLink, LogOut, ChevronDown, X } from 'lucide-react'; -import { formatEther, Address } from 'viem'; -import { createSigpassWallet, getSigpassWallet, checkSigpassWallet, checkBrowserWebAuthnSupport } from "@/lib/sigpass"; -import { ConnectButton } from '@rainbow-me/rainbowkit'; -import { useAccount, useBalance, createConfig, http, useConfig } from 'wagmi'; +import { + Copy, + Check, + KeyRound, + ChevronDown, + LogOut, + ExternalLink, +} from "lucide-react"; +import { formatEther, Address } from "viem"; +import { + createSigpassWallet, + getSigpassWallet, + checkSigpassWallet, + checkBrowserWebAuthnSupport, +} from "@/lib/sigpass"; +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import { + useAccount, + useBalance, + createConfig, + http, + useConfig, +} from "wagmi"; import { Dialog, DialogContent, @@ -18,7 +36,8 @@ import { DialogFooter, DialogTitle, DialogTrigger, -} from "@/components/ui/dialog" + DialogClose, +} from "@/components/ui/dialog"; import { Drawer, DrawerClose, @@ -28,354 +47,293 @@ import { DrawerHeader, DrawerTitle, DrawerTrigger, -} from "@/components/ui/drawer" -import Image from 'next/image'; -import { useAtom } from 'jotai'; -import { atomWithStorage, RESET } from 'jotai/utils'; -import { westendAssetHub } from '@/app/providers'; +} from "@/components/ui/drawer"; +import Image from "next/image"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; +import { atomWithStorage, RESET } from "jotai/utils"; +import { westendAssetHub } from "@/app/providers"; +import { createWalletDialogOpenAtom } from "@/lib/atoms"; +// Persistent address storage +export const addressAtom = atomWithStorage
( + "SIGPASS_ADDRESS", + undefined +); -// Set the string key and the initial value -export const addressAtom = atomWithStorage
('SIGPASS_ADDRESS', undefined) - -// create a local config for the wallet +// Local wagmi config for Sigpass‐only wallets const localConfig = createConfig({ chains: [westendAssetHub], - transports: { - [westendAssetHub.id]: http(), - }, + transports: { [westendAssetHub.id]: http() }, ssr: true, }); +/* -------------------------------------------------------------------------- */ +/* Custom ConnectButton UI */ +/* -------------------------------------------------------------------------- */ +function CustomConnectButton() { + return ( + + {({ + account, + chain, + openAccountModal, + openChainModal, + openConnectModal, + mounted, + }) => { + const ready = mounted; + const connected = + ready && + account && + chain && + !chain.unsupported; + + if (!connected) { + return ( + + ); + } + + return ( +
+ + + +
+ ); + }} +
+ ); +} + +/* -------------------------------------------------------------------------- */ +/* SigpassKit UI */ +/* -------------------------------------------------------------------------- */ export default function SigpassKit() { - const [wallet, setWallet] = useState(false); - const [open, setOpen] = useState(false); - const [webAuthnSupport, setWebAuthnSupport] = useState(false); - const isDesktop = useMediaQuery("(min-width: 768px)") - const account = useAccount(); - const [address, setAddress] = useAtom(addressAtom); - const [isCopied, setIsCopied] = useState(false); - const config = useConfig(); - const { data: balance } = useBalance({ - address: address, - chainId: westendAssetHub.id, - config: address ? localConfig : config, - }); + /* ------------------------------------------------------------------ */ + /* global & local state */ + /* ------------------------------------------------------------------ */ + const setCreateWalletOpen = useSetAtom(createWalletDialogOpenAtom); - // check if the wallet is already created - useEffect(() => { - async function fetchWalletStatus() { - const status = await checkSigpassWallet(); - setWallet(status); - } - fetchWalletStatus(); - }, []); + const [hasWallet, setHasWallet] = useState(false); // Sigpass created? + const [detailsOpen, setDetailsOpen] = useState(false); // Wallet-details modal + const [webAuthn, setWebAuthn] = useState(false); // Browser support + const [copied, setCopied] = useState(false); // Copy feedback + + const isDesktop = useMediaQuery("(min-width: 768px)"); + const account = useAccount(); + const wagmiConfig = useConfig(); + const [address, setAddress] = useAtom(addressAtom); - // check if the browser supports WebAuthn + /* ------------------------------------------------------------------ */ + /* effects */ + /* ------------------------------------------------------------------ */ useEffect(() => { - const support = checkBrowserWebAuthnSupport(); - setWebAuthnSupport(support); + checkSigpassWallet().then(setHasWallet); + setWebAuthn(checkBrowserWebAuthnSupport()); }, []); - // get the wallet - async function getWallet() { - const account = await getSigpassWallet(); - if (account) { - setAddress(account.address); - } else { - console.error('Issue getting wallet'); - } - } - - // create a wallet - async function createWallet() { - const account = await createSigpassWallet("dapp"); - if (account) { - setOpen(false); - setWallet(true); - } - } + const { data: balance } = useBalance({ + address, + chainId: westendAssetHub.id, + config: address ? localConfig : wagmiConfig, + }); - // truncate address to 6 characters and add ... at the end - function truncateAddress(address: Address, length: number = 4) { - return `${address.slice(0, length)}...${address.slice(-length)}`; - } + /* ------------------------------------------------------------------ */ + /* helper event handlers */ + /* ------------------------------------------------------------------ */ + // Retrieve stored account from WebAuthn + const handleGetWallet = async () => { + const acc = await getSigpassWallet(); + if (acc) setAddress(acc.address); + }; - // copy the address to the clipboard - function copyAddress() { - if (address) { - navigator.clipboard.writeText(address ? address : ""); - setIsCopied(true); - setTimeout(() => { - setIsCopied(false); - }, 1000); - } - } + // Copy address to clipboard + const copyAddress = () => { + if (!address) return; + navigator.clipboard.writeText(address); + setCopied(true); + setTimeout(() => setCopied(false), 1000); + }; - // disconnect the wallet - function disconnect() { + // Disconnect (local) wallet + const disconnect = () => { setAddress(undefined); - setOpen(false); + setDetailsOpen(false); setAddress(RESET); - } + }; + // Truncate address for display + const short = (addr: Address, len = 4) => + `${addr.slice(0, len)}…${addr.slice(-len)}`; - if (isDesktop) { - return ( -
- {!wallet && !account.isConnected && !address ? ( - - - - - - - Create Wallet - - Instantly get a wallet with Passkey - - -
-
-

What is a Wallet?

-
- icon-1 -
-

A Home for your Digital Assets

-

Wallets are used to send, receive, store, and display digital assets like Polkadot and NFTs.

-
-
-
- icon-2 -
-

A new way to Log In

-

Instead of creating new accounts and passwords on every website, just connect your wallet.

-
-
-
-
- -
- Learn more - { - webAuthnSupport ? ( - - ) : ( - - ) - } -
-
-
- Powered by Sigpass -
-
-
- ) : wallet && !account.isConnected && address === undefined ? ( - - ) : wallet && !account.isConnected && address ? - + /* ------------------------------------------------------------------ */ + /* rendered component */ + /* ------------------------------------------------------------------ */ + return ( +
+ {/* ----------------------------- NO WALLET CREATED ---------------------------- */} + {!hasWallet && !account.isConnected && !address ? ( + + ) : null} + + {/* ------------------------ WALLET EXISTS BUT NOT LOADED ---------------------- */} + {hasWallet && !account.isConnected && address === undefined ? ( + + ) : null} + + {/* -------------------------- LOCAL WALLET LOADED ----------------------------- */} + {hasWallet && !account.isConnected && address ? ( + /* Desktop uses Dialog; Mobile uses Drawer for wallet details */ + isDesktop ? ( + - + Wallet - - {truncateAddress(address, 4)} + + {short(address, 4)} -
- {balance ? `${formatEther(balance.value)} WND` : } -
-
- - -
-
-
- : null} - { - !address ? : null - } -
- ) - } - - return ( -
- {(!wallet && !account.isConnected && !address) ? ( - - - - - - - Create Wallet - - Instantly get a wallet with Passkey - - -
-
-

What is a Wallet?

-
- icon-1 -
-

A Home for your Digital Assets

-

Wallets are used to send, receive, store, and display digital assets like Polkadot and NFTs.

-
-
-
- icon-2 -
-

A new way to Log In

-

Instead of creating new accounts and passwords on every website, just connect your wallet.

-
-
- Learn more -
-
- - {webAuthnSupport ? ( + - ) : ( - - )} - - - -
- Powered by Sigpass +
-
-
-
- ) : wallet && !account.isConnected && address === undefined ? ( - - ) : wallet && !account.isConnected && address ? ( - - - - - - -
+ +
+ ) : ( + + + + + + Wallet - - - -
- - {truncateAddress(address, 4)} - - -
-
- {balance ? `${formatEther(balance.value)} WND` : } -
-
- -
-
- - + + + ) ) : null} - {!address ? : null} -
- ) -} + {/* ------------------------------ CONNECT BUTTON ------------------------------ */} + {!address ? : null} +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/transaction-status.tsx b/challenge-3-frontend/components/transaction-status.tsx new file mode 100644 index 0000000..471c69d --- /dev/null +++ b/challenge-3-frontend/components/transaction-status.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { + Ban, + CircleCheck, + ExternalLink, + Hash, + LoaderCircle, + X, +} from "lucide-react"; +import { type BaseError } from "wagmi"; +import { truncateHash } from "@/lib/utils"; +import CopyButton from "@/components/copy-button"; + +export interface TransactionStatusProps { + hash?: `0x${string}`; + isPending: boolean; + isConfirming: boolean; + isConfirmed: boolean; + error?: BaseError | null; + explorerUrl?: string; +} + +export default function TransactionStatus({ + hash, + isPending, + isConfirming, + isConfirmed, + error, + explorerUrl, +}: TransactionStatusProps) { + return ( +
+ {hash ? ( +
+ + Transaction Hash + + {truncateHash(hash)} + + + +
+ ) : ( +
+ + No transaction hash +
+ )} + + {!isPending && !isConfirming && !isConfirmed && ( +
+ + No transaction submitted +
+ )} + + {isConfirming && ( +
+ + Waiting for confirmation… +
+ )} + + {isConfirmed && ( +
+ + Transaction confirmed! +
+ )} + + {error && ( +
+ + Error: {(error as BaseError).shortMessage || error.message} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/ui/button.tsx b/challenge-3-frontend/components/ui/button.tsx index 36496a2..4561322 100644 --- a/challenge-3-frontend/components/ui/button.tsx +++ b/challenge-3-frontend/components/ui/button.tsx @@ -18,6 +18,8 @@ const buttonVariants = cva( "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", + polkadot: + "bg-gradient-to-r from-pink-500 via-fuchsia-500 to-violet-500 text-white shadow-md hover:opacity-90", }, size: { default: "h-10 px-4 py-2", @@ -53,4 +55,4 @@ const Button = React.forwardRef( ) Button.displayName = "Button" -export { Button, buttonVariants } +export { Button, buttonVariants } \ No newline at end of file diff --git a/challenge-3-frontend/components/ui/card.tsx b/challenge-3-frontend/components/ui/card.tsx new file mode 100644 index 0000000..f6189f0 --- /dev/null +++ b/challenge-3-frontend/components/ui/card.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLHeadingElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardFooter.displayName = "CardFooter"; + +export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter }; \ No newline at end of file diff --git a/challenge-3-frontend/components/vesting/create-form.tsx b/challenge-3-frontend/components/vesting/create-form.tsx new file mode 100644 index 0000000..38e1fbc --- /dev/null +++ b/challenge-3-frontend/components/vesting/create-form.tsx @@ -0,0 +1,303 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + useWriteContract, + useWaitForTransactionReceipt, + useConfig, +} from "wagmi"; +import { parseUnits, Address, isAddress } from "viem"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { + Form, + FormField, + FormItem, + FormLabel, + FormControl, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + CardFooter, +} from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogFooter, + DialogTitle, + DialogTrigger, + DialogClose, +} from "@/components/ui/dialog"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; +import { ChevronDown } from "lucide-react"; +import TransactionStatus from "@/components/transaction-status"; +import { tokenVestingAbi } from "@/lib/abi"; +import { TOKEN_VESTING_CONTRACT_ADDRESS } from "@/lib/config"; +import { useAtomValue } from "jotai"; +import { addressAtom } from "@/components/sigpasskit"; +import { getSigpassWallet } from "@/lib/sigpass"; +import { westendAssetHub, localConfig } from "@/app/providers"; +import { useMediaQuery } from "@/hooks/use-media-query"; + +const formSchema = z.object({ + beneficiary: z + .string() + .refine((val) => isAddress(val), { message: "Invalid address" }) as z.ZodType
, + amount: z + .string() + .refine((val) => !isNaN(parseFloat(val)) && parseFloat(val) > 0, { + message: "Must be positive", + }), + startTime: z.string().min(1, { message: "Required" }), + cliff: z.string().min(1, { message: "Required" }), + duration: z.string().min(1, { message: "Required" }), + slicePeriod: z.string().min(1, { message: "Required" }), + revocable: z.boolean(), +}); + +export default function CreateForm() { + const config = useConfig(); + const address = useAtomValue(addressAtom); + const isDesktop = useMediaQuery("(min-width: 768px)"); + const [open, setOpen] = useState(false); + + const { + data: hash, + error, + isPending, + writeContractAsync, + } = useWriteContract({ + config: address ? localConfig : config, + }); + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + config: address ? localConfig : config, + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + beneficiary: "", + amount: "", + startTime: Math.floor(Date.now() / 1000).toString(), + cliff: "0", + duration: "0", + slicePeriod: "1", + revocable: false, + }, + }); + + async function onSubmit(values: z.infer) { + await writeContractAsync({ + ...(address && { account: await getSigpassWallet() }), + address: TOKEN_VESTING_CONTRACT_ADDRESS, + abi: tokenVestingAbi, + functionName: "createVestingSchedule", + args: [ + values.beneficiary as `0x${string}`, + parseUnits(values.amount, 18), + BigInt(values.startTime), + BigInt(values.cliff), + BigInt(values.duration), + BigInt(values.slicePeriod), + values.revocable, + ], + chainId: westendAssetHub.id, + }); + } + + /* Open status panel automatically when hash appears */ + useEffect(() => { + if (hash) setOpen(true); + }, [hash]); + + return ( + + + Create Vesting Schedule + + Lock tokens with optional slice period and revocability. + + + + +
+ + {/* beneficiary */} + ( + + Beneficiary + + + + + + )} + /> + {/* amount */} + ( + + Amount + + + + + + )} + /> + {/* timing */} +
+ {["startTime","cliff","duration"].map((n)=>( + ( + + {n.replace(/([A-Z])/g," $1")} + + + + + + )} + /> + ))} +
+ {/* slice */} + ( + + Slice Period (s) + + + + + + )} + /> + {/* revocable */} + ( + + + field.onChange(e.currentTarget.checked)} + className="h-4 w-4 accent-pink-500" + /> + + Revocable by creator + + )} + /> + + + +
+ + + + UNIX seconds are used for all timestamps; ensure your wallet signs accurate values. + + + {/* transaction status trigger */} + {isDesktop ? ( + + + + + + + Transaction status + + + Follow every step—from wallet signature to final confirmation. + + + + + + + + + + ) : ( + + + + + + + Transaction status + + Follow every step—from wallet signature to final confirmation. + + +
+ +
+ + + + + +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/vesting/index.tsx b/challenge-3-frontend/components/vesting/index.tsx new file mode 100644 index 0000000..3918ff1 --- /dev/null +++ b/challenge-3-frontend/components/vesting/index.tsx @@ -0,0 +1,13 @@ +"use client"; + +import CreateForm from "./create-form"; +import ScheduleCard from "./schedule-card"; + +export default function Vesting() { + return ( +
+ + +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/vesting/schedule-card.tsx b/challenge-3-frontend/components/vesting/schedule-card.tsx new file mode 100644 index 0000000..36ca00e --- /dev/null +++ b/challenge-3-frontend/components/vesting/schedule-card.tsx @@ -0,0 +1,272 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + useReadContracts, + useWriteContract, + useConfig, + useAccount, + useWaitForTransactionReceipt, + type BaseError, +} from "wagmi"; +import { formatUnits } from "viem"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + CardFooter, +} from "@/components/ui/card"; +import { tokenVestingAbi } from "@/lib/abi"; +import { TOKEN_VESTING_CONTRACT_ADDRESS } from "@/lib/config"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useAtomValue } from "jotai"; +import { addressAtom } from "@/components/sigpasskit"; +import { getSigpassWallet } from "@/lib/sigpass"; +import { westendAssetHub, localConfig } from "@/app/providers"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogTrigger, + DialogFooter, + DialogClose, +} from "@/components/ui/dialog"; +import { + Drawer, + DrawerContent, + DrawerHeader, + DrawerTitle, + DrawerDescription, + DrawerTrigger, + DrawerFooter, + DrawerClose, +} from "@/components/ui/drawer"; +import { ChevronDown } from "lucide-react"; +import TransactionStatus from "@/components/transaction-status"; +import { useMediaQuery } from "@/hooks/use-media-query"; + +export default function ScheduleCard() { + const config = useConfig(); + const account = useAccount(); + const address = useAtomValue(addressAtom); + const isDesktop = useMediaQuery("(min-width: 768px)"); + const [open, setOpen] = useState(false); + + /* --------------------------------------------------------------------- */ + /* Derive address */ + /* --------------------------------------------------------------------- */ + const beneficiary = (address ?? account.address) as + | `0x${string}` + | undefined; + + /* --------------------------------------------------------------------- */ + /* On-chain read */ + /* --------------------------------------------------------------------- */ + const { data, isLoading, refetch } = useReadContracts({ + contracts: [ + { + address: TOKEN_VESTING_CONTRACT_ADDRESS, + abi: tokenVestingAbi, + functionName: "getVestingSchedule", + args: [ + beneficiary ?? "0x0000000000000000000000000000000000000000", + ], + }, + ], + config, + }); + + const schedule = data?.[0]?.result as + | { + totalAmount: bigint; + startTime: bigint; + cliff: bigint; + duration: bigint; + releasedAmount: bigint; + revoked: boolean; + } + | undefined; + + /* --------------------------------------------------------------------- */ + /* Write tx */ + /* --------------------------------------------------------------------- */ + const { + data: hash, + error, + isPending, + writeContractAsync, + } = useWriteContract({ + config: address ? localConfig : config, + }); + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + config: address ? localConfig : config, + }); + + const handleClaim = async () => { + if (!beneficiary) return; + await writeContractAsync({ + ...(address && { account: await getSigpassWallet() }), + address: TOKEN_VESTING_CONTRACT_ADDRESS, + abi: tokenVestingAbi, + functionName: "release", + args: [beneficiary], + chainId: westendAssetHub.id, + }); + }; + + /* --------------------------------------------------------------------- */ + /* UI side-effects */ + /* --------------------------------------------------------------------- */ + useEffect(() => { + const id = setInterval(refetch, 60_000); + return () => clearInterval(id); + }, [refetch]); + + useEffect(() => { + if (hash) setOpen(true); + }, [hash]); + + /* --------------------------------------------------------------------- */ + /* Render */ + /* --------------------------------------------------------------------- */ + return ( + + + Your Vesting Schedule + Track and claim your vested tokens. + + + + {isLoading ? ( +
+ + + + +
+ ) : schedule ? ( +
    +
  • + Total:{" "} + {formatUnits(schedule.totalAmount, 18)} tokens +
  • +
  • + Start:{" "} + {new Date(Number(schedule.startTime) * 1000).toLocaleString()} +
  • +
  • + Cliff:{" "} + {Number(schedule.cliff)} s +
  • +
  • + Duration:{" "} + {Number(schedule.duration)} s +
  • +
  • + Released:{" "} + {formatUnits(schedule.releasedAmount, 18)} tokens +
  • +
  • + Status:{" "} + {schedule.revoked ? "Revoked" : "Active"} +
  • +
+ ) : ( +

No vesting schedule found.

+ )} +
+ + {!schedule?.revoked && ( + + + + {/* ---------------------------- Status panel --------------------------- */} + {isDesktop ? ( + + + + + + + Transaction status + + + Follow every step—from wallet signature to final confirmation. + + + + + + + + + + ) : ( + + + + + + + Transaction status + + Follow every step—from wallet signature to final confirmation. + + +
+ +
+ + + + + +
+
+ )} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/write-contract.tsx b/challenge-3-frontend/components/write-contract.tsx index 1c0270b..2f589c0 100644 --- a/challenge-3-frontend/components/write-contract.tsx +++ b/challenge-3-frontend/components/write-contract.tsx @@ -10,8 +10,9 @@ import { useConfig, useWriteContract, useReadContracts, - useAccount + useAccount, } from "wagmi"; +import type { WriteContractErrorType } from "wagmi/actions"; // Viem imports import { parseUnits, formatUnits, isAddress, Address } from "viem"; @@ -25,7 +26,7 @@ import { Hash, LoaderCircle, CircleCheck, - WalletMinimal + WalletMinimal, } from "lucide-react"; // Zod imports @@ -80,66 +81,53 @@ import CopyButton from "@/components/copy-button"; // Library imports import { getSigpassWallet } from "@/lib/sigpass"; import { westendAssetHub } from "@/app/providers"; -import { useAtomValue } from 'jotai' -import { addressAtom } from '@/components/sigpasskit' -import { Skeleton } from "./ui/skeleton"; +import { useAtomValue } from "jotai"; +import { addressAtom } from "@/components/sigpasskit"; +import { Skeleton } from "@/components/ui/skeleton"; import { localConfig } from "@/app/providers"; // Abi for ERC20 Token import { erc20AbiExtend } from "@/lib/abi"; -export default function WriteContract() { - // useConfig hook to get config +export default function WriteContract() { const config = useConfig(); - - // useAccount hook to get account const account = useAccount(); - - // useMediaQuery hook to check if the screen is desktop const isDesktop = useMediaQuery("(min-width: 768px)"); - // useState hook to open/close dialog/drawer const [open, setOpen] = useState(false); + const address = useAtomValue(addressAtom); - // get the address from session storage - const address = useAtomValue(addressAtom) - - // useWriteContract hook to write contract const { data: hash, error, isPending, - writeContractAsync + writeContractAsync, } = useWriteContract({ config: address ? localConfig : config, - }) + }); const USDC_CONTRACT_ADDRESS = "0xc8576Fb6De558b313afe0302B3fedc6F6447BbEE"; - // useReadContracts hook to read contract - const { - data, - refetch - } = useReadContracts({ - contracts: [{ - address: USDC_CONTRACT_ADDRESS, - abi: erc20AbiExtend, - functionName: 'balanceOf', - args: [address ? address : account.address], - }, { - address: USDC_CONTRACT_ADDRESS, - abi: erc20AbiExtend, - functionName: 'decimals', - }], + const { data, refetch } = useReadContracts({ + contracts: [ + { + address: USDC_CONTRACT_ADDRESS, + abi: erc20AbiExtend, + functionName: "balanceOf", + args: [address ? address : account.address], + }, + { + address: USDC_CONTRACT_ADDRESS, + abi: erc20AbiExtend, + functionName: "decimals", + }, + ], config: address ? localConfig : config, - }) + }); - // get the max balance and decimals from the data const maxBalance = data?.[0]?.result as bigint | undefined; const decimals = data?.[1]?.result as number | undefined; - // form schema for sending transaction const formSchema = z.object({ - // address is a required field address: z .string() .min(2) @@ -147,7 +135,6 @@ export default function WriteContract() { .refine((val) => val === "" || isAddress(val), { message: "Invalid address format", }) as z.ZodType
, - // amount is a required field amount: z .string() .refine((val) => !isNaN(parseFloat(val)) && parseFloat(val) > 0, { @@ -158,7 +145,7 @@ export default function WriteContract() { }) .superRefine((val, ctx) => { if (!maxBalance || !decimals) return; - + const inputAmount = parseUnits(val, decimals as number); if (inputAmount > (maxBalance as bigint)) { @@ -170,66 +157,57 @@ export default function WriteContract() { }), }); - // 1. Define your form. const form = useForm>({ - // resolver is zodResolver resolver: zodResolver(formSchema), - // default values for address and amount defaultValues: { address: "", amount: "", }, }); - - // 2. Define a submit handler. async function onSubmit(values: z.infer) { if (address) { - writeContractAsync({ + await writeContractAsync({ account: await getSigpassWallet(), address: USDC_CONTRACT_ADDRESS, abi: erc20AbiExtend, - functionName: 'transfer', - args: [values.address as Address, parseUnits(values.amount, decimals as number)], + functionName: "transfer", + args: [ + values.address as Address, + parseUnits(values.amount, decimals as number), + ], chainId: westendAssetHub.id, }); } else { - // Fallback to connected wallet - writeContractAsync({ + await writeContractAsync({ address: USDC_CONTRACT_ADDRESS, abi: erc20AbiExtend, - functionName: 'transfer', - args: [values.address as Address, parseUnits(values.amount, decimals as number)], + functionName: "transfer", + args: [ + values.address as Address, + parseUnits(values.amount, decimals as number), + ], chainId: westendAssetHub.id, }); } } - // Watch for transaction hash and open dialog/drawer when received useEffect(() => { - if (hash) { - setOpen(true); - } + if (hash) setOpen(true); }, [hash]); - - // useWaitForTransactionReceipt hook to wait for transaction receipt const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash, config: address ? localConfig : config, }); - // when isConfirmed, refetch the balance of the address useEffect(() => { - if (isConfirmed) { - refetch(); - } + if (isConfirmed) refetch(); }, [isConfirmed, refetch]); - return ( -
+
( - Receiving Address + Receiving Address - The address to send USDC to + + The address to send USDC to + )} @@ -251,20 +231,21 @@ export default function WriteContract() { name="amount" render={({ field }) => ( -
+
Amount -
- {maxBalance ? formatUnits(maxBalance as bigint, decimals as number) : } USDC +
+ + {maxBalance ? ( + formatUnits(maxBalance, decimals as number) + ) : ( + + )}{" "} + USDC
{isDesktop ? ( - + ) : ( )} /> - { - isPending ? ( - - ) : ( - - ) - } + + {isPending ? ( + + ) : ( + + )} - { - // Desktop would be using dialog - isDesktop ? ( - - - - - - - Transaction status - - - Follow the transaction status below. - -
- {hash ? ( -
- - Transaction Hash - - {truncateHash(hash)} - - - -
- ) : ( -
- - No transaction hash -
- )} - { - !isPending && !isConfirmed && !isConfirming && ( -
- No transaction submitted -
- ) - } - {isConfirming && ( -
- Waiting - for confirmation... -
- )} - {isConfirmed && ( -
- Transaction confirmed! -
- )} - {error && ( -
- Error:{" "} - {(error as BaseError).shortMessage || error.message} -
- )} -
- - - - - -
-
- ) : ( - // Mobile would be using drawer - - - - - - - Transaction status - - Follow the transaction status below. - - -
- {hash ? ( -
- - Transaction Hash - - {truncateHash(hash)} - - - -
- ) : ( -
- - No transaction hash -
- )} - { - !isPending && !isConfirmed && !isConfirming && ( -
- No transaction submitted -
- ) - } - {isConfirming && ( -
- Waiting - for confirmation... -
- )} - {isConfirmed && ( -
- Transaction confirmed! -
- )} - {error && ( -
- Error:{" "} - {(error as BaseError).shortMessage || error.message} -
- )} -
- - - - - -
-
- ) - } + + {isDesktop ? ( + + + + + + + Transaction status + + + Follow every step—from wallet signature to confirmation. + + + + + + + + + + ) : ( + + + + + + + Transaction status + + Follow every step—from wallet signature to confirmation. + + +
+ +
+ + + + + +
+
+ )}
); } + +/* --------------------------------------------------------------------- */ +/* helper component */ +/* --------------------------------------------------------------------- */ + +type StatusProps = { + hash?: `0x${string}`; + isPending: boolean; + isConfirming: boolean; + isConfirmed: boolean; + error: BaseError | WriteContractErrorType | null | undefined; + explorerUrl?: string; +}; + +function StatusBody({ + hash, + isPending, + isConfirming, + isConfirmed, + error, + explorerUrl, +}: StatusProps) { + return ( +
+ {hash ? ( +
+ + Tx Hash + + {truncateHash(hash)} + + + +
+ ) : ( +
+ + No hash +
+ )} + + {isPending && ( +
+ + Awaiting signature in wallet… +
+ )} + + {!isPending && !isConfirming && !isConfirmed && ( +
+ No transaction +
+ )} + + {isConfirming && ( +
+ + Broadcast to network—waiting for confirmations… +
+ )} + + {isConfirmed && ( +
+ + Confirmed on-chain! +
+ )} + + {error && ( +
+ + {"shortMessage" in (error as BaseError) + ? (error as BaseError).shortMessage + : (error as Error).message} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/yield-farm/index.tsx b/challenge-3-frontend/components/yield-farm/index.tsx new file mode 100644 index 0000000..73e7ea7 --- /dev/null +++ b/challenge-3-frontend/components/yield-farm/index.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useMemo } from "react"; +import { formatUnits } from "viem"; +import { useReadContract } from "wagmi"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, +} from "@/components/ui/card"; +import Stake from "./stake"; +import Withdraw from "./withdraw"; +import { + LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + YIELD_FARMING_CONTRACT_ADDRESS, +} from "@/lib/config"; +import { mockErc20Abi, yieldFarmingAbi } from "@/lib/abi"; +import { Skeleton } from "@/components/ui/skeleton"; + +export default function YieldFarm() { + /* ------------------------------------------------------------ */ + /* On-chain pooled metrics */ + /* ------------------------------------------------------------ */ + const { data: tvlRaw } = useReadContract({ + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "totalSupply", + }); + + const { data: rewardRateRaw } = useReadContract({ + address: YIELD_FARMING_CONTRACT_ADDRESS, + abi: yieldFarmingAbi, + functionName: "rewardRate", + }); + + const tvl = useMemo( + () => (tvlRaw ? formatUnits(tvlRaw as bigint, 18) : null), + [tvlRaw] + ); + + const rewardRate = useMemo( + () => (rewardRateRaw ? formatUnits(rewardRateRaw as bigint, 18) : null), + [rewardRateRaw] + ); + + /* ------------------------------------------------------------ */ + /* UI */ + /* ------------------------------------------------------------ */ + return ( +
+ {/* Pool statistics */} + + + Pool Statistics + Updated live on-chain + + + + + + + + {/* Stake / Withdraw tabs */} + + + Stake + Withdraw + + + + + + + + + +
+ ); +} + +/* ------------------------------------------------------------ */ +/* Helper component */ +/* ------------------------------------------------------------ */ + +function Stat({ label, value }: { label: string; value?: string | null }) { + return ( +
+ {label} + {value ? ( + {value} + ) : ( + + )} +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/yield-farm/mint-lp-token.tsx b/challenge-3-frontend/components/yield-farm/mint-lp-token.tsx new file mode 100644 index 0000000..e016073 --- /dev/null +++ b/challenge-3-frontend/components/yield-farm/mint-lp-token.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { useEffect } from "react"; +import { + useWaitForTransactionReceipt, + useConfig, + useWriteContract, + useAccount, + useReadContract, +} from "wagmi"; +import { parseUnits } from "viem"; +import { Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { getSigpassWallet } from "@/lib/sigpass"; +import { useAtomValue } from "jotai"; +import { addressAtom } from "@/components/sigpasskit"; +import { localConfig, westendAssetHub } from "@/app/providers"; +import { LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS } from "@/lib/config"; +import { mockErc20Abi } from "@/lib/abi"; + +type Props = { + label: string; +}; + +export default function MintLpToken({ label }: Props) { + const config = useConfig(); + const account = useAccount(); + const address = useAtomValue(addressAtom); + + const { + data: hash, + isPending, + writeContractAsync, + } = useWriteContract({ + config: address ? localConfig : config, + }); + + const { data: decimalsData, refetch } = useReadContract({ + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "decimals", + chainId: westendAssetHub.id, + config: address ? localConfig : config, + }); + + const decimals = decimalsData as number | undefined; + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + config: address ? localConfig : config, + }); + + const handleMint = async () => { + try { + const args = [ + address ?? account.address, + parseUnits("1000", decimals as number), + ] as const; + + if (address) { + await writeContractAsync({ + account: await getSigpassWallet(), + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "mint", + args, + chainId: westendAssetHub.id, + }); + } else { + await writeContractAsync({ + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "mint", + args, + chainId: westendAssetHub.id, + }); + } + } catch (error) { + console.error(error); + } + }; + + useEffect(() => { + if (isConfirmed) { + refetch(); + } + }, [isConfirmed, refetch]); + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/yield-farm/stake.tsx b/challenge-3-frontend/components/yield-farm/stake.tsx new file mode 100644 index 0000000..b0c084c --- /dev/null +++ b/challenge-3-frontend/components/yield-farm/stake.tsx @@ -0,0 +1,487 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + type BaseError, + useWaitForTransactionReceipt, + useConfig, + useWriteContract, + useReadContracts, + useAccount, +} from "wagmi"; +import type { WriteContractErrorType } from "wagmi/actions"; +import { parseUnits, formatUnits } from "viem"; +import { + Ban, + ExternalLink, + ChevronDown, + X, + Hash, + LoaderCircle, + CircleCheck, + WalletMinimal, + Lock, +} from "lucide-react"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectGroup, + SelectLabel, + SelectItem, +} from "@/components/ui/select"; +import { Input } from "@/components/ui/input"; +import { useMediaQuery } from "@/hooks/use-media-query"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogFooter, + DialogTitle, + DialogTrigger, + DialogClose, +} from "@/components/ui/dialog"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; +import { truncateHash } from "@/lib/utils"; +import CopyButton from "@/components/copy-button"; +import { getSigpassWallet } from "@/lib/sigpass"; +import { westendAssetHub, localConfig } from "@/app/providers"; +import { useAtomValue } from "jotai"; +import { addressAtom } from "@/components/sigpasskit"; +import { Skeleton } from "@/components/ui/skeleton"; +import { mockErc20Abi, yieldFarmingAbi } from "@/lib/abi"; +import { LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, YIELD_FARMING_CONTRACT_ADDRESS } from "@/lib/config"; + +export default function Stake() { + const config = useConfig(); + const account = useAccount(); + const isDesktop = useMediaQuery("(min-width: 768px)"); + const [open, setOpen] = useState(false); + const address = useAtomValue(addressAtom); + + /* ------------------------------- schema ------------------------------- */ + const formSchema = z.object({ + amount: z + .string() + .refine((val) => !isNaN(parseFloat(val)) && parseFloat(val) > 0, { + message: "Amount must be a positive number", + }) + .refine((val) => /^\d*\.?\d{0,18}$/.test(val), { + message: "Amount cannot have more than 18 decimal places", + }) + .superRefine((val, ctx) => { + if (!maxBalance || !decimals) return; + const inputAmount = parseUnits(val, decimals as number); + if (inputAmount > (maxBalance as bigint)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Amount exceeds available balance", + }); + } + }), + lockDays: z.enum(["0", "7", "30", "90"]), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { amount: "", lockDays: "0" }, + }); + + /* ------------------------- on-chain allowances ------------------------ */ + const { + data, + refetch: refetchBalances, + } = useReadContracts({ + contracts: [ + { + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "balanceOf", + args: [address ?? account.address], + }, + { + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "decimals", + }, + { + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "allowance", + args: [address ?? account.address, YIELD_FARMING_CONTRACT_ADDRESS], + }, + ], + config: address ? localConfig : config, + }); + + const maxBalance = data?.[0]?.result as bigint | undefined; + const decimals = data?.[1]?.result as number | undefined; + const allowance = data?.[2]?.result as bigint | undefined; + + const amountInput = form.watch("amount"); + const needsApprove = + allowance !== undefined && amountInput + ? allowance < parseUnits(amountInput, decimals || 18) + : false; + + /* --------------------------- write helpers --------------------------- */ + const { + data: hash, + error, + isPending, + writeContractAsync, + } = useWriteContract({ + config: address ? localConfig : config, + }); + + async function onSubmit(values: z.infer) { + const stakeAmount = parseUnits(values.amount, decimals as number); + + // off-chain analytics only use lockDays + const txArgs = [stakeAmount] as const; + + if (address) { + await writeOrApprove(txArgs, needsApprove, true); + } else { + await writeOrApprove(txArgs, needsApprove, false); + } + } + + async function writeOrApprove( + stakeArgs: readonly [bigint], + approveFirst: boolean, + isSigpass: boolean + ) { + if (approveFirst) { + await writeContractAsync({ + ...(isSigpass && { account: await getSigpassWallet() }), + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "approve", + args: [YIELD_FARMING_CONTRACT_ADDRESS, stakeArgs[0]], + chainId: westendAssetHub.id, + }); + } else { + await writeContractAsync({ + ...(isSigpass && { account: await getSigpassWallet() }), + address: YIELD_FARMING_CONTRACT_ADDRESS, + abi: yieldFarmingAbi, + functionName: "stake", + args: stakeArgs, + chainId: westendAssetHub.id, + }); + } + } + + /* --------------------------- tx lifecycle ---------------------------- */ + useEffect(() => { + if (hash) setOpen(true); + }, [hash]); + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + config: address ? localConfig : config, + }); + + useEffect(() => { + if (isConfirmed) refetchBalances(); + }, [isConfirmed, refetchBalances]); + + /* ------------------------------ render ------------------------------- */ + return ( +
+
+ + {/* amount field */} + ( + +
+ Amount to stake +
+ + {maxBalance ? ( + formatUnits(maxBalance, decimals as number) + ) : ( + + )} +  LP +
+
+ + {isDesktop ? ( + + ) : ( + + )} + + + Flexible deposits earn the base APR; locking boosts rewards. + + +
+ )} + /> + + {/* lock-period selector */} + ( + + + Lock Period + + + + Longer locks receive a higher reward multiplier. + + + + )} + /> + + {/* submit */} + + + + + {/* status dialog / drawer */} + {isDesktop ? ( + + ) : ( + + )} +
+ ); +} + +/* --------------------------------------------------------------------- */ +/* helpers */ +/* --------------------------------------------------------------------- */ + +type StatusProps = { + hash?: `0x${string}`; + isPending: boolean; + isConfirming: boolean; + isConfirmed: boolean; + error: BaseError | WriteContractErrorType | null | undefined; + explorerUrl?: string; +}; + +function StatusBody({ + hash, + isPending, + isConfirming, + isConfirmed, + error, + explorerUrl, +}: StatusProps) { + return ( +
+ {hash ? ( +
+ + Tx Hash + + {truncateHash(hash)} + + + +
+ ) : ( +
+ + No hash +
+ )} + + {isPending && ( +
+ + Awaiting signature in wallet… +
+ )} + + {!isPending && !isConfirming && !isConfirmed && ( +
+ No transaction +
+ )} + + {isConfirming && ( +
+ + Broadcast to network—waiting for confirmations… +
+ )} + + {isConfirmed && ( +
+ + Confirmed on-chain! Funds are now active. +
+ )} + + {error && ( +
+ + {"shortMessage" in (error as BaseError) + ? (error as BaseError).shortMessage + : (error as Error).message} +
+ )} +
+ ); +} + +// StatusDialog and StatusDrawer remain identical except for updated StatusProps +function StatusDialog({ + open, + setOpen, + ...status +}: { open: boolean; setOpen: (v: boolean) => void } & StatusProps) { + return ( + + + + + + + Transaction status + + + Follow every step—from wallet signature to final confirmation. + + + + + + + + + + ); +} + +function StatusDrawer({ + open, + setOpen, + ...status +}: { open: boolean; setOpen: (v: boolean) => void } & StatusProps) { + return ( + + + + + + + Transaction status + + Follow every step—from wallet signature to final confirmation. + + +
+ +
+ + + + + +
+
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/components/yield-farm/withdraw.tsx b/challenge-3-frontend/components/yield-farm/withdraw.tsx new file mode 100644 index 0000000..d21ccd4 --- /dev/null +++ b/challenge-3-frontend/components/yield-farm/withdraw.tsx @@ -0,0 +1,446 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + type BaseError, + useWaitForTransactionReceipt, + useConfig, + useWriteContract, + useReadContracts, + useAccount, +} from "wagmi"; +import type { WriteContractErrorType } from "wagmi/actions"; +import { parseUnits, formatUnits } from "viem"; +import { + Ban, + ExternalLink, + ChevronDown, + X, + Hash, + LoaderCircle, + CircleCheck, + WalletMinimal, +} from "lucide-react"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { useMediaQuery } from "@/hooks/use-media-query"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogFooter, + DialogTitle, + DialogTrigger, + DialogClose, +} from "@/components/ui/dialog"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; +import { truncateHash } from "@/lib/utils"; +import CopyButton from "@/components/copy-button"; +import { getSigpassWallet } from "@/lib/sigpass"; +import { westendAssetHub } from "@/app/providers"; +import { useAtomValue } from "jotai"; +import { addressAtom } from "@/components/sigpasskit"; +import { localConfig } from "@/app/providers"; +import { mockErc20Abi, yieldFarmingAbi } from "@/lib/abi"; +import { + LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + YIELD_FARMING_CONTRACT_ADDRESS, +} from "@/lib/config"; +import { Skeleton } from "../ui/skeleton"; + +export default function Withdraw() { + const config = useConfig(); + const account = useAccount(); + const isDesktop = useMediaQuery("(min-width: 768px)"); + const [open, setOpen] = useState(false); + const address = useAtomValue(addressAtom); + const formSchema = z.object({ + amount: z + .string() + .refine((val) => !isNaN(parseFloat(val)) && parseFloat(val) > 0, { + message: "Amount must be a positive number", + }) + .refine((val) => /^\d*\.?\d{0,18}$/.test(val), { + message: "Amount cannot have more than 18 decimal places", + }) + .superRefine((val, ctx) => { + if (!maxBalance || !decimals) return; + + const inputAmount = parseUnits(val, decimals as number); + + if (inputAmount > (maxBalance as bigint)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Amount exceeds available balance", + }); + } + }), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + amount: "", + }, + }); + + const { + data: hash, + error, + isPending, + writeContractAsync, + } = useWriteContract({ + config: address ? localConfig : config, + }); + + const { data, refetch } = useReadContracts({ + contracts: [ + { + address: YIELD_FARMING_CONTRACT_ADDRESS, + abi: yieldFarmingAbi, + functionName: "userInfo", + args: [address ? address : account.address], + }, + { + address: YIELD_FARMING_CONTRACT_ADDRESS, + abi: yieldFarmingAbi, + functionName: "pendingRewards", + args: [address ? address : account.address], + }, + { + address: LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS, + abi: mockErc20Abi, + functionName: "decimals", + }, + ], + config: address ? localConfig : config, + }); + + const maxBalance = (data?.[0]?.result as [bigint] | undefined)?.[0]; + const pendingRewards = data?.[1]?.result as bigint | undefined; + const decimals = data?.[2]?.result as number | undefined; + + async function onSubmit(values: z.infer) { + if (address) { + writeContractAsync({ + account: await getSigpassWallet(), + address: YIELD_FARMING_CONTRACT_ADDRESS, + abi: yieldFarmingAbi, + functionName: "withdraw", + args: [parseUnits(values.amount, decimals as number)], + chainId: westendAssetHub.id, + }); + } else { + writeContractAsync({ + address: YIELD_FARMING_CONTRACT_ADDRESS, + abi: yieldFarmingAbi, + functionName: "withdraw", + args: [parseUnits(values.amount, decimals as number)], + chainId: westendAssetHub.id, + }); + } + } + + const handleClaim = async () => { + if (address) { + writeContractAsync({ + account: await getSigpassWallet(), + address: YIELD_FARMING_CONTRACT_ADDRESS, + abi: yieldFarmingAbi, + functionName: "claimRewards", + chainId: westendAssetHub.id, + }); + } else { + writeContractAsync({ + address: YIELD_FARMING_CONTRACT_ADDRESS, + abi: yieldFarmingAbi, + functionName: "claimRewards", + chainId: westendAssetHub.id, + }); + } + }; + + useEffect(() => { + if (hash) { + setOpen(true); + } + }, [hash]); + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + config: address ? localConfig : config, + }); + + useEffect(() => { + if (isConfirmed) { + refetch(); + } + }, [isConfirmed, refetch]); + + return ( +
+ {pendingRewards && pendingRewards > 0 ? ( + <> + + Total reward:  + {formatUnits(pendingRewards as bigint, decimals as number)} + + + + ) : null} +
+ + ( + +
+ Amount to unstake +
+ {" "} + {maxBalance ? ( + formatUnits(maxBalance as bigint, decimals as number) + ) : ( + + )}{" "} + LP Token +
+
+ + {isDesktop ? ( + + ) : ( + + )} + + + The amount of LPToken to unstake + + +
+ )} + /> +
+ {isPending ? ( + + ) : ( + + )} +
+ + + { + // Desktop would be using dialog + isDesktop ? ( + + ) : ( + // Mobile would be using drawer + + ) + } +
+ ); +} + +/* --------------------------------------------------------------------- */ +/* helpers */ +/* --------------------------------------------------------------------- */ + +type StatusProps = { + hash?: `0x${string}`; + isPending: boolean; + isConfirming: boolean; + isConfirmed: boolean; + error: BaseError | WriteContractErrorType | null | undefined; + explorerUrl?: string; +}; + +function StatusBody({ + hash, + isPending, + isConfirming, + isConfirmed, + error, + explorerUrl, +}: StatusProps) { + return ( +
+ {hash ? ( +
+ + Transaction Hash + + {truncateHash(hash)} + + + +
+ ) : ( +
+ + No transaction hash +
+ )} + + {isPending && ( +
+ Awaiting + signature in wallet… +
+ )} + + {!isPending && !isConfirmed && !isConfirming && ( +
+ No transaction submitted +
+ )} + {isConfirming && ( +
+ Broadcast to + network—waiting for confirmations… +
+ )} + {isConfirmed && ( +
+ Transaction confirmed! +
+ )} + {error && ( +
+ {" "} + {"shortMessage" in (error as BaseError) + ? (error as BaseError).shortMessage + : (error as Error).message} +
+ )} +
+ ); +} + +function StatusDialog({ + open, + setOpen, + ...status +}: { open: boolean; setOpen: (v: boolean) => void } & StatusProps) { + return ( + + + + + + + Transaction status + + + Follow every step—from wallet signature to final confirmation. + + + + + + + + + + ); +} + +function StatusDrawer({ + open, + setOpen, + ...status +}: { open: boolean; setOpen: (v: boolean) => void } & StatusProps) { + return ( + + + + + + + Transaction status + + Follow every step—from wallet signature to final confirmation. + + +
+ +
+ + + + + +
+
+ ); +} \ No newline at end of file diff --git a/challenge-3-frontend/lib/abi.ts b/challenge-3-frontend/lib/abi.ts index 78ac757..d1f64a9 100644 --- a/challenge-3-frontend/lib/abi.ts +++ b/challenge-3-frontend/lib/abi.ts @@ -1,2419 +1,3699 @@ // ERC-20 token ABI export const erc20Abi = [ - { - inputs: [ - { - internalType: "address", - name: "initialOwner", - type: "address", - }, - ], - stateMutability: "nonpayable", - type: "constructor", - }, - { - inputs: [], - name: "ECDSAInvalidSignature", - type: "error", - }, - { - inputs: [ - { - internalType: "uint256", - name: "length", - type: "uint256", - }, - ], - name: "ECDSAInvalidSignatureLength", - type: "error", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "s", - type: "bytes32", - }, - ], - name: "ECDSAInvalidSignatureS", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "spender", - type: "address", - }, - { - internalType: "uint256", - name: "allowance", - type: "uint256", - }, - { - internalType: "uint256", - name: "needed", - type: "uint256", - }, - ], - name: "ERC20InsufficientAllowance", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "uint256", - name: "balance", - type: "uint256", - }, - { - internalType: "uint256", - name: "needed", - type: "uint256", - }, - ], - name: "ERC20InsufficientBalance", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "approver", - type: "address", - }, - ], - name: "ERC20InvalidApprover", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "receiver", - type: "address", - }, - ], - name: "ERC20InvalidReceiver", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - ], - name: "ERC20InvalidSender", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "spender", - type: "address", - }, - ], - name: "ERC20InvalidSpender", - type: "error", - }, - { - inputs: [ - { - internalType: "uint256", - name: "deadline", - type: "uint256", - }, - ], - name: "ERC2612ExpiredSignature", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "signer", - type: "address", - }, - { - internalType: "address", - name: "owner", - type: "address", - }, - ], - name: "ERC2612InvalidSigner", - type: "error", - }, - { - inputs: [ - { - internalType: "uint256", - name: "maxLoan", - type: "uint256", - }, - ], - name: "ERC3156ExceededMaxLoan", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "receiver", - type: "address", - }, - ], - name: "ERC3156InvalidReceiver", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "token", - type: "address", - }, - ], - name: "ERC3156UnsupportedToken", - type: "error", - }, - { - inputs: [], - name: "EnforcedPause", - type: "error", - }, - { - inputs: [], - name: "ExpectedPause", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "account", - type: "address", - }, - { - internalType: "uint256", - name: "currentNonce", - type: "uint256", - }, - ], - name: "InvalidAccountNonce", - type: "error", - }, - { - inputs: [], - name: "InvalidShortString", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "owner", - type: "address", - }, - ], - name: "OwnableInvalidOwner", - type: "error", - }, - { - inputs: [ - { - internalType: "address", - name: "account", - type: "address", - }, - ], - name: "OwnableUnauthorizedAccount", - type: "error", - }, - { - inputs: [ - { - internalType: "string", - name: "str", - type: "string", - }, - ], - name: "StringTooLong", - type: "error", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "owner", - type: "address", - }, - { - indexed: true, - internalType: "address", - name: "spender", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "Approval", - type: "event", - }, - { - anonymous: false, - inputs: [], - name: "EIP712DomainChanged", - 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: "account", - type: "address", - }, - ], - name: "Paused", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "from", - type: "address", - }, - { - indexed: true, - internalType: "address", - name: "to", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "Transfer", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: "address", - name: "account", - type: "address", - }, - ], - name: "Unpaused", - type: "event", - }, - { - inputs: [], - name: "DOMAIN_SEPARATOR", - outputs: [ - { - internalType: "bytes32", - name: "", - type: "bytes32", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "owner", - type: "address", - }, - { - internalType: "address", - name: "spender", - type: "address", - }, - ], - name: "allowance", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "spender", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "approve", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "account", - type: "address", - }, - ], - name: "balanceOf", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "burn", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "account", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "burnFrom", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "decimals", - outputs: [ - { - internalType: "uint8", - name: "", - type: "uint8", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "eip712Domain", - outputs: [ - { - internalType: "bytes1", - name: "fields", - type: "bytes1", - }, - { - internalType: "string", - name: "name", - type: "string", - }, - { - internalType: "string", - name: "version", - type: "string", - }, - { - internalType: "uint256", - name: "chainId", - type: "uint256", - }, - { - internalType: "address", - name: "verifyingContract", - type: "address", - }, - { - internalType: "bytes32", - name: "salt", - type: "bytes32", - }, - { - internalType: "uint256[]", - name: "extensions", - type: "uint256[]", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "token", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "flashFee", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "contract IERC3156FlashBorrower", - name: "receiver", - type: "address", - }, - { - internalType: "address", - name: "token", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - { - internalType: "bytes", - name: "data", - type: "bytes", - }, - ], - name: "flashLoan", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "token", - type: "address", - }, - ], - name: "maxFlashLoan", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "uint256", - name: "amount", - type: "uint256", - }, - ], - name: "mint", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "name", - outputs: [ - { - internalType: "string", - name: "", - type: "string", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "owner", - type: "address", - }, - ], - name: "nonces", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "owner", - outputs: [ - { - internalType: "address", - name: "", - type: "address", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "pause", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "paused", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "owner", - type: "address", - }, - { - internalType: "address", - name: "spender", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - { - internalType: "uint256", - name: "deadline", - type: "uint256", - }, - { - internalType: "uint8", - name: "v", - type: "uint8", - }, - { - internalType: "bytes32", - name: "r", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "s", - type: "bytes32", - }, - ], - name: "permit", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "renounceOwnership", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "symbol", - outputs: [ - { - internalType: "string", - name: "", - type: "string", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "totalSupply", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "transfer", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "from", - type: "address", - }, - { - internalType: "address", - name: "to", - type: "address", - }, - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "transferFrom", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "address", - name: "newOwner", - type: "address", - }, - ], - name: "transferOwnership", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "unpause", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, + { + inputs: [ + { + internalType: "address", + name: "initialOwner", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "ECDSAInvalidSignature", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "length", + type: "uint256", + }, + ], + name: "ECDSAInvalidSignatureLength", + type: "error", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "ECDSAInvalidSignatureS", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "spender", + type: "address", + }, + { + internalType: "uint256", + name: "allowance", + type: "uint256", + }, + { + internalType: "uint256", + name: "needed", + type: "uint256", + }, + ], + name: "ERC20InsufficientAllowance", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "uint256", + name: "balance", + type: "uint256", + }, + { + internalType: "uint256", + name: "needed", + type: "uint256", + }, + ], + name: "ERC20InsufficientBalance", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "approver", + type: "address", + }, + ], + name: "ERC20InvalidApprover", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "receiver", + type: "address", + }, + ], + name: "ERC20InvalidReceiver", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "ERC20InvalidSender", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "spender", + type: "address", + }, + ], + name: "ERC20InvalidSpender", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + ], + name: "ERC2612ExpiredSignature", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "signer", + type: "address", + }, + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "ERC2612InvalidSigner", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "maxLoan", + type: "uint256", + }, + ], + name: "ERC3156ExceededMaxLoan", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "receiver", + type: "address", + }, + ], + name: "ERC3156InvalidReceiver", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "token", + type: "address", + }, + ], + name: "ERC3156UnsupportedToken", + type: "error", + }, + { + inputs: [], + name: "EnforcedPause", + type: "error", + }, + { + inputs: [], + name: "ExpectedPause", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "uint256", + name: "currentNonce", + type: "uint256", + }, + ], + name: "InvalidAccountNonce", + type: "error", + }, + { + inputs: [], + name: "InvalidShortString", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "OwnableInvalidOwner", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "OwnableUnauthorizedAccount", + type: "error", + }, + { + inputs: [ + { + internalType: "string", + name: "str", + type: "string", + }, + ], + name: "StringTooLong", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "spender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [], + name: "EIP712DomainChanged", + 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: "account", + type: "address", + }, + ], + name: "Paused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "Transfer", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Unpaused", + type: "event", + }, + { + inputs: [], + name: "DOMAIN_SEPARATOR", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "spender", + type: "address", + }, + ], + name: "allowance", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "spender", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "approve", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "burn", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "burnFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [ + { + internalType: "uint8", + name: "", + type: "uint8", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "eip712Domain", + outputs: [ + { + internalType: "bytes1", + name: "fields", + type: "bytes1", + }, + { + internalType: "string", + name: "name", + type: "string", + }, + { + internalType: "string", + name: "version", + type: "string", + }, + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "address", + name: "verifyingContract", + type: "address", + }, + { + internalType: "bytes32", + name: "salt", + type: "bytes32", + }, + { + internalType: "uint256[]", + name: "extensions", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "token", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "flashFee", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "contract IERC3156FlashBorrower", + name: "receiver", + type: "address", + }, + { + internalType: "address", + name: "token", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "flashLoan", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "token", + type: "address", + }, + ], + name: "maxFlashLoan", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "mint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "nonces", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "paused", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "spender", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "uint256", + name: "deadline", + type: "uint256", + }, + { + internalType: "uint8", + name: "v", + type: "uint8", + }, + { + internalType: "bytes32", + name: "r", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "s", + type: "bytes32", + }, + ], + name: "permit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "transfer", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "transferFrom", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "unpause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, ]; -// ERC-20 token ABI Extend +// ERC-20 token ABI Extend export const erc20AbiExtend = [ - { - "inputs": [ - { - "internalType": "address", - "name": "initialOwner", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ECDSAInvalidSignature", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "length", - "type": "uint256" - } - ], - "name": "ECDSAInvalidSignatureLength", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "ECDSAInvalidSignatureS", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "allowance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } - ], - "name": "ERC20InsufficientAllowance", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "needed", - "type": "uint256" - } - ], - "name": "ERC20InsufficientBalance", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "approver", - "type": "address" - } - ], - "name": "ERC20InvalidApprover", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "name": "ERC20InvalidReceiver", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "ERC20InvalidSender", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "ERC20InvalidSpender", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - } - ], - "name": "ERC2612ExpiredSignature", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "signer", - "type": "address" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "ERC2612InvalidSigner", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "maxLoan", - "type": "uint256" - } - ], - "name": "ERC3156ExceededMaxLoan", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "name": "ERC3156InvalidReceiver", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "ERC3156UnsupportedToken", - "type": "error" - }, - { - "inputs": [], - "name": "EnforcedPause", - "type": "error" - }, - { - "inputs": [], - "name": "ExpectedPause", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "currentNonce", - "type": "uint256" - } - ], - "name": "InvalidAccountNonce", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidShortString", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "OwnableInvalidOwner", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "OwnableUnauthorizedAccount", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "str", - "type": "string" - } - ], - "name": "StringTooLong", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "EIP712DomainChanged", - "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": "account", - "type": "address" - } - ], - "name": "Paused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Unpaused", - "type": "event" - }, - { - "inputs": [], - "name": "DOMAIN_SEPARATOR", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "burnFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "eip712Domain", - "outputs": [ - { - "internalType": "bytes1", - "name": "fields", - "type": "bytes1" - }, - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "string", - "name": "version", - "type": "string" - }, - { - "internalType": "uint256", - "name": "chainId", - "type": "uint256" - }, - { - "internalType": "address", - "name": "verifyingContract", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "salt", - "type": "bytes32" - }, - { - "internalType": "uint256[]", - "name": "extensions", - "type": "uint256[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "flashFee", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IERC3156FlashBorrower", - "name": "receiver", - "type": "address" - }, - { - "internalType": "address", - "name": "token", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "flashLoan", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "token", - "type": "address" - } - ], - "name": "maxFlashLoan", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "nonces", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "permit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ECDSAInvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "ERC2612ExpiredSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC2612InvalidSigner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maxLoan", + "type": "uint256" + } + ], + "name": "ERC3156ExceededMaxLoan", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC3156InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "ERC3156UnsupportedToken", + "type": "error" + }, + { + "inputs": [], + "name": "EnforcedPause", + "type": "error" + }, + { + "inputs": [], + "name": "ExpectedPause", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentNonce", + "type": "uint256" + } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidShortString", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "str", + "type": "string" + } + ], + "name": "StringTooLong", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "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": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "flashFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC3156FlashBorrower", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flashLoan", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "maxFlashLoan", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } ]; // Moonbeam SLpX ABI Contract export const moonbeamSlpxAbi = [ - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "previousAdmin", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newAdmin", - "type": "address" - } - ], - "name": "AdminChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "beacon", - "type": "address" - } - ], - "name": "BeaconUpgraded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "implementation", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, - { - "stateMutability": "payable", - "type": "fallback" - }, - { - "inputs": [], - "name": "admin", - "outputs": [ - { - "internalType": "address", - "name": "admin_", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAdmin", - "type": "address" - } - ], - "name": "changeAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "implementation", - "outputs": [ - { - "internalType": "address", - "name": "implementation_", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - } - ], - "name": "upgradeTo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "upgradeToAndCall", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "stateMutability": "payable", - "type": "receive" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "assetAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint128", - "name": "amount", - "type": "uint128" - }, - { - "indexed": false, - "internalType": "uint64", - "name": "dest_chain_id", - "type": "uint64" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "receiver", - "type": "bytes" - }, - { - "indexed": false, - "internalType": "string", - "name": "remark", - "type": "string" - }, - { - "indexed": false, - "internalType": "uint32", - "name": "channel_id", - "type": "uint32" - } - ], - "name": "CreateOrder", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "minter", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "assetAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "callcode", - "type": "bytes" - }, - { - "indexed": false, - "internalType": "string", - "name": "remark", - "type": "string" - } - ], - "name": "Mint", - "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": "account", - "type": "address" - } - ], - "name": "Paused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "redeemer", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "assetAddress", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "callcode", - "type": "bytes" - } - ], - "name": "Redeem", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Unpaused", - "type": "event" - }, - { - "inputs": [], - "name": "BNCAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "addressToAssetInfo", - "outputs": [ - { - "internalType": "bytes2", - "name": "currencyId", - "type": "bytes2" - }, - { - "internalType": "uint256", - "name": "operationalMin", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "bifrostParaId", - "outputs": [ - { - "internalType": "uint32", - "name": "", - "type": "uint32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "assetAddress", - "type": "address" - }, - { - "internalType": "uint128", - "name": "amount", - "type": "uint128" - }, - { - "internalType": "uint64", - "name": "dest_chain_id", - "type": "uint64" - }, - { - "internalType": "bytes", - "name": "receiver", - "type": "bytes" - }, - { - "internalType": "string", - "name": "remark", - "type": "string" - }, - { - "internalType": "uint32", - "name": "channel_id", - "type": "uint32" - } - ], - "name": "create_order", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "name": "destChainInfo", - "outputs": [ - { - "internalType": "bool", - "name": "is_evm", - "type": "bool" - }, - { - "internalType": "bool", - "name": "is_substrate", - "type": "bool" - }, - { - "internalType": "bytes1", - "name": "raw_chain_index", - "type": "bytes1" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_BNCAddress", - "type": "address" - }, - { - "internalType": "uint32", - "name": "_bifrostParaId", - "type": "uint32" - }, - { - "internalType": "bytes2", - "name": "_nativeCurrencyId", - "type": "bytes2" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "assetAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "string", - "name": "remark", - "type": "string" - } - ], - "name": "mintVAsset", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "assetAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "string", - "name": "remark", - "type": "string" - }, - { - "internalType": "uint32", - "name": "channel_id", - "type": "uint32" - } - ], - "name": "mintVAssetWithChannelId", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "string", - "name": "remark", - "type": "string" - } - ], - "name": "mintVNativeAsset", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "receiver", - "type": "address" - }, - { - "internalType": "string", - "name": "remark", - "type": "string" - }, - { - "internalType": "uint32", - "name": "channel_id", - "type": "uint32" - } - ], - "name": "mintVNativeAssetWithChannelId", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "enum MoonbeamSlpx.Operation", - "name": "", - "type": "uint8" - } - ], - "name": "operationToFeeInfo", - "outputs": [ - { - "internalType": "uint64", - "name": "transactRequiredWeightAtMost", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "feeAmount", - "type": "uint256" - }, - { - "internalType": "uint64", - "name": "overallWeight", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "vAssetAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "address", - "name": "receiver", - "type": "address" - } - ], - "name": "redeemAsset", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "assetAddress", - "type": "address" - }, - { - "internalType": "bytes2", - "name": "currencyId", - "type": "bytes2" - }, - { - "internalType": "uint256", - "name": "minimumValue", - "type": "uint256" - } - ], - "name": "setAssetAddressInfo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "dest_chain_id", - "type": "uint64" - }, - { - "internalType": "bool", - "name": "is_evm", - "type": "bool" - }, - { - "internalType": "bool", - "name": "is_substrate", - "type": "bool" - }, - { - "internalType": "bytes1", - "name": "raw_chain_index", - "type": "bytes1" - } - ], - "name": "setDestChainInfo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "enum MoonbeamSlpx.Operation", - "name": "_operation", - "type": "uint8" - }, - { - "internalType": "uint64", - "name": "_transactRequiredWeightAtMost", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "_overallWeight", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "_feeAmount", - "type": "uint256" - } - ], - "name": "setOperationToFeeInfo", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_logic", - "type": "address" - }, - { - "internalType": "address", - "name": "admin_", - "type": "address" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "stateMutability": "payable", - "type": "constructor" - } -]; \ No newline at end of file + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "assetAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "dest_chain_id", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "string", + "name": "remark", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "channel_id", + "type": "uint32" + } + ], + "name": "CreateOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "assetAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "callcode", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "string", + "name": "remark", + "type": "string" + } + ], + "name": "Mint", + "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": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "assetAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "callcode", + "type": "bytes" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "BNCAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "addressToAssetInfo", + "outputs": [ + { + "internalType": "bytes2", + "name": "currencyId", + "type": "bytes2" + }, + { + "internalType": "uint256", + "name": "operationalMin", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bifrostParaId", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetAddress", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "uint64", + "name": "dest_chain_id", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "string", + "name": "remark", + "type": "string" + }, + { + "internalType": "uint32", + "name": "channel_id", + "type": "uint32" + } + ], + "name": "create_order", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "name": "destChainInfo", + "outputs": [ + { + "internalType": "bool", + "name": "is_evm", + "type": "bool" + }, + { + "internalType": "bool", + "name": "is_substrate", + "type": "bool" + }, + { + "internalType": "bytes1", + "name": "raw_chain_index", + "type": "bytes1" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_BNCAddress", + "type": "address" + }, + { + "internalType": "uint32", + "name": "_bifrostParaId", + "type": "uint32" + }, + { + "internalType": "bytes2", + "name": "_nativeCurrencyId", + "type": "bytes2" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "string", + "name": "remark", + "type": "string" + } + ], + "name": "mintVAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "string", + "name": "remark", + "type": "string" + }, + { + "internalType": "uint32", + "name": "channel_id", + "type": "uint32" + } + ], + "name": "mintVAssetWithChannelId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "string", + "name": "remark", + "type": "string" + } + ], + "name": "mintVNativeAsset", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "string", + "name": "remark", + "type": "string" + }, + { + "internalType": "uint32", + "name": "channel_id", + "type": "uint32" + } + ], + "name": "mintVNativeAssetWithChannelId", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum MoonbeamSlpx.Operation", + "name": "", + "type": "uint8" + } + ], + "name": "operationToFeeInfo", + "outputs": [ + { + "internalType": "uint64", + "name": "transactRequiredWeightAtMost", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "overallWeight", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vAssetAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "redeemAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "assetAddress", + "type": "address" + }, + { + "internalType": "bytes2", + "name": "currencyId", + "type": "bytes2" + }, + { + "internalType": "uint256", + "name": "minimumValue", + "type": "uint256" + } + ], + "name": "setAssetAddressInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "dest_chain_id", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "is_evm", + "type": "bool" + }, + { + "internalType": "bool", + "name": "is_substrate", + "type": "bool" + }, + { + "internalType": "bytes1", + "name": "raw_chain_index", + "type": "bytes1" + } + ], + "name": "setDestChainInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum MoonbeamSlpx.Operation", + "name": "_operation", + "type": "uint8" + }, + { + "internalType": "uint64", + "name": "_transactRequiredWeightAtMost", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "_overallWeight", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "_feeAmount", + "type": "uint256" + } + ], + "name": "setOperationToFeeInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + } +]; + + +export const mockErc20Abi = [ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "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": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]; + +export const tokenVestingAbi = [ + { + "inputs": [ + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "EnforcedPause", + "type": "error" + }, + { + "inputs": [], + "name": "ExpectedPause", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "BeneficiaryRemovedFromWhitelist", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "BeneficiaryWhitelisted", + "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": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokensClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "VestingRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "VestingScheduleCreated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "addToWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "calculateVestedAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimVestedTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cliffDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vestingDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + } + ], + "name": "createVestingSchedule", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "removeFromWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + } + ], + "name": "revokeVesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "vestingSchedules", + "outputs": [ + { + "internalType": "uint256", + "name": "totalAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cliffDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vestingDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountClaimed", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "revoked", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "whitelist", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +]; + +export const yieldFarmingAbi = [ + { + "inputs": [ + { + "internalType": "address", + "name": "_lpToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_rewardToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_rewardRate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "EmergencyWithdrawn", + "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": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardsClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Staked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "inputs": [], + "name": "BOOST_THRESHOLD_1", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BOOST_THRESHOLD_2", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BOOST_THRESHOLD_3", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "calculateBoostMultiplier", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "earned", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "emergencyWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lastUpdateTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lpToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "pendingRewards", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardPerToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardPerTokenStored", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "rewardToken", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "stake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalStaked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newRate", + "type": "uint256" + } + ], + "name": "updateRewardRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "userInfo", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardDebt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pendingRewards", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +]; diff --git a/challenge-3-frontend/lib/atoms.ts b/challenge-3-frontend/lib/atoms.ts new file mode 100644 index 0000000..275369c --- /dev/null +++ b/challenge-3-frontend/lib/atoms.ts @@ -0,0 +1,7 @@ +import { atom } from "jotai"; + +/** + * Global open-state for the "Create Wallet” modal. + * Any component can toggle this to show / hide the dialog. + */ +export const createWalletDialogOpenAtom = atom(false); \ No newline at end of file diff --git a/challenge-3-frontend/lib/config.ts b/challenge-3-frontend/lib/config.ts new file mode 100644 index 0000000..a03963d --- /dev/null +++ b/challenge-3-frontend/lib/config.ts @@ -0,0 +1,8 @@ +export const LIQUIDITY_POOL_TOKEN_CONTRACT_ADDRESS = + "0xaa085d3505c097c0a5e0cd97ab0f52114cca9967"; +export const REWARD_TOKEN_CONTRACT_ADDRESS = + "0xdd665d880f44daa5b3d18b8a8085def1aa3a9180"; +export const YIELD_FARMING_CONTRACT_ADDRESS = + "0x5b8595975e688e84536c1c5ac0c164345ea7e352"; +export const TOKEN_VESTING_CONTRACT_ADDRESS = + "0xa8bf41815aa87f9c67cbd6792a27a5451511b85e"; diff --git a/challenge-3-frontend/package-lock.json b/challenge-3-frontend/package-lock.json index dec4c63..2a88eb1 100644 --- a/challenge-3-frontend/package-lock.json +++ b/challenge-3-frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@hookform/resolvers": "^3.9.1", "@radix-ui/react-dialog": "^1.1.3", + "@radix-ui/react-dropdown-menu": "^2.1.12", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-select": "^2.1.4", "@radix-ui/react-separator": "^1.1.1", @@ -1274,6 +1275,164 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.12.tgz", + "integrity": "sha512-VJoMs+BWWE7YhzEQyVwvF9n22Eiyr83HotCVrMQzla/OwRovXCgah7AcaEr4hMNj4gJxSdtIbcHGvmJXOoJVHA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.12", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", + "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", @@ -1355,6 +1514,517 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.12.tgz", + "integrity": "sha512-+qYq6LfbiGo97Zz9fioX83HCiIYYFNs8zAsVCMQrIakoNYylIzWuoD/anAD3UzvvR6cnswmfRFJFq/zYYq/k7Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.7", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.4", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.4", + "@radix-ui/react-portal": "1.1.6", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-roving-focus": "1.1.7", + "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz", + "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.4.tgz", + "integrity": "sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.4.tgz", + "integrity": "sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.7.tgz", + "integrity": "sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.4.tgz", + "integrity": "sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.4.tgz", + "integrity": "sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.6.tgz", + "integrity": "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", + "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.7.tgz", + "integrity": "sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", + "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz", @@ -1670,6 +2340,39 @@ } } }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-escape-keydown": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", diff --git a/challenge-3-frontend/package.json b/challenge-3-frontend/package.json index f4b780e..3594373 100644 --- a/challenge-3-frontend/package.json +++ b/challenge-3-frontend/package.json @@ -11,6 +11,7 @@ "dependencies": { "@hookform/resolvers": "^3.9.1", "@radix-ui/react-dialog": "^1.1.3", + "@radix-ui/react-dropdown-menu": "^2.1.12", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-select": "^2.1.4", "@radix-ui/react-separator": "^1.1.1", From 800d25d72c67f8782044234d17cc410987b155df Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Thu, 24 Apr 2025 01:58:03 +0800 Subject: [PATCH 8/9] Add gitignore --- .gitignore | 49 ++++++++++++++++++++++++++++++ challenge-1-vesting/.gitignore | 50 +++++++++++++++++++++++++++++++ challenge-2-yield-farm/.gitignore | 50 +++++++++++++++++++++++++++++++ challenge-3-frontend/.gitignore | 25 ++++++++++++---- 4 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fcdec4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.vscode + +# Docker +postgres_data/ + +# local env files +.env*.local diff --git a/challenge-1-vesting/.gitignore b/challenge-1-vesting/.gitignore index e8c12ff..1f53b4d 100644 --- a/challenge-1-vesting/.gitignore +++ b/challenge-1-vesting/.gitignore @@ -15,3 +15,53 @@ node_modules # Hardhat Ignition default folder for deployments against a local node ignition/deployments/chain-31337 + +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.vscode + +# Docker +postgres_data/ + +# local env files +.env*.local diff --git a/challenge-2-yield-farm/.gitignore b/challenge-2-yield-farm/.gitignore index e8c12ff..1f53b4d 100644 --- a/challenge-2-yield-farm/.gitignore +++ b/challenge-2-yield-farm/.gitignore @@ -15,3 +15,53 @@ node_modules # Hardhat Ignition default folder for deployments against a local node ignition/deployments/chain-31337 + +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.vscode + +# Docker +postgres_data/ + +# local env files +.env*.local diff --git a/challenge-3-frontend/.gitignore b/challenge-3-frontend/.gitignore index fd3dbb5..2fcdec4 100644 --- a/challenge-3-frontend/.gitignore +++ b/challenge-3-frontend/.gitignore @@ -1,10 +1,14 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz +node_modules +.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions # testing /coverage @@ -24,9 +28,10 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +.pnpm-debug.log* -# local env files -.env*.local +# env files (can opt-in for committing if needed) +.env # vercel .vercel @@ -34,3 +39,11 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +.vscode + +# Docker +postgres_data/ + +# local env files +.env*.local From 8b1412ead1c159099dbda61a985864378e2f7de3 Mon Sep 17 00:00:00 2001 From: Syntax Surge Date: Thu, 24 Apr 2025 01:58:57 +0800 Subject: [PATCH 9/9] chore: remove .DS_Store files and ignore them --- .DS_Store | Bin 8196 -> 0 bytes challenge-1-vesting/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 challenge-1-vesting/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 42f9a04bfc87f2d4eb9450da68fc3483cf0f1b1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMU2GIp6u#fI&{;dmw53`Ywq0EnqHSoQK(zt4?SfznMYccN7AUj3BORH}l%3fv zP#PP9Mg!3|f1gY=8l(82M13$3G5)?7LP!V?sEL{wO?)yjQR2CCX9+Df@&ds)H@WxB zIrp4<=bSy?y?b^UW9Tg8S{SQijLFnFRV!)uhsNi5zoJQjq?{YLJ&hjWM07_si?|7OLTkRLQ%{VvUAY1bIxR^Q?PTcZ<`Vm5o)9AlBubd)h&&&mbSL(#@JL#^SafIvDVhs>FJ1E z8(-PJV|1T2ZaYVXhX_sycJmyMk6pdHmRsx+?v^D>?w$|al_NJ*D#|Td|G>atdT4l5 zRST}M!?1MEEE-AOGu%;ikxTw>$vXD-yip{-2eXb<7`JJrDyv)B0x26EmX$IO3GHm& zv8?TmYkH<*52a1A8F40Kj z_@cV#9e3Z`yypH5T@QBmo>8l+YZlik$}mOQ_RL32Bi}!1=De{X-OUx_?+D{dfrAfaiy`L;c}>-A0;Hoejp!QM%l8+OO^ywN*JX&6X1lhps*NY0Hm z=1k8iP4vlh7N4SHuNHmT^Pk4=y((k_9G7$hw(U$;0Zj5XYo8<#7lS? zui|YS$2<4{pW{n>g|Bf27w|2TQa)df5+53~0Odo&Pn|hweTERZELyL`N`RdUS(bfo|R4o&1Q?xB2s}M}PvOYq1Rm%liuXIErD)X6W zGP+47%&GFV1H)N%f&IXKWq%MDDxsnlccKYv(2kAhMh~U`cI?0ic49YyfJ0y(#$jUu z2XTmCFpbCX6rRRW9K$mNg%=16915@Db-aN$@h;xO3A~SwaT2HSDLx}`e1miN9zWr? zIVrzwPResATxH4^Q+eBR?7d`B0?_Qmt_4>n7Oms;|Hk=$|GySnhiirr2qSQVL;&T< zRI-y^X1=3`*V-}ad#Llq^=1V1O=$4TaiV@XPV~|rhV&dGRc@0A=!~Ewp?2MW2)ORw RyuG4R))}ng5CmN2d-@3z)Pv~3s}*E%53e>Vr``D++hrQ*DvH3@$>jz zl8R#!Jc+pX;N_RRKMnb%fV zWS2NPlQIdbeh{9=fW(T`a;f%W)sU-%8fkg)D zdT3+)Kl%RsznDZLVt^RwoRBvV_L7KgP~fZl+jU|gy3GX*-T6hkbQ;$2WB ZU>Dc{bPbjo!2&`T0Yw8f#K5mI@BxlBO~3#E