mirror of
				https://github.com/Instadapp/assembly.git
				synced 2024-07-29 22:37:06 +00:00 
			
		
		
		
	
						commit
						af78644f67
					
				
							
								
								
									
										12
									
								
								assets/icons/ledger.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								assets/icons/ledger.svg
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 35"> | ||||
|   <defs/> | ||||
|   <defs/> | ||||
|   <g id="prefix__Group_26536" data-name="Group 26536" transform="translate(-80 -205)"> | ||||
|     <path id="prefix__Shape" d="M23.588 0h-16v21.583h21.6v-16A5.585 5.585 0 0023.588 0z" class="prefix__cls-1" transform="translate(85.739 205)"/> | ||||
|     <path id="prefix__Path_8749" d="M8.342 0H5.585A5.585 5.585 0 000 5.585v2.757h8.342z" class="prefix__cls-1" data-name="Path 8749" transform="translate(80 205)"/> | ||||
|     <path id="prefix__Rectangle-path" d="M0 7.59h8.342v8.342H0z" class="prefix__cls-1" transform="translate(80 210.739)"/> | ||||
|     <path id="prefix__Path_8750" d="M15.18 23.451h2.757a5.585 5.585 0 005.585-5.6V15.18H15.18z" class="prefix__cls-1" data-name="Path 8750" transform="translate(91.478 216.478)"/> | ||||
|     <path id="prefix__Path_8751" d="M7.59 15.18h8.342v8.342H7.59z" class="prefix__cls-1" data-name="Path 8751" transform="translate(85.739 216.478)"/> | ||||
|     <path id="prefix__Path_8752" d="M0 15.18v2.757a5.585 5.585 0 005.585 5.585h2.757V15.18z" class="prefix__cls-1" data-name="Path 8752" transform="translate(80 216.478)"/> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
|  | @ -23,6 +23,7 @@ | |||
|         <button | ||||
|           class="w-full px-6 py-3 text-left flex items-center h-[80px] border border-[#DBE5F4] rounded-[4px] text-lg text-[#374253] font-semibold hover:bg-background-light" | ||||
|           v-for="(wallet, key) in wallets" | ||||
|           :disabled="connecting" | ||||
|           :key="key" | ||||
|           @click="connect(wallet.connector)" | ||||
|         > | ||||
|  | @ -71,10 +72,12 @@ import { computed, defineComponent, ref } from '@nuxtjs/composition-api' | |||
| import Input from '~/components/common/input/Input.vue' | ||||
| import { useModal } from '~/composables/useModal' | ||||
| import { useWeb3 } from '@instadapp/vue-web3' | ||||
| import { injected } from '~/connectors' | ||||
| import { injected, ledger } from '~/connectors' | ||||
| import { SUPPORTED_WALLETS } from '~/constant/wallet' | ||||
| import ButtonCTA from '../../common/input/ButtonCTA.vue' | ||||
| import ButtonCTAOutlined from '../../common/input/ButtonCTAOutlined.vue' | ||||
| import { Network, useNetwork } from '~/composables/useNetwork' | ||||
| import { useNotification } from '~/composables/useNotification' | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   props: { | ||||
|  | @ -87,11 +90,27 @@ export default defineComponent({ | |||
|   setup() { | ||||
|     const { close } = useModal() | ||||
|     const { activate } = useWeb3() | ||||
|     const { activeNetworkId } = useNetwork() | ||||
|     const { showError, showAwaiting, closeAll } = useNotification() | ||||
|     const connecting = ref(false) | ||||
| 
 | ||||
|     const connect = async (connector) => { | ||||
|       await activate(connector, console.log) | ||||
|       connecting.value = true | ||||
| 
 | ||||
|       showAwaiting("Connecting...") | ||||
| 
 | ||||
|       try { | ||||
|         await activate(connector, undefined, true) | ||||
|         connecting.value = false | ||||
|         close() | ||||
|         closeAll() | ||||
|       } catch (error) { | ||||
|         closeAll() | ||||
|         showError("", error.message) | ||||
|       } | ||||
| 
 | ||||
|       connecting.value = false | ||||
| 
 | ||||
|       close() | ||||
|     } | ||||
|     const isMetamask = computed(() => process.server ? false : window.ethereum && window.ethereum.isMetaMask) | ||||
| 
 | ||||
|  | @ -102,6 +121,10 @@ export default defineComponent({ | |||
|         return null | ||||
|       } | ||||
| 
 | ||||
|       if (wallet.connector === ledger && activeNetworkId.value !== Network.Mainnet) { | ||||
|         return null | ||||
|       } | ||||
| 
 | ||||
|       return wallet | ||||
|     }).filter(Boolean)) | ||||
| 
 | ||||
|  | @ -111,6 +134,7 @@ export default defineComponent({ | |||
|       wallets, | ||||
|       isMetamask, | ||||
|       injected, | ||||
|       connecting, | ||||
|     } | ||||
|   }, | ||||
| }) | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ import { InjectedConnector } from "@web3-react/injected-connector"; | |||
| import { WalletConnectConnector } from "@web3-react/walletconnect-connector"; | ||||
| import { PortisConnector } from "@web3-react/portis-connector"; | ||||
| import { WalletLinkConnector } from "@web3-react/walletlink-connector"; | ||||
| // import { LedgerConnector } from "@web3-react/ledger-connector";
 | ||||
