Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add WalletConnect integration for Ledger devices #720

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9114e0e
feat: add WalletConnect integration for Ledger devices
devin-ai-integration[bot] Feb 6, 2025
1313fb3
Merge branch 'master-polymorpher' into devin/1738830151-fix-ledger-si…
polymorpher Feb 6, 2025
4e02ae5
fix: update WalletConnect integration to use wagmi contract methods
devin-ai-integration[bot] Feb 6, 2025
085b220
chore: update babel config to support optional chaining
devin-ai-integration[bot] Feb 7, 2025
5e3867e
Merge branch 'master' into devin/1738830151-fix-ledger-signing
polymorpher Feb 12, 2025
258e27e
Reapply "fix: update chromedriver and add M1/M2 Mac support (#718)"
polymorpher Feb 12, 2025
e624b4e
chore: update @wagmi/core and viem dependencies
devin-ai-integration[bot] Feb 12, 2025
35fbbac
chore: remove @babel/polyfill import in favor of babel useBuiltIns usage
devin-ai-integration[bot] Feb 12, 2025
b36cc73
feat: add wagmi configuration for Harmony Mainnet
devin-ai-integration[bot] Feb 12, 2025
9f19746
feat: add contract interaction utilities using wagmi
devin-ai-integration[bot] Feb 12, 2025
b6328e0
fix: use numeric decimals instead of string unit for parseUnits
devin-ai-integration[bot] Feb 12, 2025
cbd48e1
fix: add TypeScript types for contract interaction utilities
devin-ai-integration[bot] Feb 12, 2025
128db1e
fix: handle undefined amount in contract interactions
devin-ai-integration[bot] Feb 12, 2025
f85cd48
fix: restore parseUnits usage for amount conversion
devin-ai-integration[bot] Feb 12, 2025
975a4b2
chore: resolve merge conflicts in yarn.lock
devin-ai-integration[bot] Feb 12, 2025
4653e59
remove some dependencies
polymorpher Feb 12, 2025
5c61134
add back package
polymorpher Feb 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ DEFAULT_CHAIN_TITLE=mainnet
MOCK_API_URL=
DEFAULT_NETWORK=harmony
MAX_ATTEMPTS=5
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
1 change: 1 addition & 0 deletions frontend/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ DEFAULT_NETWORK=harmony
DEFAULT_CHAIN_TITLE=mainnet
MAX_ATTEMPTS=5
GOOGLE_ANALYTICS_UID=UA-122659990-7
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
12 changes: 11 additions & 1 deletion frontend/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ module.exports = {
[
"@vue/cli-plugin-babel/preset",
{
useBuiltIns: "entry"
useBuiltIns: "usage",
corejs: 3,
targets: {
node: "16.20.2",
browsers: [
"last 2 Chrome versions",
"last 2 Firefox versions",
"last 2 Safari versions",
"last 2 Edge versions"
]
}
}
]
],
Expand Down
14 changes: 10 additions & 4 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@
"@dicebear/avatars-jdenticon-sprites": "1.1.4",
"@harmony-js/core": "^0.1.36",
"@ledgerhq/hw-transport-webhid": "^5.7.0",
"@ledgerhq/hw-transport-webusb": "4.74.1",
"@metamask/detect-provider": "1.2.0",
"@wagmi/vue": "^0.1.9",
"@reown/appkit": "^1.6.6",
"@reown/appkit-adapter-wagmi": "^1.6.6",
"@tanstack/vue-query": "^4.36.1",
"@safe-global/safe-apps-provider": "^0.16.0",
"@safe-global/safe-apps-react-sdk": "^4.6.4",
"@safe-global/safe-apps-sdk": "^7.10.1",
"@wagmi/core": "2.16.4",
"apollo-boost": "^0.4.3",
"apollo-cache-inmemory": "^1.6.2",
"autosize": "^4.0.2",
Expand All @@ -51,7 +55,7 @@
"camelcase": "^5.3.0",
"chart.js": "2.9.4",
"chartjs-plugin-labels": "1.1.0",
"core-js": "^3.6.5",
"core-js": "3",
"crypto-js": "^3.1.9-1",
"jdenticon": "2.2.0",
"lodash.groupby": "4.6.0",
Expand All @@ -66,6 +70,7 @@
"query-string": "6.11.0",
"timezone-mock": "^1.0.8",
"tslib": "1.10.0",
"viem": "2.x",
"vue": "^2.6.10",
"vue-chartjs": "3.5.1",
"vue-class-component": "^7.0.2",
Expand All @@ -90,6 +95,7 @@
"@capacitor/ios": "1.1.1",
"@types/jest": "^23.1.4",
"@types/ledgerhq__hw-transport-webusb": "4.70.1",
"@ledgerhq/hw-transport-webusb": "4.74.1",
"@types/lodash": "4.14.178",
"@types/lodash.uniqby": "4.7.6",
"@typescript-eslint/eslint-plugin": "^4.18.0",
Expand All @@ -110,7 +116,7 @@
"browserstack-local": "^1.4.2",
"chai": "^4.2.0",
"chai-string": "^1.5.0",
"chromedriver": "76.0.0",
"chromedriver": "133.0.0",
"copy-webpack-plugin": "6.0.0",
"cross-env": "^5.2.0",
"csp-webpack-plugin": "^2.0.2",
Expand All @@ -137,7 +143,7 @@
"vue-template-compiler": "^2.6.10"
},
"engines": {
"node": ">=16.20.2"
"node": ">=20"
},
"homepage": "https://lunie.io",
"license": "Apache-2.0",
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/ActionModal/components/ActionModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,8 @@ const SIGN_METHODS = {
MATHWALLET: `mathwallet`,
ONEWALLET: `onewallet`,
METAMASK: `metamask`,
MULTISIG: 'multisig'
MULTISIG: 'multisig',
WALLETCONNECT: 'walletconnect'
}

