1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Introduce QR based signer into MetaMask (#12065)

* support qr based signer

* add CSP for fire fox

* get QR Hardware wallet name from device

* fix qrHardware state missing in runtime

* support qr based signer sign transaction

* refine Request Signature modal ui

* remove feature toggle

* refine ui

* fix notification is closing even there is a pending qr hardware transaction

* add chinese translation, refine ui, fix qr process was breaking in some case

* support import accounts by pubkeys

* refine qr-based wallet ui and fix bugs

* update @keystonehq/metamask-airgapped-keyring to fix that the signing hd path was inconsistent in some edge case

* fix: avoid unnecessay navigation, fix ci

* refactor qr-hardware-popover with @zxing/browser

* update lavamoat policy, remove firefox CSP

* refine qr reader ui, ignore unnecessary warning display

* code refactor, use async functions insteads promise

Co-authored-by: Soralit <soralitria@gmail.com>
This commit is contained in:
Aaron Chen 2021-11-24 01:28:39 +08:00 committed by GitHub
parent fbc6f4e603
commit a931316a53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1567 additions and 47 deletions

View File

@ -1,4 +1,49 @@
{ {
"QRHardwareInvalidTransactionTitle": {
"message": "Error"
},
"QRHardwareMismatchedSignId": {
"message": "Incongruent transaction data. Please check the transaction details."
},
"QRHardwarePubkeyAccountOutOfRange": {
"message": "No more accounts. If you would like to access another account unlisted below, please reconnect your hardware wallet and select it."
},
"QRHardwareScanInstructions": {
"message": "Place the QR code in front of your camera. The screen is blurred, but it will not affect the reading."
},
"QRHardwareSignRequestCancel": {
"message": "Reject"
},
"QRHardwareSignRequestDescription": {
"message": "After youve signed with your wallet, click on 'Get Signature' to receive the signature"
},
"QRHardwareSignRequestGetSignature": {
"message": "Get Signature"
},
"QRHardwareSignRequestSubtitle": {
"message": "Scan the QR code with your wallet"
},
"QRHardwareSignRequestTitle": {
"message": "Request Signature"
},
"QRHardwareUnknownQRCodeTitle": {
"message": "Error"
},
"QRHardwareUnknownWalletQRCode": {
"message": "Invalid QR code. Please scan the sync QR code of the hardware wallet."
},
"QRHardwareWalletImporterTitle": {
"message": "Scan QR Code"
},
"QRHardwareWalletSteps1Description": {
"message": "Connect an airgapped hardware wallet that communicates through QR-codes. Officially supported airgapped hardware wallets include:"
},
"QRHardwareWalletSteps1Title": {
"message": "QR-based HW Wallet"
},
"QRHardwareWalletSteps2Description": {
"message": "AirGap Vault & Ngrave (Coming Soon)"
},
"about": { "about": {
"message": "About" "message": "About"
}, },
@ -1306,6 +1351,12 @@
"message": "JSON File", "message": "JSON File",
"description": "format for importing an account" "description": "format for importing an account"
}, },
"keystone": {
"message": "Keystone"
},
"keystoneTutorial": {
"message": " (Tutorials)"
},
"knownAddressRecipient": { "knownAddressRecipient": {
"message": "Known contract address." "message": "Known contract address."
}, },

View File

@ -1,4 +1,46 @@
{ {
"QRHardwareInvalidTransactionTitle": {
"message": "非法交易"
},
"QRHardwareMismatchedSignId": {
"message": "扫描的签名二维码不属于当前交易,请检查交易详情后重试。"
},
"QRHardwarePubkeyAccountOutOfRange": {
"message": "暂无更多账户,若想切换到其他账户,请在硬件钱包中选择想要的账户重新同步。"
},
"QRHardwareScanInstructions": {
"message": "为了保护您的隐私,屏幕是模糊的,但不影响对二维码的读取。"
},
"QRHardwareSignRequestCancel": {
"message": "拒绝该交易"
},
"QRHardwareSignRequestDescription": {
"message": "硬件钱包扫描上方二维码完成签名后,点击“获取签名”按钮扫描已签名的二维码"
},
"QRHardwareSignRequestGetSignature": {
"message": "获取签名"
},
"QRHardwareSignRequestSubtitle": {
"message": "用硬件钱包扫描二维码"
},
"QRHardwareSignRequestTitle": {
"message": "获取签名"
},
"QRHardwareUnknownQRCodeTitle": {
"message": "非法二维码"
},
"QRHardwareUnknownWalletQRCode": {
"message": "请扫描硬件钱包的同步二维码。"
},
"QRHardwareWalletImporterTitle": {
"message": "扫描二维码"
},
"QRHardwareWalletSteps1Description": {
"message": "该类硬件钱包通过二维码实现通讯交互,做到完全脱网。官方支持的钱包有:"
},
"QRHardwareWalletSteps2Description": {
"message": "AirGap Vault & Ngrave (即将上线)"
},
"about": { "about": {
"message": "关于" "message": "关于"
}, },
@ -835,6 +877,12 @@
"message": "JSON 文件", "message": "JSON 文件",
"description": "format for importing an account" "description": "format for importing an account"
}, },
"keystone": {
"message": "铠石钱包"
},
"keystoneTutorial": {
"message": " (使用教程)"
},
"knownAddressRecipient": { "knownAddressRecipient": {
"message": "已知接收方地址。" "message": "已知接收方地址。"
}, },

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="297px" height="101px" viewBox="0 0 297 101" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>png-chahua</title>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="png-chahua" transform="translate(1.000000, 0.000000)">
<g id="P-1">
<g id="png-key" transform="translate(0.000000, 22.000000)">
<circle id="oval" stroke="#3098DC" fill="#D9F0FF" cx="32" cy="32" r="32"></circle>
<g id="png_logo" transform="translate(15.850003, 11.617735)">
<path d="M13.4677287,26.0869239 C13.6814978,26.2099569 13.8780148,26.3607837 14.0521451,26.5354635 L20.8323207,33.3370367 C22.2800205,34.7893053 22.3263651,37.1092074 20.9740009,38.6170536 L20.8237527,38.7754247 C20.8235032,38.7756734 20.8232537,38.7759221 20.8230041,38.7761707 L19.8377351,39.7578004 C18.7942353,40.7974458 17.2102004,41.0696737 15.8794816,40.438055 L8.37983921,36.8783886 C6.63920381,36.052205 5.89789413,33.9713871 6.7240777,32.2307517 C6.76351709,32.1476593 6.80622708,32.066159 6.85210754,31.986442 L9.6565953,27.1136614 C10.425485,25.7777203 12.1317875,25.3180342 13.4677287,26.0869239 Z M16.4259709,0.276152586 C16.7491885,0.460847609 17.0173036,0.728398213 17.2026794,1.05122584 L18.5283228,3.35980278 C19.1466826,4.43666186 19.1460308,5.76104031 18.5266115,6.83729026 L3.68360588,32.6272236 C3.39176095,33.1343083 2.7441005,33.3087947 2.23701577,33.0169497 C2.07620665,32.9243985 1.94252531,32.7912356 1.8493499,32.6307873 C1.23868219,31.579216 0.773046665,30.4498818 0.465104842,29.2734937 L0.222743083,28.3476321 C-0.230049649,26.6178897 0.0132040067,24.7795765 0.900309637,23.2271354 L13.5700193,1.05505383 C14.1435809,0.0513169242 15.422234,-0.297409042 16.4259709,0.276152586 Z M22.314756,10.7149284 C22.7424128,10.9610537 23.0971746,11.3161563 23.3428892,11.7440492 L26.2513718,16.8089476 C27.0189573,18.1456385 26.5576062,19.8514916 25.2209153,20.6190771 C24.7979802,20.8619446 24.3187864,20.9897452 23.8310788,20.9897452 L17.4043388,20.9897452 C16.0556092,20.9897452 14.9622479,19.8963838 14.9622479,18.5476542 C14.9622479,18.1201149 15.0744902,17.7000673 15.2877511,17.3295143 L18.5036388,11.7417258 C19.2725074,10.4057726 20.9788028,9.94605968 22.314756,10.7149284 Z" id="combine" fill="#000000"></path>
<path d="M31.1744255,25.3821635 C31.8384048,26.538444 31.6433147,27.9964878 30.6987072,28.937535 L27.4928953,32.1312641 C26.2639847,33.3541704 24.2764867,33.3507256 23.0518226,32.1235666 L17.3441941,26.3978915 C16.6640589,25.7156057 16.6658022,24.6111453 17.348088,23.9310101 C17.6750575,23.6050712 18.1179052,23.422047 18.5795817,23.422047 L27.7885506,23.422047 C29.1867243,23.422047 30.4781713,24.1696784 31.1744255,25.3821635 Z" id="path" fill="#2161FF"></path>
</g>
</g>
<path d="M83,17.0235687 L73.4978847,30 L64.0002855,17.0235687 L73.4973317,22.4634417 L73.4978847,22.4646936 L83,17.0235687 Z M73.4973317,11.0923553 L82.9936676,15.2782188 L73.4978847,20.720593 L64.0002855,15.2782188 L73.4973317,11.0923553 Z M73.4970462,0 L82.9936676,15.2781544 L73.4978847,11.0897901 L64,15.2781544 L73.4970462,0 Z" id="combine" fill="#3098DC" fill-rule="nonzero"></path>
<g id="png-key" transform="translate(64.000000, 77.000000)" fill-rule="nonzero">
<g id="Airgap_Logo_sideways_color-xs">
<path d="M9.79205788,0.504426266 C9.79205788,0.0716219234 9.47417837,-0.113865652 9.09272297,0.0716219234 C9.09272297,0.0716219234 7.56690134,0.937230609 6.35895922,1.49369334 C4.96028939,2.11198525 2.73513285,2.73027717 1.33646303,2.97759394 C0.446400413,3.16308152 0.319248611,3.71954424 0.25567271,4.21417778 C0.192096809,5.01795727 0.128520908,6.00722434 0.128520908,6.81100384 C0.0649450067,8.91319636 -0.125782697,11.0772181 0.128520908,13.1175814 C0.25567271,14.3541652 0.637128117,15.7762367 1.20931123,16.8891621 C2.09937384,18.4967211 3.24374006,19.9806217 4.57883399,21.2172055 C5.97750381,22.4537894 9.02914707,23.8758608 9.02914707,23.8758608 C9.47417837,24.0613484 9.79205788,23.8758608 9.79205788,23.3812273 L9.79205788,0.504426266 Z" id="path" fill="#3098DC"></path>
<path d="M12.2079421,23.4430564 C12.2079421,23.8758608 12.5258216,24.1231776 12.9708529,23.93769 C12.9708529,23.93769 16.0224962,22.5156186 17.421166,21.2790347 C18.8198358,20.0424509 19.9642021,18.5585503 20.7906888,16.9509913 C21.3628719,15.8380658 21.7443273,14.4159944 21.8714791,13.1794106 C22.1257827,11.1390473 21.935055,8.91319636 21.8714791,6.87283303 C21.8714791,6.06905353 21.8079032,5.07978646 21.7443273,4.27600697 C21.6807514,3.71954424 21.5535996,3.16308152 20.663537,3.03942313 C19.2648671,2.79210637 17.0397106,2.17381445 15.6410408,1.55552253 C14.4966746,0.999059801 12.907277,0.133451115 12.907277,0.133451115 C12.5258216,-0.113865652 12.2079421,0.0716219234 12.2079421,0.566255458 L12.2079421,23.4430564 L12.2079421,23.4430564 Z" id="path" fill="#3098DC"></path>
<path d="M9.85563378,1.18454738 L9.8542943,3.37036304 C9.98221357,3.6905739 10.2954143,3.90503182 10.6821205,3.90503182 L10.6821205,3.90503182 L11.3814554,3.90503182 L11.3814554,4.09051939 L9.85479032,4.08954738 L9.85479032,8.54154738 L12.8437011,8.54222121 L12.8437011,13.7358733 L9.85479032,13.7355474 L9.85479032,17.8165474 L12.907277,17.8166 C13.8609156,17.8166 14.5602505,18.5585503 14.5602505,19.424159 C14.5602505,20.2897677 13.8609156,21.031718 12.907277,21.031718 L9.85479032,21.0315474 L9.85563378,22.9484229 L9.53775427,22.8247645 C8.01193265,22.1446434 6.42253512,21.4026931 5.214593,20.2897677 C3.94307498,19.1768422 2.92586056,17.6929416 2.16294974,16.2708702 C1.65434253,15.2816031 1.33646303,14.0450193 1.20931123,12.9320938 C0.955007622,11.0772181 1.14573533,9.16051313 1.20931123,7.30563737 C1.20931123,6.3163703 1.27288713,5.26527404 1.40003893,4.21417778 C1.40003893,4.21417778 4.26095448,3.53405667 6.29538332,2.79210637 C7.50332544,2.29747283 8.64769166,1.8028393 9.85563378,1.18454738 Z" id="combine" fill="#FFFFFF"></path>
<path d="M12.3350939,1.18454738 C13.4158842,1.8028393 14.5602505,2.35930202 15.7681926,2.79210637 C17.7390455,3.59588586 20.663537,4.21417778 20.663537,4.21417778 L20.663537,4.21417778 L20.8542647,7.36746656 C20.9178406,9.22234232 21.0449924,11.1390473 20.8542647,12.993923 C20.7271129,14.1068485 20.4092334,15.3434323 19.9006262,16.3326994 C19.0741394,17.7547708 18.1205009,19.2386714 16.8489829,20.3515969 C15.5774649,21.4645223 14.0516433,22.2064726 12.5258216,22.8865937 L12.5258216,22.8865937 L12.3350939,23.0102521 L12.3343299,17.8165474 L8.32981215,17.8166 C7.56690134,17.8166 6.99471823,17.1983081 6.99471823,16.4563578 C6.99471823,15.7144075 7.63047724,15.1579447 8.32981215,15.1579447 L12.3343299,15.1575474 L12.3343299,12.3135474 L10.8728482,12.3138019 C10.3006651,12.3138019 9.85563378,11.8191684 9.85563378,11.2627056 C9.85563378,10.9631464 9.98460231,10.699423 10.1929484,10.5197638 L8.39338805,10.5207553 C7.24902183,10.5207553 6.35895922,9.59331747 6.42253512,8.54222121 C6.42253512,7.49112495 7.31259773,6.62551626 8.39338805,6.62551626 L12.3343299,6.62454738 Z" id="combine" fill="#3098DC"></path>
<path d="M14.5602505,6.56368707 L4.83313759,6.56368707 C4.07022678,6.56368707 3.49804367,5.94539515 3.49804367,5.20344485 C3.49804367,4.46149455 4.13380268,3.90503182 4.83313759,3.90503182 L14.5602505,3.90503182 C15.3231613,3.90503182 15.8953444,4.52332374 15.8953444,5.26527404 C15.8317685,6.00722434 15.2595854,6.56368707 14.5602505,6.56368707 Z" id="path" fill="#FFFFFF"></path>
<path d="M14.0516433,15.1579447 L4.83313759,15.1579447 C4.00665088,15.1579447 3.37089187,14.5396528 3.37089187,13.7358733 C3.37089187,12.9320938 4.00665088,12.3138019 4.83313759,12.3138019 L14.1152192,12.3138019 C14.9417059,12.3138019 15.5774649,12.9320938 15.5774649,13.7358733 C15.5774649,14.5396528 14.87813,15.1579447 14.0516433,15.1579447 L14.0516433,15.1579447 Z" id="path" fill="#FFFFFF"></path>
</g>
</g>
</g>
<g id="Group" transform="translate(187.000000, 17.000000)">
<g id="cp" stroke="#3098DC">
<path d="M10,0 L98,0 C100.209139,-4.05812251e-16 102,1.790861 102,4 L102,69 L102,69 L6,69 L6,4 C6,1.790861 7.790861,2.18216909e-15 10,0 Z" id="rectangle" fill="#FFFFFF"></path>
<rect id="rectangle" fill="#FFFFFF" x="10.5" y="4.5" width="87" height="57"></rect>
<rect id="rectangle" fill="#D9F0FF" x="0" y="66" width="108" height="7" rx="1"></rect>
</g>
<g id="5" transform="translate(35.000000, 17.000000)" fill-rule="nonzero">
<g id="4">
<path d="M35.7940726,0.865647154 L37.3076975,5.33187939 L36.0572619,11.2664578 L35.3130052,12.3918268 L36.2488109,13.108213 L35.0978726,14.1466087 L35.8822714,14.7050297 L34.478956,16.3199413 L37.0166977,24.0476925 L34.9619918,30.9406553 L27.2570674,28.8445942 L25.4588663,30.2992958 L22.489449,32.3310811 L15.4970666,32.3310811 L12.5415596,30.2997394 L10.7428766,28.8445914 L3.03908343,30.9403669 L0.996819559,24.0470926 L3.50539453,16.3206129 L2.11516087,14.7068706 L2.90215816,14.146619 L1.74643066,13.1038982 L2.66706954,12.387498 L1.5590983,11.552182 L0.692123065,5.33103555 L2.19338019,0.865960951 L13.1228366,4.89862071 L24.8771779,4.89862071 L35.7940726,0.865647154 Z" id="symbol" stroke="#3098DC" stroke-width="1.33783784"></path>
<path d="M20.5,27.5 L21,30 L16.5,30 L17,27.5 L20.5,27.5 Z M14.3439693,19.0321194 L15.8156414,22.0321194 L10.8156414,20.6010552 L14.3439693,19.0321194 Z M23.2820965,19.0321194 L26.8156414,20.6010552 L21.8156414,22.0321194 L23.2820965,19.0321194 Z" id="combine" fill="#3098DC"></path>
</g>
</g>
</g>
<g id="code" transform="translate(82.000000, 37.000000)">
<g id="p-2" transform="translate(26.000000, 0.000000)" fill="#3098DC" fill-rule="nonzero">
<g id="qr_code">
<path d="M1.94444444,11.6666667 L9.72222222,11.6666667 C10.7961092,11.6666667 11.6666667,10.7961092 11.6666667,9.72222222 L11.6666667,1.94444444 C11.6666667,0.870557431 10.7961092,0 9.72222222,0 L1.94444444,0 C0.870557431,0 0,0.870557431 0,1.94444444 L0,9.72222222 C0,10.7961092 0.870557431,11.6666667 1.94444444,11.6666667 Z M3.88888889,3.88888889 L7.77777778,3.88888889 L7.77777778,7.77777778 L3.88888889,7.77777778 L3.88888889,3.88888889 Z M0.972222222,19.4444444 L2.91666667,19.4444444 C3.45361017,19.4444444 3.88888889,19.0091657 3.88888889,18.4722222 L3.88888889,16.5277778 C3.88888889,15.9908343 3.45361017,15.5555556 2.91666667,15.5555556 L0.972222222,15.5555556 C0.435278715,15.5555556 0,15.9908343 0,16.5277778 L0,18.4722222 C0,19.0091657 0.435278715,19.4444444 0.972222222,19.4444444 Z M9.72222222,23.3333333 L1.94444444,23.3333333 C0.870557431,23.3333333 0,24.2038908 0,25.2777778 L0,33.0555556 C0,34.1294426 0.870557431,35 1.94444444,35 L9.72222222,35 C10.7961092,35 11.6666667,34.1294426 11.6666667,33.0555556 L11.6666667,25.2777778 C11.6666667,24.2038908 10.7961092,23.3333333 9.72222222,23.3333333 Z M7.77777778,31.1111111 L3.88888889,31.1111111 L3.88888889,27.2222222 L7.77777778,27.2222222 L7.77777778,31.1111111 Z M28.1944444,35 L34.0277778,35 C34.5647213,35 35,34.5647213 35,34.0277778 L35,28.1944444 C35,27.6575009 34.5647213,27.2222222 34.0277778,27.2222222 L32.0833333,27.2222222 C31.5463898,27.2222222 31.1111111,27.6575009 31.1111111,28.1944444 L31.1111111,31.1111111 L27.2222222,31.1111111 L27.2222222,34.0277778 C27.2222222,34.5647213 27.6575009,35 28.1944444,35 Z M33.0555556,0 L25.2777778,0 C24.2038908,0 23.3333333,0.870557431 23.3333333,1.94444444 L23.3333333,9.72222222 C23.3333333,10.7961092 24.2038908,11.6666667 25.2777778,11.6666667 L33.0555556,11.6666667 C34.1294426,11.6666667 35,10.7961092 35,9.72222222 L35,1.94444444 C35,0.870557431 34.1294426,0 33.0555556,0 Z M31.1111111,7.77777778 L27.2222222,7.77777778 L27.2222222,3.88888889 L31.1111111,3.88888889 L31.1111111,7.77777778 Z M8.75,15.5555556 C8.21305649,15.5555556 7.77777778,15.9908343 7.77777778,16.5277778 L7.77777778,18.4722222 C7.77777778,19.0091657 8.21305649,19.4444444 8.75,19.4444444 L15.5555556,19.4444444 L15.5555556,15.5555556 L8.75,15.5555556 Z M15.5555556,22.3611111 C15.5555556,22.8980546 15.9908343,23.3333333 16.5277778,23.3333333 L19.4444444,23.3333333 L19.4444444,26.25 C19.4444444,26.7869435 19.8797232,27.2222222 20.4166667,27.2222222 L23.3333333,27.2222222 L23.3333333,19.4444444 L15.5555556,19.4444444 L15.5555556,22.3611111 Z M15.5555556,32.0833333 L15.5555556,34.0277778 C15.5555556,34.5647213 15.9908343,35 16.5277778,35 L22.3611111,35 C22.8980546,35 23.3333333,34.5647213 23.3333333,34.0277778 L23.3333333,31.1111111 L16.5277778,31.1111111 C15.9908343,31.1111111 15.5555556,31.5463898 15.5555556,32.0833333 Z M34.0277778,15.5555556 L24.3055556,15.5555556 C23.768612,15.5555556 23.3333333,15.9908343 23.3333333,16.5277778 L23.3333333,19.4444444 L27.2222222,19.4444444 L27.2222222,22.3611111 C27.2222222,22.8980546 27.6575009,23.3333333 28.1944444,23.3333333 L30.1388889,23.3333333 C30.6758324,23.3333333 31.1111111,22.8980546 31.1111111,22.3611111 L31.1111111,19.4444444 L34.0277778,19.4444444 C34.5647213,19.4444444 35,19.0091657 35,18.4722222 L35,16.5277778 C35,15.9908343 34.5647213,15.5555556 34.0277778,15.5555556 Z M27.2222222,31.1111111 L27.2222222,27.2222222 L23.3333333,27.2222222 L23.3333333,31.1111111 L27.2222222,31.1111111 Z M16.5277778,7.77777778 L18.4722222,7.77777778 C19.0091657,7.77777778 19.4444444,7.34249906 19.4444444,6.80555556 L19.4444444,0.972222222 C19.4444444,0.435278715 19.0091657,0 18.4722222,0 L16.5277778,0 C15.9908343,0 15.5555556,0.435278715 15.5555556,0.972222222 L15.5555556,6.80555556 C15.5555556,7.34249906 15.9908343,7.77777778 16.5277778,7.77777778 Z M19.4444444,14.5833333 L19.4444444,12.6388889 C19.4444444,12.1019454 19.0091657,11.6666667 18.4722222,11.6666667 L16.5277778,11.6666667 C15.9908343,11.6666667 15.5555556,12.1019454 15.5555556,12.6388889 L15.5555556,15.5555556 L18.4722222,15.5555556 C19.0091657,15.5555556 19.4444444,15.1202768 19.4444444,14.5833333 Z" id="Icon-color"></path>
</g>
</g>
<g id="4" transform="translate(73.000000, 12.000000)">
<rect id="path" fill="#3098DC" x="0" y="5" width="14" height="1"></rect>
<polyline id="path" stroke="#3098DC" points="8 0 14 5.5 8 10.5"></polyline>
</g>
<g id="4" transform="translate(7.000000, 17.250000) scale(-1, 1) translate(-7.000000, -17.250000) translate(0.000000, 12.000000)">
<rect id="path" fill="#3098DC" x="0" y="5" width="14" height="1"></rect>
<polyline id="path" stroke="#3098DC" points="8 0 14 5.5 8 10.5"></polyline>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -16,6 +16,7 @@ export default class AppStateController extends EventEmitter {
onInactiveTimeout, onInactiveTimeout,
showUnlockRequest, showUnlockRequest,
preferencesStore, preferencesStore,
qrHardwareStore,
} = opts; } = opts;
super(); super();
@ -32,6 +33,7 @@ export default class AppStateController extends EventEmitter {
recoveryPhraseReminderLastShown: new Date().getTime(), recoveryPhraseReminderLastShown: new Date().getTime(),
showTestnetMessageInDropdown: true, showTestnetMessageInDropdown: true,
...initState, ...initState,
qrHardware: {},
}); });
this.timer = null; this.timer = null;
@ -48,6 +50,10 @@ export default class AppStateController extends EventEmitter {
} }
}); });
qrHardwareStore.subscribe((state) => {
this.store.updateState({ qrHardware: state });
});
const { preferences } = preferencesStore.getState(); const { preferences } = preferencesStore.getState();
this._setInactiveTimeout(preferences.autoLockTimeLimit); this._setInactiveTimeout(preferences.autoLockTimeLimit);
} }