| import { LedgerConnector } from "./ledger-connector"; | ||||
| 
 | ||||
| import INSTADAPP_LOGO_URL from "~/assets/logo/instadapp-logo-icon.svg?inline"; | ||||
| 
 | ||||
|  | @ -44,3 +46,20 @@ if (process.client) { | |||
| } | ||||
| 
 | ||||
| export { gnosisSafe }; | ||||
| 
 | ||||
| const POLLING_INTERVAL = 12000; | ||||
| 
 | ||||
| export enum LedgerDerivationPath { | ||||
|   "Legacy" = "44'/60'/0'/x", | ||||
|   "LedgerLive" = "44'/60'/x'/0/0" | ||||
| } | ||||
| 
 | ||||
| export const ledger = new LedgerConnector({ | ||||
|   chainId: 1, | ||||
|   url: `https://mainnet.infura.io/v3/${process.env.INFURA_ID}`, | ||||
|   requestTimeoutMs: 60000, | ||||
|   pollingInterval: POLLING_INTERVAL, | ||||
|   baseDerivationPath: "44'/60'/x'/0/0", | ||||
|   accountsOffset: 0, | ||||
|   accountsLength: 1 | ||||
| }); | ||||
|  |  | |||
							
								
								
									
										102
									
								
								connectors/ledger-connector.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								connectors/ledger-connector.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | |||
| // https://github.com/aave/aave-ui/blob/e0602e3560b2aabf86e8afd29db20c0ee8c249fc/src/libs/web3-data-provider/web3-providers/connectors/ledger-connector.ts
 | ||||
| 
 | ||||
| import { ConnectorUpdate } from '@web3-react/types'; | ||||
| import { AbstractConnector } from '@web3-react/abstract-connector'; | ||||
| import Web3ProviderEngine from 'web3-provider-engine'; | ||||
| import { RPCSubprovider } from '@0x/subproviders/lib/src/subproviders/rpc_subprovider'; // https://github.com/0xProject/0x-monorepo/issues/1400
 | ||||
| import createLedgerSubprovider from './ledger-subprovider'; | ||||
| import TransportU2F from '@ledgerhq/hw-transport-u2f'; | ||||
| import webUsbTransport from '@ledgerhq/hw-transport-webusb'; | ||||
| import type Transport from '@ledgerhq/hw-transport'; | ||||
| 
 | ||||
| interface LedgerConnectorArguments { | ||||
|   chainId: number; | ||||
|   url: string; | ||||
|   pollingInterval?: number; | ||||
|   requestTimeoutMs?: number; | ||||
|   baseDerivationPath?: string; | ||||
|   accountsOffset?: number; | ||||
|   accountsLength: number; | ||||
| } | ||||
| 
 | ||||
| const getTransport = async (): Promise<Transport> => { | ||||
|   if (await webUsbTransport.isSupported()) { | ||||
|     return await webUsbTransport.create(); | ||||
|   } | ||||
|   return await TransportU2F.create(); | ||||
| }; | ||||
| 
 | ||||