const signMethodOptions = {
Expand Down Expand Up @@ -1302,4 +1303,4 @@ export default {
top: 0;
}
} */
</style>
</style>
8 changes: 4 additions & 4 deletions frontend/src/components/common/TmSessionExisting.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
/>
<LiSession
v-if="!isMobileApp && !isIframe"
id="use-ledger-nano"
id="use-ledger-walletconnect"
icon="vpn_key"
title="Use Ledger Nano (Doesn't work for ledger app version 1.15.0)"
route="ledger"
title="Use Ledger via WalletConnect"
route="walletconnect"
/>

<LiSession
Expand All @@ -36,7 +36,7 @@
title="Use Harmony Multisig"
route="multisig"
/>

<LiSession
v-if="!isMobileApp && !isIframe"
id="use-onewallet"
Expand Down
76 changes: 76 additions & 0 deletions frontend/src/components/common/TmSessionWalletConnect.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<template>
<SessionFrame>
<div class="session-container">
<h2 class="session-title">Use Ledger with WalletConnect</h2>
<div class="session-main">
<p>Connect your Ledger device through Ledger Live using WalletConnect</p>
<div v-if="error" class="error-message">{{ error }}</div>
<TmBtn value="Connect Wallet" @click.native="signIn()" />
</div>
</div>
</SessionFrame>
</template>

<script>
import SessionFrame from "common/SessionFrame"
import { sessionType } from "src/ActionModal/components/ActionModal"
import { initWalletConnect } from "scripts/walletconnect-utils"
import { toBech32 } from "@harmony-js/crypto"
import TmBtn from "common/TmBtn"

export default {
name: 'session-walletconnect',
components: {
SessionFrame,
TmBtn
},
data: () => ({
modal: null,
error: null
}),
mounted() {
// TODO: Replace with actual project ID from cloud.reown.com
this.modal = initWalletConnect('harmony-staking-dashboard')
},
methods: {
async signIn() {
try {
this.error = null
const accounts = await this.modal.getAccounts()
if (accounts.length === 0) {
this.error = "No accounts found. Please connect your wallet."
return
}

this.$store.dispatch("signIn", {
sessionType: sessionType.WALLETCONNECT,
address: toBech32(accounts[0])
})

this.$router.push('/')
} catch (ex) {
console.error("WalletConnect error:", ex)
this.error = ex.message || "Failed to connect wallet"
}
}
}
}
</script>

<style scoped>
.session-title {
padding: 0 1rem;
margin: 0;
}