View File

@ -15,6 +15,7 @@ import log from 'loglevel';
import TrezorKeyring from 'eth-trezor-keyring'; import TrezorKeyring from 'eth-trezor-keyring';
import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring'; import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring';
import LatticeKeyring from 'eth-lattice-keyring'; import LatticeKeyring from 'eth-lattice-keyring';
import { MetaMaskKeyring as QRHardwareKeyring } from '@keystonehq/metamask-airgapped-keyring';
import EthQuery from 'eth-query'; import EthQuery from 'eth-query';
import nanoid from 'nanoid'; import nanoid from 'nanoid';
import { ethErrors } from 'eth-rpc-errors'; import { ethErrors } from 'eth-rpc-errors';
@ -41,7 +42,10 @@ import {
SWAPS_CLIENT_ID, SWAPS_CLIENT_ID,
} from '../../shared/constants/swaps'; } from '../../shared/constants/swaps';
import { MAINNET_CHAIN_ID } from '../../shared/constants/network'; import { MAINNET_CHAIN_ID } from '../../shared/constants/network';
import { KEYRING_TYPES } from '../../shared/constants/hardware-wallets'; import {
DEVICE_NAMES,
KEYRING_TYPES,
} from '../../shared/constants/hardware-wallets';
import { UI_NOTIFICATIONS } from '../../shared/notifications'; import { UI_NOTIFICATIONS } from '../../shared/notifications';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import { MILLISECOND } from '../../shared/constants/time'; import { MILLISECOND } from '../../shared/constants/time';
@ -286,6 +290,8 @@ export default class MetamaskController extends EventEmitter {
}, },
}); });
this.qrHardwareKeyring = new QRHardwareKeyring();
this.appStateController = new AppStateController({ this.appStateController = new AppStateController({
addUnlockListener: this.on.bind(this, 'unlock'), addUnlockListener: this.on.bind(this, 'unlock'),
isUnlocked: this.isUnlocked.bind(this), isUnlocked: this.isUnlocked.bind(this),
@ -293,6 +299,7 @@ export default class MetamaskController extends EventEmitter {
onInactiveTimeout: () => this.setLocked(), onInactiveTimeout: () => this.setLocked(),
showUnlockRequest: opts.showUserConfirmation, showUnlockRequest: opts.showUserConfirmation,
preferencesStore: this.preferencesController.store, preferencesStore: this.preferencesController.store,
qrHardwareStore: this.qrHardwareKeyring.getMemStore(),
}); });
const currencyRateMessenger = this.controllerMessenger.getRestricted({ const currencyRateMessenger = this.controllerMessenger.getRestricted({
@ -432,6 +439,7 @@ export default class MetamaskController extends EventEmitter {
TrezorKeyring, TrezorKeyring,
LedgerBridgeKeyring, LedgerBridgeKeyring,
LatticeKeyring, LatticeKeyring,
QRHardwareKeyring,
]; ];
this.keyringController = new KeyringController({ this.keyringController = new KeyringController({
keyringTypes: additionalKeyrings, keyringTypes: additionalKeyrings,
@ -938,6 +946,28 @@ export default class MetamaskController extends EventEmitter {
this, this,
), ),
// qr hardware devices
submitQRHardwareCryptoHDKey: nodeify(
this.qrHardwareKeyring.submitCryptoHDKey,
this.qrHardwareKeyring,
),
submitQRHardwareCryptoAccount: nodeify(
this.qrHardwareKeyring.submitCryptoAccount,
this.qrHardwareKeyring,
),
cancelSyncQRHardware: nodeify(
this.qrHardwareKeyring.cancelSync,
this.qrHardwareKeyring,
),
submitQRHardwareSignature: nodeify(
this.qrHardwareKeyring.submitSignature,
this.qrHardwareKeyring,
),
cancelQRHardwareSignRequest: nodeify(
this.qrHardwareKeyring.cancelSignRequest,
this.qrHardwareKeyring,
),
// mobile // mobile
fetchInfoToSync: nodeify(this.fetchInfoToSync, this), fetchInfoToSync: nodeify(this.fetchInfoToSync, this),
@ -1647,13 +1677,16 @@ export default class MetamaskController extends EventEmitter {
async getKeyringForDevice(deviceName, hdPath = null) { async getKeyringForDevice(deviceName, hdPath = null) {
let keyringName = null; let keyringName = null;
switch (deviceName) { switch (deviceName) {
case 'trezor': case DEVICE_NAMES.TREZOR:
keyringName = TrezorKeyring.type; keyringName = TrezorKeyring.type;
break; break;
case 'ledger': case DEVICE_NAMES.LEDGER:
keyringName = LedgerBridgeKeyring.type; keyringName = LedgerBridgeKeyring.type;
break; break;
case 'lattice': case DEVICE_NAMES.QR:
keyringName = QRHardwareKeyring.type;
break;
case DEVICE_NAMES.LATTICE:
keyringName = LatticeKeyring.type; keyringName = LatticeKeyring.type;
break; break;
default: default:
@ -1670,7 +1703,7 @@ export default class MetamaskController extends EventEmitter {
if (hdPath && keyring.setHdPath) { if (hdPath && keyring.setHdPath) {
keyring.setHdPath(hdPath); keyring.setHdPath(hdPath);
} }
if (deviceName === 'lattice') { if (deviceName === DEVICE_NAMES.LATTICE) {
keyring.appName = 'MetaMask'; keyring.appName = 'MetaMask';
} }
keyring.network = this.networkController.getProviderConfig().type; keyring.network = this.networkController.getProviderConfig().type;
@ -1740,6 +1773,18 @@ export default class MetamaskController extends EventEmitter {
return true; return true;
} }
/**
* get hardware account label
*
* @return string label
* */
getAccountLabel(name, index, hdPathDescription) {
return `${name[0].toUpperCase()}${name.slice(1)} ${
parseInt(index, 10) + 1
} ${hdPathDescription || ''}`.trim();
}
/** /**
* Imports an account from a Trezor or Ledger device. * Imports an account from a Trezor or Ledger device.
* *
@ -1760,10 +1805,12 @@ export default class MetamaskController extends EventEmitter {
this.preferencesController.setAddresses(newAccounts); this.preferencesController.setAddresses(newAccounts);
newAccounts.forEach((address) => { newAccounts.forEach((address) => {
if (!oldAccounts.includes(address)) { if (!oldAccounts.includes(address)) {
const label = `${deviceName[0].toUpperCase()}${deviceName.slice(1)} ${ const label = this.getAccountLabel(
parseInt(index, 10) + 1 deviceName === DEVICE_NAMES.QR ? keyring.getName() : deviceName,
} ${hdPathDescription || ''}`.trim(); index,
// Set the account label to Trezor 1 / Ledger 1, etc hdPathDescription,
);
// Set the account label to Trezor 1 / Ledger 1 / QR Hardware 1, etc
this.preferencesController.setAccountLabel(address, label); this.preferencesController.setAccountLabel(address, label);
// Select the account // Select the account
this.preferencesController.setSelectedAddress(address); this.preferencesController.setSelectedAddress(address);
@ -2179,6 +2226,12 @@ export default class MetamaskController extends EventEmitter {
}); });
} }
case KEYRING_TYPES.QR: {
return Promise.reject(
new Error('QR hardware does not support eth_getEncryptionPublicKey.'),
);
}
default: { default: {
const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync( const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync(
msgParams, msgParams,

View File

@ -118,13 +118,14 @@ export default class ExtensionPlatform {
) { ) {
let extensionURL = extension.runtime.getURL('home.html'); let extensionURL = extension.runtime.getURL('home.html');
if (route) {
extensionURL += `#${route}`;
}
if (queryString) { if (queryString) {
extensionURL += `?${queryString}`; extensionURL += `?${queryString}`;
} }
if (route) {
extensionURL += `#${route}`;
}
this.openTab({ url: extensionURL }); this.openTab({ url: extensionURL });
if ( if (
getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND && getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND &&

View File

@ -412,6 +412,47 @@
"Intl.getCanonicalLocales": true "Intl.getCanonicalLocales": true
} }
}, },
"@keystonehq/base-eth-keyring": {
"packages": {
"@ethereumjs/tx": true,
"@keystonehq/bc-ur-registry-eth": true,
"buffer": true,
"ethereumjs-util": true,
"hdkey": true,
"uuid": true
}
},
"@keystonehq/bc-ur-registry": {
"globals": {
"define": true
},
"packages": {
"@ngraveio/bc-ur": true,
"bs58check": true,
"buffer": true
}
},
"@keystonehq/bc-ur-registry-eth": {
"packages": {
"@keystonehq/bc-ur-registry": true,
"buffer": true,
"ethereumjs-util": true,
"hdkey": true,
"uuid": true
}
},
"@keystonehq/metamask-airgapped-keyring": {
"packages": {
"@ethereumjs/tx": true,
"@keystonehq/base-eth-keyring": true,
"@keystonehq/bc-ur-registry-eth": true,
"@metamask/obs-store": true,
"buffer": true,
"events": true,
"rlp": true,
"uuid": true
}
},
"@material-ui/core": { "@material-ui/core": {
"globals": { "globals": {
"Image": true, "Image": true,
@ -619,6 +660,18 @@
"events": true "events": true
} }
}, },
"@ngraveio/bc-ur": {
"packages": {
"@apocentre/alias-sampling": true,
"assert": true,
"bignumber.js": true,
"buffer": true,
"cbor-sync": true,
"crc": true,
"jsbi": true,
"sha.js": true
}
},
"@popperjs/core": { "@popperjs/core": {
"globals": { "globals": {
"Element": true, "Element": true,
@ -738,6 +791,23 @@
"util": true "util": true
} }
}, },
"@zxing/browser": {
"globals": {
"HTMLElement": true,
"HTMLImageElement": true,
"HTMLVideoElement": true,
"URL.createObjectURL": true,
"clearTimeout": true,
"console.error": true,
"console.warn": true,
"document": true,
"navigator": true,
"setTimeout": true
},
"packages": {
"@zxing/library": true
}
},
"@zxing/library": { "@zxing/library": {
"globals": { "globals": {
"TextDecoder": true, "TextDecoder": true,
@ -975,6 +1045,9 @@
} }
}, },
"bn.js": { "bn.js": {
"globals": {
"Buffer": true
},
"packages": { "packages": {
"browser-resolve": true "browser-resolve": true
} }
@ -1099,6 +1172,14 @@
"buffer": true "buffer": true
} }
}, },
"cbor-sync": {
"globals": {
"define": true
},
"packages": {
"buffer": true
}
},
"cids": { "cids": {
"packages": { "packages": {
"buffer": true, "buffer": true,
@ -1191,6 +1272,11 @@
"is-buffer": true "is-buffer": true
} }
}, },
"crc": {
"packages": {
"buffer": true
}
},
"crc-32": { "crc-32": {
"globals": { "globals": {
"DO_NOT_EXPORT_CRC": true, "DO_NOT_EXPORT_CRC": true,
@ -2007,6 +2093,7 @@
"hdkey": { "hdkey": {
"packages": { "packages": {
"assert": true, "assert": true,
"bs58check": true,
"coinstring": true, "coinstring": true,
"crypto-browserify": true, "crypto-browserify": true,
"safe-buffer": true, "safe-buffer": true,
@ -2559,6 +2646,11 @@
"console.warn": true "console.warn": true
} }
}, },
"jsbi": {
"globals": {
"define": true
}
},
"json-rpc-engine": { "json-rpc-engine": {
"packages": { "packages": {
"@metamask/safe-event-emitter": true, "@metamask/safe-event-emitter": true,
@ -3769,6 +3861,17 @@
"define": true "define": true
} }
}, },
"qrcode.react": {
"globals": {
"Path2D": true,
"devicePixelRatio": true
},
"packages": {
"prop-types": true,
"qr.js": true,
"react": true
}
},
"rabin-wasm": { "rabin-wasm": {
"globals": { "globals": {
"Blob": true, "Blob": true,

View File

@ -412,6 +412,47 @@
"Intl.getCanonicalLocales": true "Intl.getCanonicalLocales": true
} }
}, },
"@keystonehq/base-eth-keyring": {
"packages": {
"@ethereumjs/tx": true,
"@keystonehq/bc-ur-registry-eth": true,
"buffer": true,
"ethereumjs-util": true,
"hdkey": true,
"uuid": true
}
},
"@keystonehq/bc-ur-registry": {
"globals": {
"define": true
},
"packages": {
"@ngraveio/bc-ur": true,
"bs58check": true,
"buffer": true
}
},
"@keystonehq/bc-ur-registry-eth": {
"packages": {
"@keystonehq/bc-ur-registry": true,
"buffer": true,
"ethereumjs-util": true,
"hdkey": true,
"uuid": true
}
},
"@keystonehq/metamask-airgapped-keyring": {
"packages": {
"@ethereumjs/tx": true,
"@keystonehq/base-eth-keyring": true,
"@keystonehq/bc-ur-registry-eth": true,
"@metamask/obs-store": true,
"buffer": true,
"events": true,
"rlp": true,
"uuid": true
}
},
"@material-ui/core": { "@material-ui/core": {
"globals": { "globals": {
"Image": true, "Image": true,
@ -619,6 +660,18 @@
"events": true "events": true
} }
}, },
"@ngraveio/bc-ur": {
"packages": {
"@apocentre/alias-sampling": true,
"assert": true,
"bignumber.js": true,
"buffer": true,
"cbor-sync": true,
"crc": true,
"jsbi": true,
"sha.js": true
}
},
"@popperjs/core": { "@popperjs/core": {
"globals": { "globals": {
"Element": true, "Element": true,
@ -738,6 +791,23 @@
"util": true "util": true
} }
}, },
"@zxing/browser": {
"globals": {
"HTMLElement": true,
"HTMLImageElement": true,
"HTMLVideoElement": true,
"URL.createObjectURL": true,
"clearTimeout": true,
"console.error": true,
"console.warn": true,
"document": true,
"navigator": true,
"setTimeout": true
},
"packages": {
"@zxing/library": true
}
},
"@zxing/library": { "@zxing/library": {
"globals": { "globals": {
"TextDecoder": true, "TextDecoder": true,
@ -975,6 +1045,9 @@
} }
}, },
"bn.js": { "bn.js": {
"globals": {
"Buffer": true
},
"packages": { "packages": {
"browser-resolve": true "browser-resolve": true
} }
@ -1099,6 +1172,14 @@
"buffer": true "buffer": true
} }
}, },
"cbor-sync": {
"globals": {
"define": true
},
"packages": {
"buffer": true
}
},
"cids": { "cids": {
"packages": { "packages": {
"buffer": true, "buffer": true,
@ -1191,6 +1272,11 @@
"is-buffer": true "is-buffer": true
} }
}, },
"crc": {
"packages": {
"buffer": true
}
},
"crc-32": { "crc-32": {
"globals": { "globals": {
"DO_NOT_EXPORT_CRC": true, "DO_NOT_EXPORT_CRC": true,
@ -2007,6 +2093,7 @@
"hdkey": { "hdkey": {
"packages": { "packages": {
"assert": true, "assert": true,
"bs58check": true,
"coinstring": true, "coinstring": true,
"crypto-browserify": true, "crypto-browserify": true,
"safe-buffer": true, "safe-buffer": true,
@ -2559,6 +2646,11 @@
"console.warn": true "console.warn": true
} }
}, },
"jsbi": {
"globals": {
"define": true
}
},
"json-rpc-engine": { "json-rpc-engine": {
"packages": { "packages": {
"@metamask/safe-event-emitter": true, "@metamask/safe-event-emitter": true,
@ -3769,6 +3861,17 @@
"define": true "define": true
} }
}, },
"qrcode.react": {
"globals": {
"Path2D": true,
"devicePixelRatio": true
},
"packages": {
"prop-types": true,
"qr.js": true,
"react": true
}
},
"rabin-wasm": { "rabin-wasm": {
"globals": { "globals": {
"Blob": true, "Blob": true,

View File

@ -412,6 +412,47 @@
"Intl.getCanonicalLocales": true "Intl.getCanonicalLocales": true
} }
}, },
"@keystonehq/base-eth-keyring": {
"packages": {
"@ethereumjs/tx": true,
"@keystonehq/bc-ur-registry-eth": true,
"buffer": true,
"ethereumjs-util": true,
"hdkey": true,
"uuid": true
}
},
"@keystonehq/bc-ur-registry": {
"globals": {
"define": true
},
"packages": {
"@ngraveio/bc-ur": true,
"bs58check": true,
"buffer": true
}
},
"@keystonehq/bc-ur-registry-eth": {
"packages": {
"@keystonehq/bc-ur-registry": true,
"buffer": true,
"ethereumjs-util": true,
"hdkey": true,
"uuid": true
}
},
"@keystonehq/metamask-airgapped-keyring": {
"packages": {
"@ethereumjs/tx": true,
"@keystonehq/base-eth-keyring": true,
"@keystonehq/bc-ur-registry-eth": true,
"@metamask/obs-store": true,
"buffer": true,
"events": true,
"rlp": true,
"uuid": true
}
},
"@material-ui/core": { "@material-ui/core": {
"globals": { "globals": {
"Image": true, "Image": true,
@ -619,6 +660,18 @@
"events": true "events": true
} }
}, },
"@ngraveio/bc-ur": {
"packages": {
"@apocentre/alias-sampling": true,
"assert": true,
"bignumber.js": true,
"buffer": true,
"cbor-sync": true,
"crc": true,
"jsbi": true,
"sha.js": true
}
},
"@popperjs/core": { "@popperjs/core": {
"globals": { "globals": {
"Element": true, "Element": true,
@ -738,6 +791,23 @@
"util": true "util": true
} }
}, },
"@zxing/browser": {
"globals": {
"HTMLElement": true,
"HTMLImageElement": true,
"HTMLVideoElement": true,
"URL.createObjectURL": true,
"clearTimeout": true,
"console.error": true,
"console.warn": true,
"document": true,
"navigator": true,
"setTimeout": true
},
"packages": {
"@zxing/library": true
}
},
"@zxing/library": { "@zxing/library": {
"globals": { "globals": {
"TextDecoder": true, "TextDecoder": true,
@ -975,6 +1045,9 @@
} }
}, },
"bn.js": { "bn.js": {
"globals": {
"Buffer": true
},
"packages": { "packages": {
"browser-resolve": true "browser-resolve": true
} }
@ -1099,6 +1172,14 @@
"buffer": true "buffer": true
} }
}, },
"cbor-sync": {
"globals": {
"define": true
},
"packages": {
"buffer": true
}
},
"cids": { "cids": {
"packages": { "packages": {
"buffer": true, "buffer": true,
@ -1191,6 +1272,11 @@
"is-buffer": true "is-buffer": true
} }
}, },
"crc": {
"packages": {
"buffer": true
}
},
"crc-32": { "crc-32": {
"globals": { "globals": {
"DO_NOT_EXPORT_CRC": true, "DO_NOT_EXPORT_CRC": true,
@ -2007,6 +2093,7 @@
"hdkey": { "hdkey": {
"packages": { "packages": {
"assert": true, "assert": true,
"bs58check": true,
"coinstring": true, "coinstring": true,
"crypto-browserify": true, "crypto-browserify": true,
"safe-buffer": true, "safe-buffer": true,
@ -2559,6 +2646,11 @@
"console.warn": true "console.warn": true
} }
}, },
"jsbi": {
"globals": {
"define": true
}
},
"json-rpc-engine": { "json-rpc-engine": {
"packages": { "packages": {
"@metamask/safe-event-emitter": true, "@metamask/safe-event-emitter": true,
@ -3769,6 +3861,17 @@
"define": true "define": true
} }
}, },
"qrcode.react": {
"globals": {
"Path2D": true,
"devicePixelRatio": true
},
"packages": {
"prop-types": true,
"qr.js": true,
"react": true
}
},
"rabin-wasm": { "rabin-wasm": {
"globals": { "globals": {
"Blob": true, "Blob": true,

View File

@ -106,6 +106,8 @@
"@ethereumjs/tx": "^3.2.1", "@ethereumjs/tx": "^3.2.1",
"@formatjs/intl-relativetimeformat": "^5.2.6", "@formatjs/intl-relativetimeformat": "^5.2.6",
"@fortawesome/fontawesome-free": "^5.13.0", "@fortawesome/fontawesome-free": "^5.13.0",
"@keystonehq/bc-ur-registry-eth": "^0.6.8",
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.28.0", "@metamask/contract-metadata": "^1.28.0",
"@metamask/controllers": "^20.0.0", "@metamask/controllers": "^20.0.0",
@ -117,11 +119,13 @@
"@metamask/obs-store": "^5.0.0", "@metamask/obs-store": "^5.0.0",
"@metamask/post-message-stream": "^4.0.0", "@metamask/post-message-stream": "^4.0.0",
"@metamask/providers": "^8.1.1", "@metamask/providers": "^8.1.1",
"@ngraveio/bc-ur": "^1.1.6",
"@popperjs/core": "^2.4.0", "@popperjs/core": "^2.4.0",
"@reduxjs/toolkit": "^1.6.2", "@reduxjs/toolkit": "^1.6.2",
"@sentry/browser": "^6.0.0", "@sentry/browser": "^6.0.0",
"@sentry/integrations": "^6.0.0", "@sentry/integrations": "^6.0.0",
"@zxing/library": "^0.8.0", "@zxing/browser": "^0.0.10",
"@zxing/library": "0.8.0",
"analytics-node": "^3.4.0-beta.3", "analytics-node": "^3.4.0-beta.3",
"await-semaphore": "^0.1.1", "await-semaphore": "^0.1.1",
"base32-encode": "^1.2.0", "base32-encode": "^1.2.0",
@ -181,6 +185,7 @@
"pump": "^3.0.0", "pump": "^3.0.0",
"punycode": "^2.1.1", "punycode": "^2.1.1",
"qrcode-generator": "1.4.1", "qrcode-generator": "1.4.1",
"qrcode.react": "^1.0.1",
"react": "^16.12.0", "react": "^16.12.0",
"react-dnd": "^3.0.2", "react-dnd": "^3.0.2",
"react-dnd-html5-backend": "^7.4.4", "react-dnd-html5-backend": "^7.4.4",
@ -206,6 +211,7 @@
"swappable-obj-proxy": "^1.1.0", "swappable-obj-proxy": "^1.1.0",
"textarea-caret": "^3.0.1", "textarea-caret": "^3.0.1",
"unicode-confusables": "^0.1.1", "unicode-confusables": "^0.1.1",
"uuid": "^8.3.2",
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"web3": "^0.20.7", "web3": "^0.20.7",
"web3-stream-provider": "^4.0.0" "web3-stream-provider": "^4.0.0"

View File

@ -1,5 +1,5 @@
/** /**
* Accounts can be instantiated from simple, HD or the two hardware wallet * Accounts can be instantiated from simple, HD or the multiple hardware wallet
* keyring types. Both simple and HD are treated as default but we do special * keyring types. Both simple and HD are treated as default but we do special
* case accounts managed by a hardware wallet. * case accounts managed by a hardware wallet.
*/ */
@ -7,6 +7,14 @@ export const KEYRING_TYPES = {
LEDGER: 'Ledger Hardware', LEDGER: 'Ledger Hardware',
TREZOR: 'Trezor Hardware', TREZOR: 'Trezor Hardware',
LATTICE: 'Lattice Hardware', LATTICE: 'Lattice Hardware',
QR: 'QR Hardware Wallet Device',
};
export const DEVICE_NAMES = {
LEDGER: 'ledger',
TREZOR: 'trezor',
QR: 'QR Hardware',
LATTICE: 'lattice',
}; };
/** /**

View File

@ -239,6 +239,7 @@ export default class AccountMenu extends Component {
case KEYRING_TYPES.TREZOR: case KEYRING_TYPES.TREZOR:
case KEYRING_TYPES.LEDGER: case KEYRING_TYPES.LEDGER:
case KEYRING_TYPES.LATTICE: case KEYRING_TYPES.LATTICE:
case KEYRING_TYPES.QR:
label = t('hardware'); label = t('hardware');
break; break;
case 'Simple Key Pair': case 'Simple Key Pair':

View File

@ -0,0 +1,217 @@
import React, { useEffect, useRef, useState } from 'react';
import log from 'loglevel';
import { URDecoder } from '@ngraveio/bc-ur';
import PropTypes from 'prop-types';
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../shared/constants/app';
import WebcamUtils from '../../../helpers/utils/webcam-utils';
import PageContainerFooter from '../../ui/page-container/page-container-footer/page-container-footer.component';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { SECOND } from '../../../../shared/constants/time';
import EnhancedReader from './enhanced-reader';
const READY_STATE = {
ACCESSING_CAMERA: 'ACCESSING_CAMERA',
NEED_TO_ALLOW_ACCESS: 'NEED_TO_ALLOW_ACCESS',
READY: 'READY',
};
const BaseReader = ({
isReadingWallet,
handleCancel,
handleSuccess,
setErrorTitle,
}) => {
const t = useI18nContext();
const [ready, setReady] = useState(READY_STATE.ACCESSING_CAMERA);
const [error, setError] = useState(null);
const [urDecoder, setURDecoder] = useState(new URDecoder());
let permissionChecker = null;
const mounted = useRef(false);
const reset = () => {
setReady(READY_STATE.ACCESSING_CAMERA);
setError(null);
setURDecoder(new URDecoder());
};
const checkEnvironment = async () => {
try {
const { environmentReady } = await WebcamUtils.checkStatus();
if (
!environmentReady &&
getEnvironmentType() !== ENVIRONMENT_TYPE_FULLSCREEN
) {
const currentUrl = new URL(window.location.href);
const currentHash = currentUrl.hash;
const currentRoute = currentHash ? currentHash.substring(1) : null;
global.platform.openExtensionInBrowser(currentRoute);
}
} catch (e) {
if (mounted.current) {
setError(e);
}
}
// initial attempt is required to trigger permission prompt
// eslint-disable-next-line no-use-before-define
return initCamera();
};
const checkPermissions = async () => {
try {
const { permissions } = await WebcamUtils.checkStatus();
if (permissions) {
// Let the video stream load first...
await new Promise((resolve) => setTimeout(resolve, SECOND * 2));
if (!mounted.current) {
return;
}
setReady(READY_STATE.READY);
} else if (mounted.current) {
// Keep checking for permissions
permissionChecker = setTimeout(checkPermissions, SECOND);
setReady(READY_STATE.NEED_TO_ALLOW_ACCESS);
}
} catch (e) {
if (mounted.current) {
setError(e);
}
}
};
const handleScan = (data) => {
try {
if (!data) {
return;
}
urDecoder.receivePart(data);
if (urDecoder.isComplete()) {
const result = urDecoder.resultUR();
handleSuccess(result).catch(setError);
}
} catch (e) {
if (isReadingWallet) {
setErrorTitle(t('QRHardwareUnknownQRCodeTitle'));
} else {
setErrorTitle(t('QRHardwareInvalidTransactionTitle'));
}
setError(new Error(t('unknownQrCode')));
}
};
const initCamera = () => {
try {
checkPermissions();
} catch (e) {
if (!mounted.current) {
return;
}
if (e.name === 'NotAllowedError') {
log.info(`Permission denied: '${e}'`);
setReady(READY_STATE.NEED_TO_ALLOW_ACCESS);
} else {
setError(e);
}
}
};
useEffect(() => {
mounted.current = true;
checkEnvironment();
return () => {
mounted.current = false;
clearTimeout(permissionChecker);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (ready === READY_STATE.READY) {
initCamera();
} else if (ready === READY_STATE.NEED_TO_ALLOW_ACCESS) {
checkPermissions();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ready]);
const tryAgain = () => {
clearTimeout(permissionChecker);
reset();
checkEnvironment();
};
const renderError = () => {
let title, msg;
if (error.type === 'NO_WEBCAM_FOUND') {
title = t('noWebcamFoundTitle');
msg = t('noWebcamFound');
} else if (error.message === t('unknownQrCode')) {
if (isReadingWallet) {
msg = t('QRHardwareUnknownWalletQRCode');
} else {
msg = t('unknownQrCode');
}
} else if (error.message === t('QRHardwareMismatchedSignId')) {
msg = t('QRHardwareMismatchedSignId');
} else {
title = t('unknownCameraErrorTitle');
msg = t('unknownCameraError');
}
return (
<>
<div className="qr-scanner__image">
<img src="images/webcam.svg" width="70" height="70" alt="" />
</div>
{title ? <div className="qr-scanner__title">{title}</div> : null}
<div className="qr-scanner__error">{msg}</div>
<PageContainerFooter
onCancel={() => {
setErrorTitle('');
handleCancel();
}}
onSubmit={() => {
setErrorTitle('');
tryAgain();
}}
cancelText={t('cancel')}
submitText={t('tryAgain')}
submitButtonType="confirm"
/>
</>
);
};
const renderVideo = () => {
let message;
if (ready === READY_STATE.ACCESSING_CAMERA) {
message = t('accessingYourCamera');
} else if (ready === READY_STATE.READY) {
message = t('QRHardwareScanInstructions');
} else if (ready === READY_STATE.NEED_TO_ALLOW_ACCESS) {
message = t('youNeedToAllowCameraAccess');
}
return (
<>
<div className="qr-scanner__content">
<EnhancedReader handleScan={handleScan} />
</div>
{message && <div className="qr-scanner__status">{message}</div>}
</>
);
};
return (
<div className="qr-scanner">{error ? renderError() : renderVideo()}</div>
);
};
BaseReader.propTypes = {
isReadingWallet: PropTypes.bool.isRequired,
handleCancel: PropTypes.func.isRequired,
handleSuccess: PropTypes.func.isRequired,
setErrorTitle: PropTypes.func.isRequired,
};
export default BaseReader;

View File

@ -0,0 +1,67 @@
import React, { useEffect, useMemo, useState } from 'react';
import { BarcodeFormat, DecodeHintType } from '@zxing/library';
import { BrowserQRCodeReader } from '@zxing/browser';
import log from 'loglevel';
import PropTypes from 'prop-types';
import { MILLISECOND } from '../../../../shared/constants/time';
import Spinner from '../../ui/spinner';
const EnhancedReader = ({ handleScan }) => {
const [canplay, setCanplay] = useState(false);
const codeReader = useMemo(() => {
const hint = new Map();
hint.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.QR_CODE]);
return new BrowserQRCodeReader(hint, {
delayBetweenScanAttempts: MILLISECOND * 100,
delayBetweenScanSuccess: MILLISECOND * 100,
});
}, []);
useEffect(() => {
const videoElem = document.getElementById('video');
const canplayListener = () => {
setCanplay(true);
};
videoElem.addEventListener('canplay', canplayListener);
const promise = codeReader.decodeFromVideoDevice(
undefined,
'video',
(result) => {
if (result) {
handleScan(result.getText());
}
},
);
return () => {
videoElem.removeEventListener('canplay', canplayListener);
promise
.then((controls) => {
if (controls) {
controls.stop();
}
})
.catch(log.info);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="qr-scanner__content__video-wrapper">
<video
id="video"
style={{
display: canplay ? 'block' : 'none',
width: '100%',
filter: 'blur(4px)',
}}
/>
{canplay ? null : <Spinner color="#F7C06C" />}
</div>
);
};
EnhancedReader.propTypes = {
handleScan: PropTypes.func.isRequired,
};
export default EnhancedReader;

View File

@ -0,0 +1,3 @@
import QRHardwarePopover from './qr-hardware-popover';
export default QRHardwarePopover;

View File

@ -0,0 +1,102 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getCurrentQRHardwareState } from '../../../selectors';
import Popover from '../../ui/popover';
import { useI18nContext } from '../../../hooks/useI18nContext';
import {
cancelSyncQRHardware as cancelSyncQRHardwareAction,
cancelQRHardwareSignRequest as cancelQRHardwareSignRequestAction,
cancelTx,
cancelPersonalMsg,
cancelMsg,
cancelTypedMsg,
} from '../../../store/actions';
import { MESSAGE_TYPE } from '../../../../shared/constants/app';
import QRHardwareWalletImporter from './qr-hardware-wallet-importer';
import QRHardwareSignRequest from './qr-hardware-sign-request';
const QRHardwarePopover = () => {
const t = useI18nContext();
const qrHardware = useSelector(getCurrentQRHardwareState);
const { sync, sign } = qrHardware;
const showWalletImporter = sync?.reading;
const showSignRequest = sign?.request;
const showPopover = showWalletImporter || showSignRequest;
const [errorTitle, setErrorTitle] = useState('');
const { txData } = useSelector((state) => {
return state.confirmTransaction;
});
// the confirmTransaction's life cycle is not consistent with QR hardware wallet;
// the confirmTransaction will change after the previous tx is confirmed or cancel,
// we want to block the changing by sign request id;
const _txData = useMemo(() => {
return txData;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sign?.request?.requestId]);
const dispatch = useDispatch();
const walletImporterCancel = useCallback(
() => dispatch(cancelSyncQRHardwareAction()),
[dispatch],
);
const signRequestCancel = useCallback(() => {
let action = cancelTx;
switch (_txData.type) {
case MESSAGE_TYPE.PERSONAL_SIGN: {
action = cancelPersonalMsg;
break;
}
case MESSAGE_TYPE.ETH_SIGN: {
action = cancelMsg;
break;
}
case MESSAGE_TYPE.ETH_SIGN_TYPED_DATA: {
action = cancelTypedMsg;
break;
}
default: {
action = cancelTx;
}
}
dispatch(action(_txData));
dispatch(cancelQRHardwareSignRequestAction());
}, [dispatch, _txData]);
const title = useMemo(() => {
let _title = '';
if (showSignRequest) {
_title = t('QRHardwareSignRequestTitle');
} else if (showWalletImporter) {
_title = t('QRHardwareWalletImporterTitle');
}
if (errorTitle !== '') {
_title = errorTitle;
}
return _title;
}, [showSignRequest, showWalletImporter, t, errorTitle]);
return showPopover ? (
<Popover
title={title}
onClose={showWalletImporter ? walletImporterCancel : signRequestCancel}
>
{showWalletImporter && (
<QRHardwareWalletImporter
handleCancel={walletImporterCancel}
setErrorTitle={setErrorTitle}
/>
)}
{showSignRequest && (
<QRHardwareSignRequest
setErrorTitle={setErrorTitle}
handleCancel={signRequestCancel}
request={sign.request}
/>
)}
</Popover>
) : null;
};
export default QRHardwarePopover;

View File

@ -0,0 +1,3 @@
import QRHardwareSignRequest from './qr-hardware-sign-request.component';
export default QRHardwareSignRequest;

View File

@ -0,0 +1,71 @@
import React, { useEffect, useMemo, useState } from 'react';
import QRCode from 'qrcode.react';
import { UR, UREncoder } from '@ngraveio/bc-ur';
import PropTypes from 'prop-types';
import Typography from '../../../ui/typography';
import Box from '../../../ui/box';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import {
ALIGN_ITEMS,
DISPLAY,
FLEX_DIRECTION,
TEXT_ALIGN,
} from '../../../../helpers/constants/design-system';
import { PageContainerFooter } from '../../../ui/page-container';
const Player = ({ type, cbor, cancelQRHardwareSignRequest, toRead }) => {
const t = useI18nContext();
const urEncoder = useMemo(
() => new UREncoder(new UR(Buffer.from(cbor, 'hex'), type), 400),
[cbor, type],
);
const [currentQRCode, setCurrentQRCode] = useState(urEncoder.nextPart());
useEffect(() => {
const id = setInterval(() => {
setCurrentQRCode(urEncoder.nextPart());
}, 100);
return () => {
clearInterval(id);
};
}, [urEncoder]);
return (
<>
<Box>
<Typography align={TEXT_ALIGN.CENTER}>
{t('QRHardwareSignRequestSubtitle')}
</Typography>
</Box>
<Box
paddingTop={4}
paddingBottom={4}
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.CENTER}
flexDirection={FLEX_DIRECTION.COLUMN}
>
<QRCode value={currentQRCode.toUpperCase()} size={250} />
</Box>
<Box paddingBottom={4} paddingLeft={4} paddingRight={4}>
<Typography align={TEXT_ALIGN.CENTER}>
{t('QRHardwareSignRequestDescription')}
</Typography>
</Box>
<PageContainerFooter
onCancel={cancelQRHardwareSignRequest}
onSubmit={toRead}
cancelText={t('QRHardwareSignRequestCancel')}
submitText={t('QRHardwareSignRequestGetSignature')}
submitButtonType="confirm"
/>
</>
);
};
Player.propTypes = {
type: PropTypes.string.isRequired,
cbor: PropTypes.string.isRequired,
cancelQRHardwareSignRequest: PropTypes.func.isRequired,
toRead: PropTypes.func.isRequired,
};
export default Player;

View File

@ -0,0 +1,45 @@
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { submitQRHardwareSignature } from '../../../../store/actions';
import Player from './player';
import Reader from './reader';
const QRHardwareSignRequest = ({ request, handleCancel, setErrorTitle }) => {
const [status, setStatus] = useState('play');
const toRead = useCallback(() => setStatus('read'), []);
const renderPlayer = () => {
const { payload } = request;
return (
<Player
type={payload.type}
cbor={payload.cbor}
cancelQRHardwareSignRequest={handleCancel}
toRead={toRead}
/>
);
};
const renderReader = () => {
return (
<Reader
cancelQRHardwareSignRequest={handleCancel}
submitQRHardwareSignature={submitQRHardwareSignature}
requestId={request.requestId}
setErrorTitle={setErrorTitle}
/>
);
};
if (status === 'play') return renderPlayer();
return renderReader();
};
QRHardwareSignRequest.propTypes = {
request: PropTypes.object.isRequired,
handleCancel: PropTypes.func.isRequired,
setErrorTitle: PropTypes.func.isRequired,
};
export default QRHardwareSignRequest;

View File

@ -0,0 +1,52 @@
import React from 'react';
import { ETHSignature } from '@keystonehq/bc-ur-registry-eth';
import * as uuid from 'uuid';
import PropTypes from 'prop-types';
import BaseReader from '../base-reader';
import { useI18nContext } from '../../../../hooks/useI18nContext';
const Reader = ({
submitQRHardwareSignature,
cancelQRHardwareSignRequest,
requestId,
setErrorTitle,
}) => {
const t = useI18nContext();
const cancel = () => {
cancelQRHardwareSignRequest();
};
const handleSuccess = async (ur) => {
if (ur.type === 'eth-signature') {
const ethSignature = ETHSignature.fromCBOR(ur.cbor);
const buffer = ethSignature.getRequestId();
const signId = uuid.stringify(buffer);
if (signId === requestId) {
return await submitQRHardwareSignature(signId, ur.cbor.toString('hex'));
}
setErrorTitle(t('QRHardwareInvalidTransactionTitle'));
throw new Error(t('QRHardwareMismatchedSignId'));
} else {
setErrorTitle(t('QRHardwareInvalidTransactionTitle'));
throw new Error(t('unknownQrCode'));
}
};
return (
<BaseReader
isReadingWallet={false}
handleCancel={cancel}
handleSuccess={handleSuccess}
setErrorTitle={setErrorTitle}
/>
);
};
Reader.propTypes = {
submitQRHardwareSignature: PropTypes.func.isRequired,
cancelQRHardwareSignRequest: PropTypes.func.isRequired,
requestId: PropTypes.string.isRequired,
setErrorTitle: PropTypes.func.isRequired,
};
export default Reader;

View File

@ -0,0 +1,3 @@
import QRHardwareWalletImporter from './qr-hardware-wallet-importer.component';
export default QRHardwareWalletImporter;

View File

@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
submitQRHardwareCryptoAccount,
submitQRHardwareCryptoHDKey,
} from '../../../../store/actions';
import BaseReader from '../base-reader';
import { useI18nContext } from '../../../../hooks/useI18nContext';
const QRHardwareWalletImporter = ({ handleCancel, setErrorTitle }) => {
const t = useI18nContext();
const handleSuccess = async (ur) => {
if (ur.type === 'crypto-hdkey') {
return await submitQRHardwareCryptoHDKey(ur.cbor.toString('hex'));
} else if (ur.type === 'crypto-account') {
return await submitQRHardwareCryptoAccount(ur.cbor.toString('hex'));
}
setErrorTitle(t('QRHardwareUnknownQRCodeTitle'));
throw new Error(t('unknownQrCode'));
};
return (
<BaseReader
isReadingWallet
handleCancel={handleCancel}
handleSuccess={handleSuccess}
setErrorTitle={setErrorTitle}
/>
);
};
QRHardwareWalletImporter.propTypes = {
handleCancel: PropTypes.func.isRequired,
setErrorTitle: PropTypes.func.isRequired,
};
export default QRHardwareWalletImporter;

View File

@ -11,7 +11,10 @@ import {
import { formatBalance } from '../../../helpers/utils/util'; import { formatBalance } from '../../../helpers/utils/util';
import { getMostRecentOverviewPage } from '../../../ducks/history/history'; import { getMostRecentOverviewPage } from '../../../ducks/history/history';
import { SECOND } from '../../../../shared/constants/time'; import { SECOND } from '../../../../shared/constants/time';
import { LEDGER_TRANSPORT_TYPES } from '../../../../shared/constants/hardware-wallets'; import {
DEVICE_NAMES,
LEDGER_TRANSPORT_TYPES,
} from '../../../../shared/constants/hardware-wallets';
import SelectHardware from './select-hardware'; import SelectHardware from './select-hardware';
import AccountList from './account-list'; import AccountList from './account-list';
@ -76,7 +79,11 @@ class ConnectHardwareForm extends Component {
} }
async checkIfUnlocked() { async checkIfUnlocked() {
for (const device of ['trezor', 'ledger', 'lattice']) { for (const device of [
DEVICE_NAMES.TREZOR,
DEVICE_NAMES.LEDGER,
DEVICE_NAMES.LATTICE,
]) {
const path = this.props.defaultHdPaths[device]; const path = this.props.defaultHdPaths[device];
const unlocked = await this.props.checkHardwareStatus(device, path); const unlocked = await this.props.checkHardwareStatus(device, path);
if (unlocked) { if (unlocked) {
@ -176,9 +183,22 @@ class ConnectHardwareForm extends Component {
this.setState({ this.setState({
error: this.context.t('ledgerTimeout'), error: this.context.t('ledgerTimeout'),
}); });
} else if (
errorMessage
.toLowerCase()
.includes(
'KeystoneError#pubkey_account.no_expected_account'.toLowerCase(),
)
) {
this.setState({
error: this.context.t('QRHardwarePubkeyAccountOutOfRange'),
});
} else if ( } else if (
errorMessage !== 'Window closed' && errorMessage !== 'Window closed' &&
errorMessage !== 'Popup closed' errorMessage !== 'Popup closed' &&
errorMessage
.toLowerCase()
.includes('KeystoneError#sync_cancel'.toLowerCase()) === false
) { ) {
this.setState({ this.setState({
error: errorMessage, error: errorMessage,

View File

@ -52,6 +52,7 @@
justify-content: center; justify-content: center;
border-radius: 5px; border-radius: 5px;
padding: 0; padding: 0;
margin-right: 15px;
&__img { &__img {
width: 95px; width: 95px;
@ -64,7 +65,6 @@
} }
&__btn:first-child { &__btn:first-child {
margin-right: 15px;
margin-left: 20px; margin-left: 20px;
} }

View File

@ -2,7 +2,10 @@ import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Button from '../../../components/ui/button'; import Button from '../../../components/ui/button';
import { LEDGER_TRANSPORT_TYPES } from '../../../../shared/constants/hardware-wallets'; import {
DEVICE_NAMES,
LEDGER_TRANSPORT_TYPES,
} from '../../../../shared/constants/hardware-wallets';
export default class SelectHardware extends Component { export default class SelectHardware extends Component {
static contextTypes = { static contextTypes = {
@ -30,9 +33,9 @@ export default class SelectHardware extends Component {
return ( return (
<button <button
className={classnames('hw-connect__btn', { className={classnames('hw-connect__btn', {
selected: this.state.selectedDevice === 'trezor', selected: this.state.selectedDevice === DEVICE_NAMES.TREZOR,
})} })}
onClick={(_) => this.setState({ selectedDevice: 'trezor' })} onClick={(_) => this.setState({ selectedDevice: DEVICE_NAMES.TREZOR })}
> >
<img <img
className="hw-connect__btn__img" className="hw-connect__btn__img"
@ -47,9 +50,9 @@ export default class SelectHardware extends Component {
return ( return (
<button <button
className={classnames('hw-connect__btn', { className={classnames('hw-connect__btn', {
selected: this.state.selectedDevice === 'lattice', selected: this.state.selectedDevice === DEVICE_NAMES.LATTICE,
})} })}
onClick={(_) => this.setState({ selectedDevice: 'lattice' })} onClick={(_) => this.setState({ selectedDevice: DEVICE_NAMES.LATTICE })}
> >
<img <img
className="hw-connect__btn__img" className="hw-connect__btn__img"
@ -64,9 +67,9 @@ export default class SelectHardware extends Component {
return ( return (
<button <button
className={classnames('hw-connect__btn', { className={classnames('hw-connect__btn', {
selected: this.state.selectedDevice === 'ledger', selected: this.state.selectedDevice === DEVICE_NAMES.LEDGER,
})} })}
onClick={(_) => this.setState({ selectedDevice: 'ledger' })} onClick={(_) => this.setState({ selectedDevice: DEVICE_NAMES.LEDGER })}
> >
<img <img
className="hw-connect__btn__img" className="hw-connect__btn__img"
@ -77,6 +80,23 @@ export default class SelectHardware extends Component {
); );
} }
renderConnectToQRButton() {
return (
<button
className={classnames('hw-connect__btn', {
selected: this.state.selectedDevice === DEVICE_NAMES.QR,
})}
onClick={(_) => this.setState({ selectedDevice: DEVICE_NAMES.QR })}
>
<img
className="hw-connect__btn__img"
src="images/qrcode-wallet-logo.svg"
alt="QRCode"
/>
</button>
);
}
renderButtons() { renderButtons() {
return ( return (
<> <>
@ -89,6 +109,7 @@ export default class SelectHardware extends Component {
style={{ margin: '10px 0 0 0' }} style={{ margin: '10px 0 0 0' }}
> >
{this.renderConnectToLatticeButton()} {this.renderConnectToLatticeButton()}
{this.renderConnectToQRButton()}
</div> </div>
</> </>
); );
@ -149,12 +170,14 @@ export default class SelectHardware extends Component {
renderTutorialsteps() { renderTutorialsteps() {
switch (this.state.selectedDevice) { switch (this.state.selectedDevice) {
case 'ledger': case DEVICE_NAMES.LEDGER:
return this.renderLedgerTutorialSteps(); return this.renderLedgerTutorialSteps();
case 'trezor': case DEVICE_NAMES.TREZOR:
return this.renderTrezorTutorialSteps(); return this.renderTrezorTutorialSteps();
case 'lattice': case DEVICE_NAMES.LATTICE:
return this.renderLatticeTutorialSteps(); return this.renderLatticeTutorialSteps();
case DEVICE_NAMES.QR:
return this.renderQRHardwareWalletSteps();
default: default:
return ''; return '';
} }
@ -296,6 +319,65 @@ export default class SelectHardware extends Component {
); );
} }
renderQRHardwareWalletSteps() {
const steps = [];
steps.push(
{
title: this.context.t('QRHardwareWalletSteps1Title'),
message: this.context.t('QRHardwareWalletSteps1Description'),
},
{
message: (
<>
<a
className="hw-connect__msg-link"
href="https://keyst.one"
rel="noopener noreferrer"
target="_blank"
key="keystone-support-link"
>
{this.context.t('keystone')}
</a>
<a
className="hw-connect__msg-link"
href="https://keyst.one/mm"
rel="noopener noreferrer"
target="_blank"
key="keystone-tutorial-link"
>
{this.context.t('keystoneTutorial')}
</a>
</>
),
},
{
message: this.context.t('QRHardwareWalletSteps2Description'),
},
{
asset: 'qrcode-wallet-demo',
dimensions: { width: '225px', height: '75px' },
},
);
return (
<div className="hw-tutorial">
{steps.map((step, index) => (
<div className="hw-connect" key={index}>
{step.title && <h3 className="hw-connect__title">{step.title}</h3>}
<p className="hw-connect__msg">{step.message}</p>
{step.asset && (
<img
className="hw-connect__step-asset"
src={`images/${step.asset}.svg`}
{...step.dimensions}
alt=""
/>
)}
</div>
))}
</div>
);
}
renderConnectScreen() { renderConnectScreen() {
return ( return (
<div className="new-external-account-form"> <div className="new-external-account-form">

View File

@ -91,6 +91,7 @@ export default class Home extends PureComponent {
seedPhraseBackedUp: PropTypes.bool.isRequired, seedPhraseBackedUp: PropTypes.bool.isRequired,
newNetworkAdded: PropTypes.string, newNetworkAdded: PropTypes.string,
setNewNetworkAdded: PropTypes.func.isRequired, setNewNetworkAdded: PropTypes.func.isRequired,
isSigningQRHardwareTransaction: PropTypes.bool.isRequired,
}; };
state = { state = {
@ -99,7 +100,7 @@ export default class Home extends PureComponent {
canShowBlockageNotification: true, canShowBlockageNotification: true,
}; };
componentDidMount() { checkStatusAndNavigate() {
const { const {
firstPermissionsRequestId, firstPermissionsRequestId,
history, history,
@ -111,11 +112,13 @@ export default class Home extends PureComponent {
showAwaitingSwapScreen, showAwaitingSwapScreen,
swapsFetchParams, swapsFetchParams,
pendingConfirmations, pendingConfirmations,
isSigningQRHardwareTransaction,
} = this.props; } = this.props;
if (
// eslint-disable-next-line react/no-unused-state isNotification &&
this.setState({ mounted: true }); totalUnapprovedCount === 0 &&
if (isNotification && totalUnapprovedCount === 0) { !isSigningQRHardwareTransaction
) {
global.platform.closeCurrentWindow(); global.platform.closeCurrentWindow();
} else if (!isNotification && showAwaitingSwapScreen) { } else if (!isNotification && showAwaitingSwapScreen) {
history.push(AWAITING_SWAP_ROUTE); history.push(AWAITING_SWAP_ROUTE);
@ -134,6 +137,12 @@ export default class Home extends PureComponent {
} }
} }
componentDidMount() {
// eslint-disable-next-line react/no-unused-state
this.setState({ mounted: true });
this.checkStatusAndNavigate();
}
static getDerivedStateFromProps( static getDerivedStateFromProps(
{ {
firstPermissionsRequestId, firstPermissionsRequestId,
@ -144,11 +153,16 @@ export default class Home extends PureComponent {
haveSwapsQuotes, haveSwapsQuotes,
showAwaitingSwapScreen, showAwaitingSwapScreen,
swapsFetchParams, swapsFetchParams,
isSigningQRHardwareTransaction,
}, },
{ mounted }, { mounted },
) { ) {
if (!mounted) { if (!mounted) {
if (isNotification && totalUnapprovedCount === 0) { if (
isNotification &&
totalUnapprovedCount === 0 &&
!isSigningQRHardwareTransaction
) {
return { closing: true }; return { closing: true };
} else if ( } else if (
firstPermissionsRequestId || firstPermissionsRequestId ||
@ -169,12 +183,15 @@ export default class Home extends PureComponent {
showRestorePrompt, showRestorePrompt,
threeBoxLastUpdated, threeBoxLastUpdated,
threeBoxSynced, threeBoxSynced,
isNotification,
} = this.props; } = this.props;
if (!prevState.closing && this.state.closing) { if (!prevState.closing && this.state.closing) {
global.platform.closeCurrentWindow(); global.platform.closeCurrentWindow();
} }
isNotification && this.checkStatusAndNavigate();
if (threeBoxSynced && showRestorePrompt && threeBoxLastUpdated === null) { if (threeBoxSynced && showRestorePrompt && threeBoxLastUpdated === null) {
setupThreeBox(); setupThreeBox();
} }

View File

@ -16,6 +16,8 @@ import {
getSortedNotificationsToShow, getSortedNotificationsToShow,
getShowRecoveryPhraseReminder, getShowRecoveryPhraseReminder,
getNewNetworkAdded, getNewNetworkAdded,
hasUnsignedQRHardwareTransaction,
hasUnsignedQRHardwareMessage,
} from '../../selectors'; } from '../../selectors';
import { import {
@ -83,6 +85,10 @@ const mapStateToProps = (state) => {
getWeb3ShimUsageStateForOrigin(state, originOfCurrentTab) === getWeb3ShimUsageStateForOrigin(state, originOfCurrentTab) ===
WEB3_SHIM_USAGE_ALERT_STATES.RECORDED; WEB3_SHIM_USAGE_ALERT_STATES.RECORDED;
const isSigningQRHardwareTransaction =
hasUnsignedQRHardwareTransaction(state) ||
hasUnsignedQRHardwareMessage(state);
return { return {
forgottenPassword, forgottenPassword,
suggestedAssets, suggestedAssets,
@ -115,6 +121,7 @@ const mapStateToProps = (state) => {
showRecoveryPhraseReminder: getShowRecoveryPhraseReminder(state), showRecoveryPhraseReminder: getShowRecoveryPhraseReminder(state),
seedPhraseBackedUp, seedPhraseBackedUp,
newNetworkAdded: getNewNetworkAdded(state), newNetworkAdded: getNewNetworkAdded(state),
isSigningQRHardwareTransaction,
}; };
}; };

View File

@ -66,6 +66,7 @@ import {
import { getEnvironmentType } from '../../../app/scripts/lib/util'; import { getEnvironmentType } from '../../../app/scripts/lib/util';
import ConfirmationPage from '../confirmation'; import ConfirmationPage from '../confirmation';
import OnboardingFlow from '../onboarding-flow/onboarding-flow'; import OnboardingFlow from '../onboarding-flow/onboarding-flow';
import QRHardwarePopover from '../../components/app/qr-hardware-popover';
export default class Routes extends Component { export default class Routes extends Component {
static propTypes = { static propTypes = {
@ -321,6 +322,7 @@ export default class Routes extends Component {
} }
}} }}
> >
<QRHardwarePopover />
<Modal /> <Modal />
<Alert visible={this.props.alertOpen} msg={alertMessage} /> <Alert visible={this.props.alertOpen} msg={alertMessage} />
{!this.hideAppHeader() && ( {!this.hideAppHeader() && (

View File

@ -49,6 +49,7 @@ import {
getLedgerWebHidConnectedStatus, getLedgerWebHidConnectedStatus,
getLedgerTransportStatus, getLedgerTransportStatus,
} from '../ducks/app/app'; } from '../ducks/app/app';
import { MESSAGE_TYPE } from '../../shared/constants/app';
/** /**
* One of the only remaining valid uses of selecting the network subkey of the * One of the only remaining valid uses of selecting the network subkey of the
@ -82,6 +83,48 @@ export function getCurrentChainId(state) {
return chainId; return chainId;
} }
export function getCurrentQRHardwareState(state) {
const { qrHardware } = state.metamask;
return qrHardware || {};
}
export function hasUnsignedQRHardwareTransaction(state) {
const { txParams } = state.confirmTransaction.txData;
if (!txParams) return false;
const { from } = txParams;
const { keyrings } = state.metamask;
const qrKeyring = keyrings.find((kr) => kr.type === KEYRING_TYPES.QR);
if (!qrKeyring) return false;
return Boolean(
qrKeyring.accounts.find(
(account) => account.toLowerCase() === from.toLowerCase(),
),
);
}
export function hasUnsignedQRHardwareMessage(state) {
const { type, msgParams } = state.confirmTransaction.txData;
if (!type || !msgParams) {
return false;
}
const { from } = msgParams;
const { keyrings } = state.metamask;
const qrKeyring = keyrings.find((kr) => kr.type === KEYRING_TYPES.QR);
if (!qrKeyring) return false;
switch (type) {
case MESSAGE_TYPE.ETH_SIGN_TYPED_DATA:
case MESSAGE_TYPE.ETH_SIGN:
case MESSAGE_TYPE.PERSONAL_SIGN:
return Boolean(
qrKeyring.accounts.find(
(account) => account.toLowerCase() === from.toLowerCase(),
),
);
default:
return false;
}
}
export function getCurrentKeyring(state) { export function getCurrentKeyring(state) {
const identity = getSelectedIdentity(state); const identity = getSelectedIdentity(state);

View File

@ -29,6 +29,7 @@ import { switchedToUnconnectedAccount } from '../ducks/alerts/unconnected-accoun
import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask'; import { getUnconnectedAccountAlertEnabledness } from '../ducks/metamask/metamask';
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
import { import {
DEVICE_NAMES,
LEDGER_TRANSPORT_TYPES, LEDGER_TRANSPORT_TYPES,
LEDGER_USB_VENDOR_ID, LEDGER_USB_VENDOR_ID,
} from '../../shared/constants/hardware-wallets'; } from '../../shared/constants/hardware-wallets';
@ -414,7 +415,7 @@ export function connectHardware(deviceName, page, hdPath, t) {
await promisifiedBackground.establishLedgerTransportPreference(); await promisifiedBackground.establishLedgerTransportPreference();
} }
if ( if (
deviceName === 'ledger' && deviceName === DEVICE_NAMES.LEDGER &&
ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID
) { ) {
const connectedDevices = await window.navigator.hid.requestDevice({ const connectedDevices = await window.navigator.hid.requestDevice({
@ -443,7 +444,8 @@ export function connectHardware(deviceName, page, hdPath, t) {
dispatch(displayWarning(t('ledgerDeviceOpenFailureMessage'))); dispatch(displayWarning(t('ledgerDeviceOpenFailureMessage')));
throw new Error(t('ledgerDeviceOpenFailureMessage')); throw new Error(t('ledgerDeviceOpenFailureMessage'));
} else { } else {
dispatch(displayWarning(error.message)); if (deviceName !== DEVICE_NAMES.QR)
dispatch(displayWarning(error.message));
throw error; throw error;
} }
} finally { } finally {
@ -2989,3 +2991,30 @@ export async function detectNewTokens() {
export function hideTestNetMessage() { export function hideTestNetMessage() {
return promisifiedBackground.setShowTestnetMessageInDropdown(false); return promisifiedBackground.setShowTestnetMessageInDropdown(false);
} }
// QR Hardware Wallets
export async function submitQRHardwareCryptoHDKey(cbor) {
await promisifiedBackground.submitQRHardwareCryptoHDKey(cbor);
}
export async function submitQRHardwareCryptoAccount(cbor) {
await promisifiedBackground.submitQRHardwareCryptoAccount(cbor);
}
export function cancelSyncQRHardware() {
return async (dispatch) => {
dispatch(hideLoadingIndication());
await promisifiedBackground.cancelSyncQRHardware();
};
}
export async function submitQRHardwareSignature(requestId, cbor) {
await promisifiedBackground.submitQRHardwareSignature(requestId, cbor);
}
export function cancelQRHardwareSignRequest() {
return async (dispatch) => {
dispatch(hideLoadingIndication());
await promisifiedBackground.cancelQRHardwareSignRequest();
};
}

192
yarn.lock
View File

@ -62,6 +62,11 @@
resolved "https://registry.yarnpkg.com/@agoric/transform-module/-/transform-module-0.4.1.tgz#9fb152364faf372e1bda535cb4ef89717724f57c" resolved "https://registry.yarnpkg.com/@agoric/transform-module/-/transform-module-0.4.1.tgz#9fb152364faf372e1bda535cb4ef89717724f57c"
integrity sha512-4TJJHXeXAWu1FCA7yXCAZmhBNoGTB/BEAe2pv+J2X8W/mJTr9b395OkDCSRMpzvmSshLfBx6wT0D7dqWIWEC1w== integrity sha512-4TJJHXeXAWu1FCA7yXCAZmhBNoGTB/BEAe2pv+J2X8W/mJTr9b395OkDCSRMpzvmSshLfBx6wT0D7dqWIWEC1w==
"@apocentre/alias-sampling@^0.5.3":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@apocentre/alias-sampling/-/alias-sampling-0.5.3.tgz#897ff181b48ad7b2bcb4ecf29400214888244f08"
integrity sha512-7UDWIIF9hIeJqfKXkNIzkVandlwLf1FWTSdrb9iXvOP8oF544JRXQjCbiTmCv2c9n44n/FIWtehhBfNuAx2CZA==
"@babel/code-frame@7.10.4", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.5.5": "@babel/code-frame@7.10.4", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.5.5":
version "7.10.4" version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
@ -2423,7 +2428,7 @@
ethers "^5.4.5" ethers "^5.4.5"
lodash "^4.17.21" lodash "^4.17.21"
"@ethereumjs/common@^2.3.1", "@ethereumjs/common@^2.4.0": "@ethereumjs/common@^2.0.0", "@ethereumjs/common@^2.3.1", "@ethereumjs/common@^2.4.0":
version "2.4.0" version "2.4.0"
resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.4.0.tgz#2d67f6e6ba22246c5c89104e6b9a119fb3039766" resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.4.0.tgz#2d67f6e6ba22246c5c89104e6b9a119fb3039766"
integrity sha512-UdkhFWzWcJCZVsj1O/H8/oqj/0RVYjLc1OhPjBrQdALAkQHpCp8xXI4WLnuGTADqTdJZww0NtgwG+TRPkXt27w== integrity sha512-UdkhFWzWcJCZVsj1O/H8/oqj/0RVYjLc1OhPjBrQdALAkQHpCp8xXI4WLnuGTADqTdJZww0NtgwG+TRPkXt27w==
@ -2431,6 +2436,14 @@
crc-32 "^1.2.0" crc-32 "^1.2.0"
ethereumjs-util "^7.1.0" ethereumjs-util "^7.1.0"
"@ethereumjs/tx@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.0.0.tgz#8dfd91ed6e91e63996e37b3ddc340821ebd48c81"
integrity sha512-H9tfy6qgYxPXvt1TSObfVmVjlF43OoQqoPQ3PJsG2JiuqaMHj5ettV1pGFEC3FamENDBkl6vD6niQEvIlXv/VQ==
dependencies:
"@ethereumjs/common" "^2.0.0"
ethereumjs-util "^7.0.7"
"@ethereumjs/tx@^3.1.1", "@ethereumjs/tx@^3.1.4", "@ethereumjs/tx@^3.2.0", "@ethereumjs/tx@^3.2.1", "@ethereumjs/tx@^3.3.0": "@ethereumjs/tx@^3.1.1", "@ethereumjs/tx@^3.1.4", "@ethereumjs/tx@^3.2.0", "@ethereumjs/tx@^3.2.1", "@ethereumjs/tx@^3.3.0":
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.0.tgz#14ed1b7fa0f28e1cd61e3ecbdab824205f6a4378" resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.0.tgz#14ed1b7fa0f28e1cd61e3ecbdab824205f6a4378"
@ -3943,6 +3956,58 @@
"@types/yargs" "^15.0.0" "@types/yargs" "^15.0.0"
chalk "^4.0.0" chalk "^4.0.0"
"@keystonehq/base-eth-keyring@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@keystonehq/base-eth-keyring/-/base-eth-keyring-0.3.1.tgz#c985803f7083f0a2e6ea55846905099c46573142"
integrity sha512-lbVLCMD3R4Ki8CThctZOjafKvJn0p2u19csuMrJHBlFllqu88vYoyfv3I/BPtOpnWqeC90Kta23w68FFUnV8Zg==
dependencies:
"@ethereumjs/tx" "3.0.0"
"@keystonehq/bc-ur-registry-eth" "^0.7.5"
ethereumjs-util "^7.0.8"
hdkey "^2.0.1"
uuid "^8.3.2"
"@keystonehq/bc-ur-registry-eth@^0.6.8":
version "0.6.13"
resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry-eth/-/bc-ur-registry-eth-0.6.13.tgz#c1680930b1d3fed14857336bd4fb47a484dfac32"
integrity sha512-sQQMMiKlacxMOIGeH8l/m/j3sL2VaM7Zid/xvf6cogZ5EZ5pa8Jow8cgY/t7krTOOBp81/GglCbwCGC8RIOLqA==
dependencies:
"@keystonehq/bc-ur-registry" "^0.4.4"
ethereumjs-util "^7.0.8"
hdkey "^2.0.1"
uuid "^8.3.2"
"@keystonehq/bc-ur-registry-eth@^0.7.5":
version "0.7.5"
resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry-eth/-/bc-ur-registry-eth-0.7.5.tgz#30a146e2b6ba01f73380530bbb6bd6a62d540a8b"
integrity sha512-9WcIe4WcqJxf/HKxKhnOBgEfre8/BB5Zi68iHFdw/pyfdYBfzU/nAn2/NB/ggqIHNGWO4zsRnBk85vbJ3QwQsQ==
dependencies:
"@keystonehq/bc-ur-registry" "^0.4.4"
ethereumjs-util "^7.0.8"
hdkey "^2.0.1"
uuid "^8.3.2"
"@keystonehq/bc-ur-registry@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.4.4.tgz#3073fdd4b33cdcbd04526a313a7685891a4b4583"
integrity sha512-SBdKdAZfp3y14GTGrKjfJJHf4iXObjcm4/qKUZ92lj8HVR8mxHHGmHksjE328bJPTAsJPloLix4rTnWg+qgS2w==
dependencies:
"@ngraveio/bc-ur" "^1.1.5"
base58check "^2.0.0"
tslib "^2.3.0"
"@keystonehq/metamask-airgapped-keyring@0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@keystonehq/metamask-airgapped-keyring/-/metamask-airgapped-keyring-0.2.1.tgz#d6a8dd75d97cf7911faa8c2a8b19a0168b74891e"
integrity sha512-LTBGLR8KaJycZLG9igOoIi1tdM2CDN07+dXVGHYnls6DWDN8v3DPzOeAuu1+7H+NDIZYUhGmaa1RcbBT3lY+Uw==
dependencies:
"@ethereumjs/tx" "^3.3.0"
"@keystonehq/base-eth-keyring" "^0.3.1"
"@keystonehq/bc-ur-registry-eth" "^0.7.5"
"@metamask/obs-store" "^7.0.0"
rlp "^2.2.6"
uuid "^8.3.2"
"@lavamoat/allow-scripts@^1.0.6": "@lavamoat/allow-scripts@^1.0.6":
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/@lavamoat/allow-scripts/-/allow-scripts-1.0.6.tgz#fbdf7c35a5c2c2cff05ba002b7bc8f3355bda22c" resolved "https://registry.yarnpkg.com/@lavamoat/allow-scripts/-/allow-scripts-1.0.6.tgz#fbdf7c35a5c2c2cff05ba002b7bc8f3355bda22c"
@ -4341,6 +4406,14 @@
readable-stream "^2.2.2" readable-stream "^2.2.2"
through2 "^2.0.3" through2 "^2.0.3"
"@metamask/obs-store@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@metamask/obs-store/-/obs-store-7.0.0.tgz#6cae5f28306bb3e83a381bc9ae22682316095bd3"
integrity sha512-Tr61Uu9CGXkCg5CZwOYRMQERd+y6fbtrtLd/PzDTPHO5UJpmSbU+7MPcQK7d1DwZCOCeCIvhmZSUCvYliC8uGw==
dependencies:
"@metamask/safe-event-emitter" "^2.0.0"
through2 "^2.0.3"
"@metamask/post-message-stream@^4.0.0": "@metamask/post-message-stream@^4.0.0":
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-4.0.0.tgz#72f120e562346ca86ccc9b3684023ad44265f0df" resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-4.0.0.tgz#72f120e562346ca86ccc9b3684023ad44265f0df"
@ -4389,6 +4462,19 @@
resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121"
integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw== integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==
"@ngraveio/bc-ur@^1.1.5", "@ngraveio/bc-ur@^1.1.6":
version "1.1.6"
resolved "https://registry.yarnpkg.com/@ngraveio/bc-ur/-/bc-ur-1.1.6.tgz#8f8c75fff22f6a5e4dfbc5a6b540d7fe8f42cd39"
integrity sha512-G+2XgjXde2IOcEQeCwR250aS43/Swi7gw0FuETgJy2c3HqF8f88SXDMsIGgJlZ8jXd0GeHR4aX0MfjXf523UZg==
dependencies:
"@apocentre/alias-sampling" "^0.5.3"
assert "^2.0.0"
bignumber.js "^9.0.1"
cbor-sync "^1.0.4"
crc "^3.8.0"
jsbi "^3.1.5"
sha.js "^2.4.11"
"@nodelib/fs.scandir@2.1.3": "@nodelib/fs.scandir@2.1.3":
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
@ -6303,7 +6389,14 @@
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
"@zxing/library@^0.8.0": "@zxing/browser@^0.0.10":
version "0.0.10"
resolved "https://registry.yarnpkg.com/@zxing/browser/-/browser-0.0.10.tgz#63c0a762fc2fd4ee946a20953ef24fab225698a9"
integrity sha512-P2wQc5fs+cjSc39zFS4UDhejWqdikf4FjuWIlFrzXD8fOsZ4ASfmLDKGeg7mRgmJq11oMKcVXvFFI6kcIKtxuQ==
optionalDependencies:
"@zxing/text-encoding" "^0.9.0"
"@zxing/library@0.8.0":
version "0.8.0" version "0.8.0"
resolved "https://registry.yarnpkg.com/@zxing/library/-/library-0.8.0.tgz#accd9f3cd5c06fa40a95c2c1f61398c41548a9e3" resolved "https://registry.yarnpkg.com/@zxing/library/-/library-0.8.0.tgz#accd9f3cd5c06fa40a95c2c1f61398c41548a9e3"
integrity sha512-D7oopukr7cJ0Va01Er2zXiSPXvmvc6D1PpOq/THRvd/57yEsBs+setRsiDo7tSRnYHcw7FrRZSZ7rwyzNSLJeA== integrity sha512-D7oopukr7cJ0Va01Er2zXiSPXvmvc6D1PpOq/THRvd/57yEsBs+setRsiDo7tSRnYHcw7FrRZSZ7rwyzNSLJeA==
@ -6312,7 +6405,7 @@
optionalDependencies: optionalDependencies:
text-encoding "^0.6.4" text-encoding "^0.6.4"
"@zxing/text-encoding@0.9.0": "@zxing/text-encoding@0.9.0", "@zxing/text-encoding@^0.9.0":
version "0.9.0" version "0.9.0"
resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b"
integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==
@ -7268,6 +7361,16 @@ assert@^1.1.1, assert@^1.4.0, assert@^1.4.1:
object-assign "^4.1.1" object-assign "^4.1.1"
util "0.10.3" util "0.10.3"
assert@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32"
integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==
dependencies:
es6-object-assign "^1.1.0"
is-nan "^1.2.1"
object-is "^1.0.1"
util "^0.12.0"
assertion-error@^1.0.1: assertion-error@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"
@ -8265,6 +8368,11 @@ base-x@3.0.8, base-x@^3.0.2, base-x@^3.0.8:
dependencies: dependencies:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
base-x@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-1.1.0.tgz#42d3d717474f9ea02207f6d1aa1f426913eeb7ac"
integrity sha1-QtPXF0dPnqAiB/bRqh9CaRPut6w=
base32-encode@^1.1.0: base32-encode@^1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/base32-encode/-/base32-encode-1.1.1.tgz#d022d86aca0002a751bbe1bf20eb4a9b1cef4e95" resolved "https://registry.yarnpkg.com/base32-encode/-/base32-encode-1.1.1.tgz#d022d86aca0002a751bbe1bf20eb4a9b1cef4e95"
@ -8282,6 +8390,13 @@ base32.js@~0.1.0:
resolved "https://registry.yarnpkg.com/base32.js/-/base32.js-0.1.0.tgz#b582dec693c2f11e893cf064ee6ac5b6131a2202" resolved "https://registry.yarnpkg.com/base32.js/-/base32.js-0.1.0.tgz#b582dec693c2f11e893cf064ee6ac5b6131a2202"
integrity sha1-tYLexpPC8R6JPPBk7mrFthMaIgI= integrity sha1-tYLexpPC8R6JPPBk7mrFthMaIgI=
base58check@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/base58check/-/base58check-2.0.0.tgz#8046652d14bc87f063bd16be94a39134d3b61173"
integrity sha1-gEZlLRS8h/BjvRa+lKORNNO2EXM=
dependencies:
bs58 "^3.0.0"
base64-arraybuffer@0.1.4: base64-arraybuffer@0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
@ -9055,6 +9170,13 @@ bs58@^2.0.1:
resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d" resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d"
integrity sha1-VZCNWPGYKrogCPob7Y+RmYopv40= integrity sha1-VZCNWPGYKrogCPob7Y+RmYopv40=
bs58@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-3.1.0.tgz#d4c26388bf4804cac714141b1945aa47e5eb248e"
integrity sha1-1MJjiL9IBMrHFBQbGUWqR+XrJI4=
dependencies:
base-x "^1.1.0"
bs58check@2.1.2, bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1, bs58check@^2.1.2: bs58check@2.1.2, bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1, bs58check@^2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc"
@ -9154,7 +9276,7 @@ buffer@^4.3.0:
ieee754 "^1.1.4" ieee754 "^1.1.4"
isarray "^1.0.0" isarray "^1.0.0"
buffer@^5.0.5, buffer@^5.2.1, buffer@^5.4.2, buffer@^5.5.0, buffer@^5.6.0: buffer@^5.0.5, buffer@^5.1.0, buffer@^5.2.1, buffer@^5.4.2, buffer@^5.5.0, buffer@^5.6.0:
version "5.7.1" version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
@ -10548,6 +10670,13 @@ crc-32@^1.2.0:
exit-on-epipe "~1.0.1" exit-on-epipe "~1.0.1"
printj "~1.1.0" printj "~1.1.0"
crc@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==
dependencies:
buffer "^5.1.0"
crdts@~0.1.2: crdts@~0.1.2:
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/crdts/-/crdts-0.1.5.tgz#89413e8adfc3ab943300a890ee6392db5ba60c06" resolved "https://registry.yarnpkg.com/crdts/-/crdts-0.1.5.tgz#89413e8adfc3ab943300a890ee6392db5ba60c06"
@ -12346,6 +12475,11 @@ es6-map@^0.1.3, es6-map@^0.1.5:
es6-symbol "~3.1.1" es6-symbol "~3.1.1"
event-emitter "~0.3.5" event-emitter "~0.3.5"
es6-object-assign@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=
es6-promise@^4.2.8: es6-promise@^4.2.8:
version "4.2.8" version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
@ -13327,7 +13461,7 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum
safe-buffer "^5.1.1" safe-buffer "^5.1.1"
secp256k1 "^3.0.1" secp256k1 "^3.0.1"
ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.9, ethereumjs-util@^7.1.0: ethereumjs-util@^7.0.10, ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.7, ethereumjs-util@^7.0.8, ethereumjs-util@^7.0.9, ethereumjs-util@^7.1.0:
version "7.1.0" version "7.1.0"
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.0.tgz#e2b43a30bfcdbcb432a4eb42bd5f2393209b3fd5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.0.tgz#e2b43a30bfcdbcb432a4eb42bd5f2393209b3fd5"
integrity sha512-kR+vhu++mUDARrsMMhsjjzPduRVAeundLGXucGRHF3B4oEltOUspfgCVco4kckucj3FMlLaZHUl9n7/kdmr6Tw== integrity sha512-kR+vhu++mUDARrsMMhsjjzPduRVAeundLGXucGRHF3B4oEltOUspfgCVco4kckucj3FMlLaZHUl9n7/kdmr6Tw==
@ -16146,6 +16280,15 @@ hdkey@0.8.0:
safe-buffer "^5.1.1" safe-buffer "^5.1.1"
secp256k1 "^3.0.1" secp256k1 "^3.0.1"
hdkey@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-2.0.1.tgz#0a211d0c510bfc44fa3ec9d44b13b634641cad74"
integrity sha512-c+tl9PHG9/XkGgG0tD7CJpRVaE0jfZizDNmnErUAKQ4EjQSOcOUcV3EN9ZEZS8pZ4usaeiiK0H7stzuzna8feA==
dependencies:
bs58check "^2.1.2"
safe-buffer "^5.1.1"
secp256k1 "^4.0.0"
he@1.2.0, he@^1.2.0: he@1.2.0, he@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@ -17746,6 +17889,14 @@ is-my-json-valid@^2.10.0:
jsonpointer "^4.0.0" jsonpointer "^4.0.0"
xtend "^4.0.0" xtend "^4.0.0"
is-nan@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
dependencies:
call-bind "^1.0.0"
define-properties "^1.1.3"
is-negated-glob@^1.0.0: is-negated-glob@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
@ -18731,6 +18882,11 @@ jsan@^3.1.13:
resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.13.tgz#4de8c7bf8d1cfcd020c313d438f930cec4b91d86" resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.13.tgz#4de8c7bf8d1cfcd020c313d438f930cec4b91d86"
integrity sha512-9kGpCsGHifmw6oJet+y8HaCl14y7qgAsxVdV3pCHDySNR3BfDC30zgkssd7x5LRVAT22dnpbe9JdzzmXZnq9/g== integrity sha512-9kGpCsGHifmw6oJet+y8HaCl14y7qgAsxVdV3pCHDySNR3BfDC30zgkssd7x5LRVAT22dnpbe9JdzzmXZnq9/g==
jsbi@^3.1.5:
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.0.tgz#3500a08fb3e8e56cf0439964fc774a8762b151ed"
integrity sha512-nL7F2gCfPTXLRoS1ZABhzyYCib6L4bAjX9F6qutL4L2o0r+gDndWVlQ7A6bMa80RTN53R82hXTm6FRsdRxbLgQ==
jsbn@1.1.0: jsbn@1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
@ -24851,11 +25007,25 @@ pushdata-bitcoin@^1.0.1:
dependencies: dependencies:
bitcoin-ops "^1.3.0" bitcoin-ops "^1.3.0"
qr.js@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=
qrcode-generator@1.4.1: qrcode-generator@1.4.1:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.1.tgz#bfb6760e05d12c39df8acd60a0d459bdb2fa0756" resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.1.tgz#bfb6760e05d12c39df8acd60a0d459bdb2fa0756"
integrity sha512-KOdSAyFBPf0/5Z3mra4JfSbjrDlUn2J3YH8Rm33tRGbptxP4vhogLWysvkQp8mp5ix9u80Wfr4vxHXTeR9o0Ug== integrity sha512-KOdSAyFBPf0/5Z3mra4JfSbjrDlUn2J3YH8Rm33tRGbptxP4vhogLWysvkQp8mp5ix9u80Wfr4vxHXTeR9o0Ug==
qrcode.react@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-1.0.1.tgz#2834bb50e5e275ffe5af6906eff15391fe9e38a5"
integrity sha512-8d3Tackk8IRLXTo67Y+c1rpaiXjoz/Dd2HpcMdW//62/x8J1Nbho14Kh8x974t9prsLHN6XqVgcnRiBGFptQmg==
dependencies:
loose-envify "^1.4.0"
prop-types "^15.6.0"
qr.js "0.0.0"
qs@6.7.0, qs@^6.4.0, qs@^6.5.1, qs@^6.5.2: qs@6.7.0, qs@^6.4.0, qs@^6.5.1, qs@^6.5.2:
version "6.7.0" version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@ -26859,7 +27029,7 @@ scss-parser@^1.0.4:
dependencies: dependencies:
invariant "2.2.4" invariant "2.2.4"
secp256k1@4.0.2, secp256k1@^4.0.1: secp256k1@4.0.2, secp256k1@^4.0.0, secp256k1@^4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz#15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz#15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1"
integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg== integrity sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==
@ -27096,7 +27266,7 @@ setprototypeof@1.1.1:
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8, sha.js@~2.4.4:
version "2.4.11" version "2.4.11"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
@ -29161,9 +29331,9 @@ truncate-utf8-bytes@^1.0.0:
utf8-byte-length "^1.0.1" utf8-byte-length "^1.0.1"
ts-custom-error@^2.2.1: ts-custom-error@^2.2.1:
version "2.2.1" version "2.2.2"
resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-2.2.1.tgz#47086fbc34df5c7c2d4fba8c92d8767662066951" resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-2.2.2.tgz#ee769cd6a9cf35dc2e9fedefbb3842f3a2fbceae"
integrity sha512-lHKZtU+PXkVuap6nlFZybIAFLUO8B3jbCs1VynBL8AUSAHfeG6HpztcBTDRp5I+fN5820N9kGg+eTIvr+le2yg== integrity sha512-I0FEdfdatDjeigRqh1JFj67bcIKyRNm12UVGheBjs2pXgyELg2xeiQLVaWu1pVmNGXZVnz/fvycSU41moBIpOg==
ts-dedent@^2.0.0: ts-dedent@^2.0.0:
version "2.0.0" version "2.0.0"
@ -29905,7 +30075,7 @@ util@^0.11.0:
dependencies: dependencies:
inherits "2.0.3" inherits "2.0.3"
util@^0.12.3, util@~0.12.0: util@^0.12.0, util@^0.12.3, util@~0.12.0:
version "0.12.4" version "0.12.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253"
integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw== integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==