| export class LedgerConnector extends AbstractConnector { | ||||
|   private readonly chainId: number; | ||||
|   private readonly url: string; | ||||
|   private readonly pollingInterval?: number; | ||||
|   private readonly requestTimeoutMs?: number; | ||||
|   private readonly baseDerivationPath?: string; | ||||
|   private readonly accountsOffset?: number; | ||||
|   private readonly accountsLength: number; | ||||
| 
 | ||||
|   private provider: any; | ||||
| 
 | ||||
|   constructor({ | ||||
|     chainId, | ||||
|     url, | ||||
|     pollingInterval, | ||||
|     requestTimeoutMs, | ||||
|     baseDerivationPath, | ||||
|     accountsOffset = 0, | ||||
|     accountsLength = 1, | ||||
|   }: LedgerConnectorArguments) { | ||||
|     super({ supportedChainIds: [chainId] }); | ||||
| 
 | ||||
|     this.chainId = chainId; | ||||
|     this.url = url; | ||||
|     this.requestTimeoutMs = requestTimeoutMs; | ||||
|     this.baseDerivationPath = baseDerivationPath; | ||||
|     this.pollingInterval = pollingInterval; | ||||
|     this.accountsOffset = accountsOffset; | ||||
|     this.accountsLength = accountsLength; | ||||
|   } | ||||
| 
 | ||||
|   public async activate(): Promise<ConnectorUpdate> { | ||||
|     if (!this.provider) { | ||||
|       const engine = new Web3ProviderEngine({ pollingInterval: this.pollingInterval }); | ||||
|       const ledgerProvider = await createLedgerSubprovider(getTransport, { | ||||
|         networkId: this.chainId, | ||||
|         paths: this.baseDerivationPath ? [this.baseDerivationPath] : undefined, | ||||
|         accountsLength: this.accountsLength, | ||||
|         accountsOffset: this.accountsOffset, | ||||
|       }); | ||||
|       engine.addProvider(ledgerProvider); | ||||
|       engine.addProvider(new RPCSubprovider(this.url, this.requestTimeoutMs)); | ||||
|       this.provider = engine; | ||||
|       this.provider.start(); | ||||
|     } | ||||
|     const account = await this.getAccount(); | ||||
|     return { provider: this.provider, chainId: this.chainId, account }; | ||||
|   } | ||||
| 
 | ||||
|   public async getProvider(): Promise<Web3ProviderEngine> { | ||||
|     return this.provider; | ||||
|   } | ||||
| 
 | ||||
|   public async getChainId(): Promise<number> { | ||||
|     return this.chainId; | ||||
|   } | ||||
| 
 | ||||
|   public async getAccount(): Promise<string> { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       this.provider._providers[0].getAccounts(function (error: any, result: string[]) { | ||||
|         if (error) { | ||||
|           return reject(error); | ||||
|         } | ||||
|         return resolve(result[0]); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   public deactivate() { | ||||
|     if (this.provider) { | ||||
|       this.provider.stop(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										138
									
								
								connectors/ledger-subprovider.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								connectors/ledger-subprovider.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,138 @@ | |||
| import AppEth from '@ledgerhq/hw-app-eth'; | ||||
| import type Transport from '@ledgerhq/hw-transport'; | ||||
| import HookedWalletSubprovider from 'web3-provider-engine/subproviders/hooked-wallet'; | ||||
| import stripHexPrefix from 'strip-hex-prefix'; | ||||
| import { Transaction as EthereumTx } from 'ethereumjs-tx'; | ||||
| 
 | ||||
| function makeError(msg: string, id: string) { | ||||
|   const err: any = new Error(msg); | ||||
|   err.id = id; | ||||
|   return err; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  */ | ||||
| type SubproviderOptions = { | ||||
|   // refer to https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
 | ||||
|   networkId: number; | ||||
|   // derivation path schemes (with a x in the path)
 | ||||
|   paths?: string[]; | ||||
|   // should use actively validate on the device
 | ||||
|   askConfirm?: boolean; | ||||
|   // number of accounts to derivate
 | ||||
|   accountsLength?: number; | ||||
|   // offset index to use to start derivating the accounts
 | ||||
|   accountsOffset?: number; | ||||
| }; | ||||
| 
 | ||||
| const defaultOptions = { | ||||
|   networkId: 1, // mainnet
 | ||||
|   paths: ["44'/60'/x'/0/0", "44'/60'/0'/x"], // ledger live derivation path
 | ||||
|   askConfirm: false, | ||||
|   accountsLength: 1, | ||||
|   accountsOffset: 0, | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Create a HookedWalletSubprovider for Ledger devices. | ||||
|  */ | ||||
| export default async function createLedgerSubprovider( | ||||
|   getTransport: () => Promise<Transport>, | ||||
|   options?: SubproviderOptions | ||||
| ) { | ||||
|   if (options && 'path' in options) { | ||||
|     throw new Error( | ||||
|       "@ledgerhq/web3-subprovider: path options was replaced by paths. example: paths: [\"44'/60'/x'/0/0\"]" | ||||
|     ); | ||||
|   } | ||||
|   const { networkId, paths, askConfirm, accountsLength, accountsOffset } = { | ||||
|     ...defaultOptions, | ||||
|     ...options, | ||||
|   }; | ||||
| 
 | ||||
|   if (!paths.length) { | ||||
|     throw new Error('paths must not be empty'); | ||||
|   } | ||||
| 
 | ||||
|   const addressToPathMap: any = {}; | ||||
|   const transport = await getTransport(); | ||||
| 
 | ||||
|   async function getAccounts() { | ||||
|     const eth = new AppEth(transport); | ||||
|     const addresses: any = {}; | ||||
|     for (let i = accountsOffset; i < accountsOffset + accountsLength; i++) { | ||||
|       const x = Math.floor(i / paths.length); | ||||
|       const pathIndex = i - paths.length * x; | ||||
|       const path = paths[pathIndex].replace('x', String(x)); | ||||
|       const address = await eth.getAddress(path, askConfirm, false); | ||||
|       addresses[path] = address.address; | ||||
|       addressToPathMap[address.address.toLowerCase()] = path; | ||||
|     } | ||||
|     return addresses; | ||||
|   } | ||||
| 
 | ||||
|   async function signPersonalMessage(msgData: any) { | ||||
|     const path = addressToPathMap[msgData.from.toLowerCase()]; | ||||
|     if (!path) throw new Error("address unknown '" + msgData.from + "'"); | ||||
|     const eth = new AppEth(transport); | ||||
|     const result = await eth.signPersonalMessage(path, stripHexPrefix(msgData.data)); | ||||
|     const v = parseInt(result.v, 10) - 27; | ||||
|     let vHex = v.toString(16); | ||||
|     if (vHex.length < 2) { | ||||
|       vHex = `0${v}`; | ||||
|     } | ||||
|     return `0x${result.r}${result.s}${vHex}`; | ||||
|   } | ||||
| 
 | ||||
|   async function signTransaction(txData: any) { | ||||
|     const path = addressToPathMap[txData.from.toLowerCase()]; | ||||
|     if (!path) throw new Error("address unknown '" + txData.from + "'"); | ||||
|     const eth = new AppEth(transport); | ||||
|     const tx = new EthereumTx(txData, { chain: networkId }); | ||||
| 
 | ||||
|     // Set the EIP155 bits
 | ||||
|     tx.raw[6] = Buffer.from([networkId]); // v
 | ||||
|     tx.raw[7] = Buffer.from([]); // r
 | ||||
|     tx.raw[8] = Buffer.from([]); // s
 | ||||
| 
 | ||||
|     // Pass hex-rlp to ledger for signing
 | ||||
|     const result = await eth.signTransaction(path, tx.serialize().toString('hex')); | ||||
| 
 | ||||
|     // Store signature in transaction
 | ||||
|     tx.v = Buffer.from(result.v, 'hex'); | ||||
|     tx.r = Buffer.from(result.r, 'hex'); | ||||
|     tx.s = Buffer.from(result.s, 'hex'); | ||||
| 
 | ||||
|     // EIP155: v should be chain_id * 2 + {35, 36}
 | ||||
|     const signedChainId = Math.floor((tx.v[0] - 35) / 2); | ||||
|     const validChainId = networkId & 0xff; // FIXME this is to fixed a current workaround that app don't support > 0xff
 | ||||
|     if (signedChainId !== validChainId) { | ||||
|       throw makeError( | ||||
|         'Invalid networkId signature returned. Expected: ' + networkId + ', Got: ' + signedChainId, | ||||
|         'InvalidNetworkId' | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return `0x${tx.serialize().toString('hex')}`; | ||||
|   } | ||||
| 
 | ||||
|   const subprovider = new HookedWalletSubprovider({ | ||||
|     getAccounts: (callback: (err: any, res: any) => void) => { | ||||
|       getAccounts() | ||||
|         .then((res) => callback(null, Object.values(res))) | ||||
|         .catch((err) => callback(err, null)); | ||||
|     }, | ||||
|     signPersonalMessage: (txData: any, callback: (err: any, res: any) => void) => { | ||||
|       signPersonalMessage(txData) | ||||
|         .then((res) => callback(null, res)) | ||||
|         .catch((err) => callback(err, null)); | ||||
|     }, | ||||
|     signTransaction: (txData: any, callback: (err: any, res: any) => void) => { | ||||
|       signTransaction(txData) | ||||
|         .then((res) => callback(null, res)) | ||||
|         .catch((err) => callback(err, null)); | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   return subprovider; | ||||
| } | ||||
|  | @ -1,10 +1,11 @@ | |||
| import { AbstractConnector } from '@web3-react/abstract-connector' | ||||
| import { injected, walletconnect, portis, walletlink } from '~/connectors' | ||||
| import { injected, walletconnect, portis, walletlink, ledger } from '~/connectors' | ||||
| 
 | ||||
| import METAMASK_ICON_URL from '~/assets/icons/metamask.svg?inline' | ||||
| import WALLETCONNECT_ICON_URL from '~/assets/icons/wallet-connect-icon.svg?inline' | ||||
| import PORTIS_ICON_URL from '~/assets/icons/portis.svg?inline' | ||||
| import COINBASE_ICON_URL from '~/assets/icons/coinbase.svg?inline' | ||||
| import LEDGER_ICON_URL from '~/assets/icons/ledger.svg?inline' | ||||
| 
 | ||||
| interface WalletInfo { | ||||
|   connector?: AbstractConnector; | ||||
|  | @ -34,5 +35,10 @@ export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = { | |||
|       name: 'Coinbase Wallet', | ||||
|       iconURL: COINBASE_ICON_URL, | ||||
|     }, | ||||
|     LEDGER: { | ||||
|       connector: ledger, | ||||
|       name: 'Ledger', | ||||
|       iconURL: LEDGER_ICON_URL, | ||||
|     }, | ||||
|   } | ||||
|    | ||||
|  | @ -13,6 +13,7 @@ | |||
|     "@gnosis.pm/safe-apps-web3-react": "^0.6.2", | ||||
|     "@gnosis.pm/safe-apps-web3modal": "^2.0.0", | ||||
|     "@instadapp/vue-web3": "^0.3.0", | ||||
|     "@ledgerhq/hw-transport-webusb": "^6.6.0", | ||||
|     "@nuxtjs/axios": "^5.13.6", | ||||
|     "@nuxtjs/composition-api": "^0.27.0", | ||||
|     "@portis/web3": "^4.0.5", | ||||
|  | @ -22,6 +23,7 @@ | |||
|     "@walletconnect/web3-provider": "^1.4.1", | ||||
|     "@web3-react/core": "^6.1.9", | ||||
|     "@web3-react/injected-connector": "^6.0.7", | ||||
|     "@web3-react/ledger-connector": "^6.1.9", | ||||
|     "@web3-react/portis-connector": "^6.1.9", | ||||
|     "@web3-react/walletconnect-connector": "^6.2.4", | ||||
|     "@web3-react/walletlink-connector": "^6.2.3", | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Sowmay Jain
						Sowmay Jain