.session-main {
padding: 1rem;
text-align: center;
}

.error-message {
color: red;
margin: 1rem 0;
font-size: 0.9rem;
}
</style>
1 change: 0 additions & 1 deletion frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { tooltipStyles, focusElement, focusParentLast } from "src/directives"
import App from "./App.vue"
import init from "./initializeApp"
import { getURLParams } from "./scripts/url"
import "@babel/polyfill"
import SvgIcon from 'vue-svgicon'
// @ts-ignore
import Loading from 'vue-loading-overlay';
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ export default [
feature: "Session",
},
},
{
path: `/walletconnect`,
name: `walletconnect`,
components: {
session: require(`./components/common/TmSessionWalletConnect`).default,
},
meta: {
feature: "Session",
},
},
{
path: `/multisig`,
name: `multisig`,
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/scripts/walletconnect-utils/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { http, createConfig } from '@wagmi/core'

const HARMONY_MAINNET = {
id: 1666600000,
name: 'Harmony Mainnet',
network: 'harmony',
nativeCurrency: { name: 'ONE', symbol: 'ONE', decimals: 18 },
rpcUrls: {
default: { http: ['https://api.harmony.one'] },
public: { http: ['https://api.harmony.one'] },
},
blockExplorers: {
default: { name: 'Explorer', url: 'https://explorer.harmony.one/' },
},
}

export const config = createConfig({
chains: [HARMONY_MAINNET],
transports: {
[HARMONY_MAINNET.id]: http(),
},
})
103 changes: 103 additions & 0 deletions frontend/src/scripts/walletconnect-utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { createAppKit } from '@reown/appkit/vue'
import { mainnet } from '@reown/appkit/networks'
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
import { toBech32 } from "@harmony-js/crypto"
import { writeContract, simulateContract } from '@wagmi/core'
import { parseEther } from 'viem'
import { abi } from '../metamask-utils/staking-abi'

const STAKING_CONTRACT = '0x00000000000000000000000000000000000000FC'

const HARMONY_MAINNET = {
id: 1666600000,
name: 'Harmony Mainnet',
network: 'harmony',
nativeCurrency: { name: 'ONE', symbol: 'ONE', decimals: 18 },
rpcUrls: {
default: { http: ['https://api.harmony.one'] },
public: { http: ['https://api.harmony.one'] },
},
blockExplorers: {
default: { name: 'Explorer', url: 'https://explorer.harmony.one/' },
},
}

export const initWalletConnect = (projectId) => {
const metadata = {
name: 'Harmony Staking',
description: 'Harmony Staking Dashboard',
url: window.location.origin,
icons: ['https://staking.harmony.one/icons/icon-512x512.png']
}

const wagmiAdapter = new WagmiAdapter({
networks: [HARMONY_MAINNET],
projectId
})

return createAppKit({
adapters: [wagmiAdapter],
networks: [HARMONY_MAINNET],
projectId,
metadata,
features: {
analytics: true
}
})
}

export const processWalletConnectMessage = async (transactionData, fee, networkConfig) => {
const { gasEstimate } = fee

const baseRequest = {
address: STAKING_CONTRACT,
abi,
chainId: networkConfig.chain_id,
gas: BigInt(gasEstimate) + BigInt(20000),
}

switch (transactionData.type) {
case "MsgSend":
return writeContract({
...baseRequest,
functionName: 'Delegate',
args: [
transactionData.delegatorAddress,
transactionData.validatorAddress,
parseEther(transactionData.amount.toString())
]
})

case "MsgDelegate":
return writeContract({
...baseRequest,
functionName: 'Delegate',
args: [
transactionData.delegatorAddress,
transactionData.validatorAddress,
parseEther(transactionData.amount.toString())
]
})

case "MsgUndelegate":
return writeContract({
...baseRequest,
functionName: 'Undelegate',
args: [
transactionData.delegatorAddress,
transactionData.validatorAddress,
parseEther(transactionData.amount.toString())
]
})

case "MsgWithdrawDelegationReward":
return writeContract({
...baseRequest,
functionName: 'CollectRewards',
args: [transactionData.delegatorAddress]
})

default:
throw new Error(`Unknown transaction type: ${transactionData.type}`)
}
}
Loading