@1001-digital/components 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/base/components/Toasts.vue +12 -1
- package/src/base/composables/toast.ts +1 -0
- package/src/evm/assets/wallets/coinbase.svg +4 -0
- package/src/evm/assets/wallets/in-app.svg +5 -0
- package/src/evm/assets/wallets/metamask.svg +1 -0
- package/src/evm/assets/wallets/phantom.svg +4 -0
- package/src/evm/assets/wallets/rabby.svg +24 -0
- package/src/evm/assets/wallets/rainbow.svg +59 -0
- package/src/evm/assets/wallets/safe.png +0 -0
- package/src/evm/assets/wallets/walletconnect.svg +1 -0
- package/src/evm/components/EvmConnect.vue +55 -18
- package/src/evm/components/EvmInAppWalletSetup.vue +238 -0
- package/src/evm/components/EvmSeedPhraseInput.vue +196 -0
- package/src/evm/components/EvmTransactionFlow.vue +1 -0
- package/src/evm/connectors/inAppWallet.ts +220 -0
- package/src/evm/index.ts +5 -0
package/package.json
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
:model-value="toast.progress === true ? null : toast.progress"
|
|
43
43
|
/>
|
|
44
44
|
<ToastAction
|
|
45
|
-
v-if="toast.action"
|
|
45
|
+
v-if="toast.action && !toast.action.persistent"
|
|
46
46
|
:alt-text="toast.action.label"
|
|
47
47
|
:as="Actions"
|
|
48
48
|
class="left"
|
|
@@ -54,6 +54,17 @@
|
|
|
54
54
|
{{ toast.action.label }}
|
|
55
55
|
</Button>
|
|
56
56
|
</ToastAction>
|
|
57
|
+
<Actions
|
|
58
|
+
v-if="toast.action?.persistent"
|
|
59
|
+
class="left"
|
|
60
|
+
>
|
|
61
|
+
<Button
|
|
62
|
+
class="small tertiary"
|
|
63
|
+
@click="toast.action!.onClick()"
|
|
64
|
+
>
|
|
65
|
+
{{ toast.action.label }}
|
|
66
|
+
</Button>
|
|
67
|
+
</Actions>
|
|
57
68
|
</section>
|
|
58
69
|
</ToastRoot>
|
|
59
70
|
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="1024" height="1024" fill="#0052FF"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M152 512C152 710.823 313.177 872 512 872C710.823 872 872 710.823 872 512C872 313.177 710.823 152 512 152C313.177 152 152 313.177 152 512ZM420 396C406.745 396 396 406.745 396 420V604C396 617.255 406.745 628 420 628H604C617.255 628 628 617.255 628 604V420C628 406.745 617.255 396 604 396H420Z" fill="white"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="56" height="56" fill="#323232"/>
|
|
3
|
+
<path d="M35.5 31H35.515" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M20.5 20.5H38.5C39.2956 20.5 40.0587 20.8161 40.6213 21.3787C41.1839 21.9413 41.5 22.7044 41.5 23.5V38.5C41.5 39.2956 41.1839 40.0587 40.6213 40.6213C40.0587 41.1839 39.2956 41.5 38.5 41.5H17.5C16.7044 41.5 15.9413 41.1839 15.3787 40.6213C14.8161 40.0587 14.5 39.2956 14.5 38.5V17.5C14.5 16.7044 14.8161 15.9413 15.3787 15.3787C15.9413 14.8161 16.7044 14.5 17.5 14.5H38.5" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="33" viewBox="0 0 35 33" width="35" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round" stroke-width=".25"><path d="m32.9582 1-13.1341 9.7183 2.4424-5.72731z" fill="#e17726" stroke="#e17726"/><g fill="#e27625" stroke="#e27625"><path d="m2.66296 1 13.01714 9.809-2.3254-5.81802z"/><path d="m28.2295 23.5335-3.4947 5.3386 7.4829 2.0603 2.1436-7.2823z"/><path d="m1.27281 23.6501 2.13055 7.2823 7.46994-2.0603-3.48166-5.3386z"/><path d="m10.4706 14.5149-2.0786 3.1358 7.405.3369-.2469-7.969z"/><path d="m25.1505 14.5149-5.1575-4.58704-.1688 8.05974 7.4049-.3369z"/><path d="m10.8733 28.8721 4.4819-2.1639-3.8583-3.0062z"/><path d="m20.2659 26.7082 4.4689 2.1639-.6105-5.1701z"/></g><path d="m24.7348 28.8721-4.469-2.1639.3638 2.9025-.039 1.231z" fill="#d5bfb2" stroke="#d5bfb2"/><path d="m10.8732 28.8721 4.1572 1.9696-.026-1.231.3508-2.9025z" fill="#d5bfb2" stroke="#d5bfb2"/><path d="m15.1084 21.7842-3.7155-1.0884 2.6243-1.2051z" fill="#233447" stroke="#233447"/><path d="m20.5126 21.7842 1.0913-2.2935 2.6372 1.2051z" fill="#233447" stroke="#233447"/><path d="m10.8733 28.8721.6495-5.3386-4.13117.1167z" fill="#cc6228" stroke="#cc6228"/><path d="m24.0982 23.5335.6366 5.3386 3.4946-5.2219z" fill="#cc6228" stroke="#cc6228"/><path d="m27.2291 17.6507-7.405.3369.6885 3.7966 1.0913-2.2935 2.6372 1.2051z" fill="#cc6228" stroke="#cc6228"/><path d="m11.3929 20.6958 2.6242-1.2051 1.0913 2.2935.6885-3.7966-7.40495-.3369z" fill="#cc6228" stroke="#cc6228"/><path d="m8.392 17.6507 3.1049 6.0513-.1039-3.0062z" fill="#e27525" stroke="#e27525"/><path d="m24.2412 20.6958-.1169 3.0062 3.1049-6.0513z" fill="#e27525" stroke="#e27525"/><path d="m15.797 17.9876-.6886 3.7967.8704 4.4833.1949-5.9087z" fill="#e27525" stroke="#e27525"/><path d="m19.8242 17.9876-.3638 2.3584.1819 5.9216.8704-4.4833z" fill="#e27525" stroke="#e27525"/><path d="m20.5127 21.7842-.8704 4.4834.6236.4406 3.8584-3.0062.1169-3.0062z" fill="#f5841f" stroke="#f5841f"/><path d="m11.3929 20.6958.104 3.0062 3.8583 3.0062.6236-.4406-.8704-4.4834z" fill="#f5841f" stroke="#f5841f"/><path d="m20.5906 30.8417.039-1.231-.3378-.2851h-4.9626l-.3248.2851.026 1.231-4.1572-1.9696 1.4551 1.1921 2.9489 2.0344h5.0536l2.962-2.0344 1.442-1.1921z" fill="#c0ac9d" stroke="#c0ac9d"/><path d="m20.2659 26.7082-.6236-.4406h-3.6635l-.6236.4406-.3508 2.9025.3248-.2851h4.9626l.3378.2851z" fill="#161616" stroke="#161616"/><path d="m33.5168 11.3532 1.1043-5.36447-1.6629-4.98873-12.6923 9.3944 4.8846 4.1205 6.8983 2.0085 1.52-1.7752-.6626-.4795 1.0523-.9588-.8054-.622 1.0523-.8034z" fill="#763e1a" stroke="#763e1a"/><path d="m1 5.98873 1.11724 5.36447-.71451.5313 1.06527.8034-.80545.622 1.05228.9588-.66255.4795 1.51997 1.7752 6.89835-2.0085 4.8846-4.1205-12.69233-9.3944z" fill="#763e1a" stroke="#763e1a"/><path d="m32.0489 16.5234-6.8983-2.0085 2.0786 3.1358-3.1049 6.0513 4.1052-.0519h6.1318z" fill="#f5841f" stroke="#f5841f"/><path d="m10.4705 14.5149-6.89828 2.0085-2.29944 7.1267h6.11883l4.10519.0519-3.10487-6.0513z" fill="#f5841f" stroke="#f5841f"/><path d="m19.8241 17.9876.4417-7.5932 2.0007-5.4034h-8.9119l2.0006 5.4034.4417 7.5932.1689 2.3842.013 5.8958h3.6635l.013-5.8958z" fill="#f5841f" stroke="#f5841f"/></g></svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="128" height="128" fill="#AB9FF2"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.6416 82.1477C50.8744 89.4525 42.8862 98.6966 32.2568 98.6966C27.232 98.6966 22.4004 96.628 22.4004 87.6424C22.4004 64.7584 53.6445 29.3335 82.6339 29.3335C99.1257 29.3335 105.697 40.7755 105.697 53.7689C105.697 70.4471 94.8739 89.5171 84.1156 89.5171C80.7013 89.5171 79.0264 87.6424 79.0264 84.6688C79.0264 83.8931 79.1552 83.0527 79.4129 82.1477C75.7409 88.4182 68.6546 94.2361 62.0192 94.2361C57.1877 94.2361 54.7397 91.1979 54.7397 86.9314C54.7397 85.3799 55.0618 83.7638 55.6416 82.1477ZM80.6133 53.3182C80.6133 57.1044 78.3795 58.9975 75.8806 58.9975C73.3438 58.9975 71.1479 57.1044 71.1479 53.3182C71.1479 49.532 73.3438 47.6389 75.8806 47.6389C78.3795 47.6389 80.6133 49.532 80.6133 53.3182ZM94.8102 53.3184C94.8102 57.1046 92.5763 58.9977 90.0775 58.9977C87.5407 58.9977 85.3447 57.1046 85.3447 53.3184C85.3447 49.5323 87.5407 47.6392 90.0775 47.6392C92.5763 47.6392 94.8102 49.5323 94.8102 53.3184Z" fill="#FFFDF8"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M438.47 279.097C452.952 246.637 381.359 155.948 312.964 118.165C269.853 88.895 224.93 92.9162 215.832 105.768C195.865 133.972 281.948 157.871 339.518 185.759C327.143 191.152 315.481 200.83 308.623 213.207C287.16 189.697 240.052 169.451 184.777 185.759C147.528 196.749 116.571 222.658 104.606 261.791C101.699 260.495 98.4799 259.774 95.0934 259.774C82.1436 259.774 71.6456 270.308 71.6456 283.301C71.6456 296.295 82.1436 306.828 95.0934 306.828C97.4937 306.828 104.999 305.213 104.999 305.213L224.93 306.085C176.967 382.43 139.063 393.59 139.063 406.817C139.063 420.043 175.331 416.459 188.948 411.529C254.138 387.928 324.155 314.373 336.17 293.199C386.625 299.515 429.028 300.262 438.47 279.097Z" fill="url(#paint0_linear_1758_656)"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M339.513 185.763C339.516 185.764 339.519 185.766 339.522 185.767C342.191 184.712 341.759 180.758 341.026 177.652C339.342 170.515 310.284 141.724 282.997 128.829C245.815 111.257 218.435 112.163 214.39 120.262C221.964 135.837 257.077 150.461 293.748 165.733C309.394 172.249 325.323 178.883 339.519 185.76C339.517 185.761 339.515 185.762 339.513 185.763Z" fill="url(#paint1_linear_1758_656)"/>
|
|
4
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M292.329 342.523C284.809 339.64 276.315 336.994 266.658 334.594C276.955 316.108 279.115 288.74 269.391 271.437C255.743 247.153 238.612 234.228 198.802 234.228C176.907 234.228 117.955 241.628 116.909 291.006C116.799 296.187 116.906 300.935 117.28 305.301L224.93 306.084C210.417 329.185 196.825 346.318 184.926 359.345C199.213 363.019 211.003 366.103 221.828 368.934C232.098 371.62 241.499 374.079 251.339 376.598C266.182 365.748 280.135 353.917 292.329 342.523Z" fill="url(#paint2_linear_1758_656)"/>
|
|
5
|
+
<path d="M103.169 300.228C107.567 337.737 128.813 352.437 172.227 356.788C215.641 361.138 240.544 358.22 273.698 361.246C301.389 363.774 326.113 377.932 335.285 373.04C343.539 368.636 338.921 352.728 327.876 342.521C313.558 329.291 293.742 320.093 258.875 316.828C265.824 297.739 263.877 270.973 253.085 256.411C237.481 235.355 208.68 225.836 172.227 229.995C134.143 234.34 97.6504 253.153 103.169 300.228Z" fill="url(#paint3_linear_1758_656)"/>
|
|
6
|
+
<defs>
|
|
7
|
+
<linearGradient id="paint0_linear_1758_656" x1="180.439" y1="250.352" x2="435.479" y2="322.433" gradientUnits="userSpaceOnUse">
|
|
8
|
+
<stop stop-color="#8697FF"/>
|
|
9
|
+
<stop offset="1" stop-color="#ABB7FF"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="paint1_linear_1758_656" x1="392.428" y1="245.489" x2="207.876" y2="61.1077" gradientUnits="userSpaceOnUse">
|
|
12
|
+
<stop stop-color="#8697FF"/>
|
|
13
|
+
<stop offset="1" stop-color="#5156D8" stop-opacity="0"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<linearGradient id="paint2_linear_1758_656" x1="297.446" y1="348.967" x2="120.465" y2="247.558" gradientUnits="userSpaceOnUse">
|
|
16
|
+
<stop stop-color="#465EED"/>
|
|
17
|
+
<stop offset="1" stop-color="#8697FF" stop-opacity="0"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<linearGradient id="paint3_linear_1758_656" x1="195.658" y1="248.443" x2="315.581" y2="400.306" gradientUnits="userSpaceOnUse">
|
|
20
|
+
<stop stop-color="#8898FF"/>
|
|
21
|
+
<stop offset="0.983895" stop-color="#6277F1"/>
|
|
22
|
+
</linearGradient>
|
|
23
|
+
</defs>
|
|
24
|
+
</svg>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<svg viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_8_458)">
|
|
3
|
+
<rect width="120" height="120" fill="url(#paint0_linear_8_458)"/>
|
|
4
|
+
<path d="M20 38H26C56.9279 38 82 63.0721 82 94V100H94C97.3137 100 100 97.3137 100 94C100 53.1309 66.8691 20 26 20C22.6863 20 20 22.6863 20 26V38Z" fill="url(#paint1_radial_8_458)"/>
|
|
5
|
+
<path d="M84 94H100C100 97.3137 97.3137 100 94 100H84V94Z" fill="url(#paint2_linear_8_458)"/>
|
|
6
|
+
<path d="M26 20L26 36H20L20 26C20 22.6863 22.6863 20 26 20Z" fill="url(#paint3_linear_8_458)"/>
|
|
7
|
+
<path d="M20 36H26C58.0325 36 84 61.9675 84 94V100H66V94C66 71.9086 48.0914 54 26 54H20V36Z" fill="url(#paint4_radial_8_458)"/>
|
|
8
|
+
<path d="M68 94H84V100H68V94Z" fill="url(#paint5_linear_8_458)"/>
|
|
9
|
+
<path d="M20 52L20 36L26 36L26 52H20Z" fill="url(#paint6_linear_8_458)"/>
|
|
10
|
+
<path d="M20 62C20 65.3137 22.6863 68 26 68C40.3594 68 52 79.6406 52 94C52 97.3137 54.6863 100 58 100H68V94C68 70.804 49.196 52 26 52H20V62Z" fill="url(#paint7_radial_8_458)"/>
|
|
11
|
+
<path d="M52 94H68V100H58C54.6863 100 52 97.3137 52 94Z" fill="url(#paint8_radial_8_458)"/>
|
|
12
|
+
<path d="M26 68C22.6863 68 20 65.3137 20 62L20 52L26 52L26 68Z" fill="url(#paint9_radial_8_458)"/>
|
|
13
|
+
</g>
|
|
14
|
+
<defs>
|
|
15
|
+
<linearGradient id="paint0_linear_8_458" x1="60" y1="0" x2="60" y2="120" gradientUnits="userSpaceOnUse">
|
|
16
|
+
<stop stop-color="#174299"/>
|
|
17
|
+
<stop offset="1" stop-color="#001E59"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<radialGradient id="paint1_radial_8_458" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26 94) rotate(-90) scale(74)">
|
|
20
|
+
<stop offset="0.770277" stop-color="#FF4000"/>
|
|
21
|
+
<stop offset="1" stop-color="#8754C9"/>
|
|
22
|
+
</radialGradient>
|
|
23
|
+
<linearGradient id="paint2_linear_8_458" x1="83" y1="97" x2="100" y2="97" gradientUnits="userSpaceOnUse">
|
|
24
|
+
<stop stop-color="#FF4000"/>
|
|
25
|
+
<stop offset="1" stop-color="#8754C9"/>
|
|
26
|
+
</linearGradient>
|
|
27
|
+
<linearGradient id="paint3_linear_8_458" x1="23" y1="20" x2="23" y2="37" gradientUnits="userSpaceOnUse">
|
|
28
|
+
<stop stop-color="#8754C9"/>
|
|
29
|
+
<stop offset="1" stop-color="#FF4000"/>
|
|
30
|
+
</linearGradient>
|
|
31
|
+
<radialGradient id="paint4_radial_8_458" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26 94) rotate(-90) scale(58)">
|
|
32
|
+
<stop offset="0.723929" stop-color="#FFF700"/>
|
|
33
|
+
<stop offset="1" stop-color="#FF9901"/>
|
|
34
|
+
</radialGradient>
|
|
35
|
+
<linearGradient id="paint5_linear_8_458" x1="68" y1="97" x2="84" y2="97" gradientUnits="userSpaceOnUse">
|
|
36
|
+
<stop stop-color="#FFF700"/>
|
|
37
|
+
<stop offset="1" stop-color="#FF9901"/>
|
|
38
|
+
</linearGradient>
|
|
39
|
+
<linearGradient id="paint6_linear_8_458" x1="23" y1="52" x2="23" y2="36" gradientUnits="userSpaceOnUse">
|
|
40
|
+
<stop stop-color="#FFF700"/>
|
|
41
|
+
<stop offset="1" stop-color="#FF9901"/>
|
|
42
|
+
</linearGradient>
|
|
43
|
+
<radialGradient id="paint7_radial_8_458" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26 94) rotate(-90) scale(42)">
|
|
44
|
+
<stop offset="0.59513" stop-color="#00AAFF"/>
|
|
45
|
+
<stop offset="1" stop-color="#01DA40"/>
|
|
46
|
+
</radialGradient>
|
|
47
|
+
<radialGradient id="paint8_radial_8_458" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(51 97) scale(17 45.3333)">
|
|
48
|
+
<stop stop-color="#00AAFF"/>
|
|
49
|
+
<stop offset="1" stop-color="#01DA40"/>
|
|
50
|
+
</radialGradient>
|
|
51
|
+
<radialGradient id="paint9_radial_8_458" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(23 69) rotate(-90) scale(17 322.37)">
|
|
52
|
+
<stop stop-color="#00AAFF"/>
|
|
53
|
+
<stop offset="1" stop-color="#01DA40"/>
|
|
54
|
+
</radialGradient>
|
|
55
|
+
<clipPath id="clip0_8_458">
|
|
56
|
+
<rect width="120" height="120" fill="white"/>
|
|
57
|
+
</clipPath>
|
|
58
|
+
</defs>
|
|
59
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="400" viewBox="0 0 400 400" width="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h400v400h-400z"/></clipPath><g clip-path="url(#a)"><circle cx="200" cy="200" fill="#3396ff" r="199.5" stroke="#66b1ff"/><path d="m122.519 148.965c42.791-41.729 112.171-41.729 154.962 0l5.15 5.022c2.14 2.086 2.14 5.469 0 7.555l-17.617 17.18c-1.07 1.043-2.804 1.043-3.874 0l-7.087-6.911c-29.853-29.111-78.253-29.111-108.106 0l-7.59 7.401c-1.07 1.043-2.804 1.043-3.874 0l-17.617-17.18c-2.14-2.086-2.14-5.469 0-7.555zm191.397 35.529 15.679 15.29c2.14 2.086 2.14 5.469 0 7.555l-70.7 68.944c-2.139 2.087-5.608 2.087-7.748 0l-50.178-48.931c-.535-.522-1.402-.522-1.937 0l-50.178 48.931c-2.139 2.087-5.608 2.087-7.748 0l-70.7015-68.945c-2.1396-2.086-2.1396-5.469 0-7.555l15.6795-15.29c2.1396-2.086 5.6085-2.086 7.7481 0l50.1789 48.932c.535.522 1.402.522 1.937 0l50.177-48.932c2.139-2.087 5.608-2.087 7.748 0l50.179 48.932c.535.522 1.402.522 1.937 0l50.179-48.931c2.139-2.087 5.608-2.087 7.748 0z" fill="#fff"/></g></svg>
|
|
@@ -20,8 +20,13 @@
|
|
|
20
20
|
v-model:open="chooseModalOpen"
|
|
21
21
|
@closed="onModalClosed"
|
|
22
22
|
>
|
|
23
|
+
<EvmInAppWalletSetup
|
|
24
|
+
v-if="showInAppSetup"
|
|
25
|
+
@connected="onInAppConnected"
|
|
26
|
+
@back="showInAppSetup = false"
|
|
27
|
+
/>
|
|
23
28
|
<Alert
|
|
24
|
-
v-if="errorMessage"
|
|
29
|
+
v-else-if="errorMessage"
|
|
25
30
|
type="error"
|
|
26
31
|
>
|
|
27
32
|
{{ errorMessage }}
|
|
@@ -52,10 +57,8 @@
|
|
|
52
57
|
class="choose-connector"
|
|
53
58
|
>
|
|
54
59
|
<img
|
|
55
|
-
v-if="ICONS[connector.name]"
|
|
56
|
-
:src="
|
|
57
|
-
connector.icon || `${base}icons/wallets/${ICONS[connector.name]}`
|
|
58
|
-
"
|
|
60
|
+
v-if="ICONS[connector.name] || connector.icon"
|
|
61
|
+
:src="ICONS[connector.name] || connector.icon"
|
|
59
62
|
:alt="connector.name"
|
|
60
63
|
/>
|
|
61
64
|
<div
|
|
@@ -72,11 +75,22 @@
|
|
|
72
75
|
class="choose-connector"
|
|
73
76
|
>
|
|
74
77
|
<img
|
|
75
|
-
:src="
|
|
78
|
+
:src="safeIcon"
|
|
76
79
|
alt="Safe"
|
|
77
80
|
/>
|
|
78
81
|
<span>Safe</span>
|
|
79
82
|
</Button>
|
|
83
|
+
<Button
|
|
84
|
+
v-if="inAppConnector"
|
|
85
|
+
@click="showInAppSetup = true"
|
|
86
|
+
class="choose-connector"
|
|
87
|
+
>
|
|
88
|
+
<img
|
|
89
|
+
:src="inAppIcon"
|
|
90
|
+
alt="Seed Phrase"
|
|
91
|
+
/>
|
|
92
|
+
<span>In App</span>
|
|
93
|
+
</Button>
|
|
80
94
|
<Button
|
|
81
95
|
to="https://ethereum.org/wallets/"
|
|
82
96
|
target="_blank"
|
|
@@ -106,16 +120,26 @@ import Loading from '../../base/components/Loading.vue'
|
|
|
106
120
|
import EvmAccount from './EvmAccount.vue'
|
|
107
121
|
import EvmMetaMaskQR from './EvmMetaMaskQR.vue'
|
|
108
122
|
import EvmWalletConnectWallets from './EvmWalletConnectWallets.vue'
|
|
109
|
-
import
|
|
123
|
+
import EvmInAppWalletSetup from './EvmInAppWalletSetup.vue'
|
|
124
|
+
|
|
125
|
+
import coinbaseIcon from '../assets/wallets/coinbase.svg'
|
|
126
|
+
import metamaskIcon from '../assets/wallets/metamask.svg'
|
|
127
|
+
import phantomIcon from '../assets/wallets/phantom.svg'
|
|
128
|
+
import rabbyIcon from '../assets/wallets/rabby.svg'
|
|
129
|
+
import rainbowIcon from '../assets/wallets/rainbow.svg'
|
|
130
|
+
import safeIcon from '../assets/wallets/safe.png'
|
|
131
|
+
import inAppIcon from '../assets/wallets/in-app.svg'
|
|
132
|
+
import walletconnectIcon from '../assets/wallets/walletconnect.svg'
|
|
110
133
|
|
|
111
134
|
const ICONS: Record<string, string> = {
|
|
112
|
-
'Base Account':
|
|
113
|
-
MetaMask:
|
|
114
|
-
Phantom:
|
|
115
|
-
'Rabby Wallet':
|
|
116
|
-
Rainbow:
|
|
117
|
-
Safe:
|
|
118
|
-
|
|
135
|
+
'Base Account': coinbaseIcon,
|
|
136
|
+
MetaMask: metamaskIcon,
|
|
137
|
+
Phantom: phantomIcon,
|
|
138
|
+
'Rabby Wallet': rabbyIcon,
|
|
139
|
+
Rainbow: rainbowIcon,
|
|
140
|
+
Safe: safeIcon,
|
|
141
|
+
'In App': inAppIcon,
|
|
142
|
+
WalletConnect: walletconnectIcon,
|
|
119
143
|
}
|
|
120
144
|
|
|
121
145
|
const PRIORITY: Record<string, number> = {
|
|
@@ -130,13 +154,16 @@ const emit = defineEmits<{
|
|
|
130
154
|
connected: [{ address: `0x${string}` | undefined }]
|
|
131
155
|
disconnected: []
|
|
132
156
|
}>()
|
|
133
|
-
const base = useBaseURL()
|
|
134
|
-
|
|
135
157
|
const chainId = useChainId()
|
|
136
158
|
const connectors = useConnectors()
|
|
137
159
|
const { mutateAsync: connectAsync } = useConnect()
|
|
138
160
|
const { address, isConnected } = useConnection()
|
|
139
161
|
|
|
162
|
+
const inAppConnector = computed(() =>
|
|
163
|
+
connectors.value.find((c) => c.type === 'inAppWallet'),
|
|
164
|
+
)
|
|
165
|
+
const showInAppSetup = ref(false)
|
|
166
|
+
|
|
140
167
|
const showConnect = computed(() => !isConnected.value)
|
|
141
168
|
const shownConnectors = computed(() => {
|
|
142
169
|
const unique = Array.from(
|
|
@@ -147,8 +174,11 @@ const shownConnectors = computed(() => {
|
|
|
147
174
|
|
|
148
175
|
const filtered =
|
|
149
176
|
unique.length > 1
|
|
150
|
-
? unique.filter(
|
|
151
|
-
|
|
177
|
+
? unique.filter(
|
|
178
|
+
(c) =>
|
|
179
|
+
c.id !== 'injected' && c.id !== 'safe' && c.type !== 'inAppWallet',
|
|
180
|
+
)
|
|
181
|
+
: unique.filter((c) => c.type !== 'inAppWallet')
|
|
152
182
|
|
|
153
183
|
return filtered.sort((a, b) => {
|
|
154
184
|
const priorityA = PRIORITY[a.name] ?? 5
|
|
@@ -247,6 +277,11 @@ const login = async (connector: Connector) => {
|
|
|
247
277
|
}
|
|
248
278
|
}
|
|
249
279
|
|
|
280
|
+
const onInAppConnected = () => {
|
|
281
|
+
chooseModalOpen.value = false
|
|
282
|
+
showInAppSetup.value = false
|
|
283
|
+
}
|
|
284
|
+
|
|
250
285
|
const onModalClosed = () => {
|
|
251
286
|
errorMessage.value = ''
|
|
252
287
|
isConnecting.value = false
|
|
@@ -254,6 +289,7 @@ const onModalClosed = () => {
|
|
|
254
289
|
metaMaskUri.value = ''
|
|
255
290
|
walletConnectUri.value = ''
|
|
256
291
|
safeDeepLink.value = false
|
|
292
|
+
showInAppSetup.value = false
|
|
257
293
|
}
|
|
258
294
|
|
|
259
295
|
const check = () =>
|
|
@@ -276,6 +312,7 @@ onMounted(() => check())
|
|
|
276
312
|
width: 100%;
|
|
277
313
|
inline-size: auto;
|
|
278
314
|
justify-content: flex-start;
|
|
315
|
+
padding-inline-start: var(--ui-padding-inline);
|
|
279
316
|
|
|
280
317
|
img,
|
|
281
318
|
.default-wallet-icon {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="in-app-wallet-setup">
|
|
3
|
+
<!-- Step: Choose -->
|
|
4
|
+
<div
|
|
5
|
+
v-if="step === 'choose'"
|
|
6
|
+
class="setup-step"
|
|
7
|
+
>
|
|
8
|
+
<div class="setup-options">
|
|
9
|
+
<Button @click="startGenerate">
|
|
10
|
+
<Icon type="plus" />
|
|
11
|
+
<span>Create New Wallet</span>
|
|
12
|
+
</Button>
|
|
13
|
+
<Button @click="step = 'restore'">
|
|
14
|
+
<Icon type="key" />
|
|
15
|
+
<span>I Have a Seed Phrase</span>
|
|
16
|
+
</Button>
|
|
17
|
+
</div>
|
|
18
|
+
<Button
|
|
19
|
+
class="link muted small"
|
|
20
|
+
@click="$emit('back')"
|
|
21
|
+
>
|
|
22
|
+
<Icon type="arrow-left" />
|
|
23
|
+
<span>Back</span>
|
|
24
|
+
</Button>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Step: Generate -->
|
|
28
|
+
<div
|
|
29
|
+
v-else-if="step === 'generate'"
|
|
30
|
+
class="setup-step"
|
|
31
|
+
>
|
|
32
|
+
<Alert type="info">
|
|
33
|
+
Write down these 12 words in order. You will need them to restore your wallet. They will not be shown again.
|
|
34
|
+
</Alert>
|
|
35
|
+
|
|
36
|
+
<div class="generated-words">
|
|
37
|
+
<div
|
|
38
|
+
v-for="(word, i) in generatedWords"
|
|
39
|
+
:key="i"
|
|
40
|
+
class="generated-word"
|
|
41
|
+
>
|
|
42
|
+
<span class="word-number">{{ i + 1 }}</span>
|
|
43
|
+
<span class="word-text">{{ word }}</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<FormCheckbox v-model="backupConfirmed">
|
|
48
|
+
I've saved my seed phrase
|
|
49
|
+
</FormCheckbox>
|
|
50
|
+
|
|
51
|
+
<Button
|
|
52
|
+
:disabled="!backupConfirmed"
|
|
53
|
+
@click="confirmGenerated"
|
|
54
|
+
>
|
|
55
|
+
Continue
|
|
56
|
+
</Button>
|
|
57
|
+
<Button
|
|
58
|
+
class="link muted small"
|
|
59
|
+
@click="step = 'choose'"
|
|
60
|
+
>
|
|
61
|
+
<Icon type="arrow-left" />
|
|
62
|
+
<span>Back</span>
|
|
63
|
+
</Button>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- Step: Restore -->
|
|
67
|
+
<div
|
|
68
|
+
v-else-if="step === 'restore'"
|
|
69
|
+
class="setup-step"
|
|
70
|
+
>
|
|
71
|
+
<p class="muted font-sm">Enter your 12-word seed phrase to restore your wallet.</p>
|
|
72
|
+
|
|
73
|
+
<EvmSeedPhraseInput
|
|
74
|
+
v-model="restorePhrase"
|
|
75
|
+
@valid="restoreValid = $event"
|
|
76
|
+
@submit="restoreWallet"
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
<Button
|
|
80
|
+
:disabled="!restoreValid"
|
|
81
|
+
@click="restoreWallet"
|
|
82
|
+
>
|
|
83
|
+
Restore Wallet
|
|
84
|
+
</Button>
|
|
85
|
+
<Button
|
|
86
|
+
class="link muted small"
|
|
87
|
+
@click="step = 'choose'"
|
|
88
|
+
>
|
|
89
|
+
<Icon type="arrow-left" />
|
|
90
|
+
<span>Back</span>
|
|
91
|
+
</Button>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<!-- Step: Connecting -->
|
|
95
|
+
<div
|
|
96
|
+
v-else-if="step === 'connecting'"
|
|
97
|
+
class="setup-step"
|
|
98
|
+
>
|
|
99
|
+
<Loading
|
|
100
|
+
txt="Connecting wallet..."
|
|
101
|
+
spinner
|
|
102
|
+
stacked
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<script setup lang="ts">
|
|
109
|
+
import { ref, computed } from 'vue'
|
|
110
|
+
import { useConnect, useConnectors } from '@wagmi/vue'
|
|
111
|
+
import Button from '../../base/components/Button.vue'
|
|
112
|
+
import Icon from '../../base/components/Icon.vue'
|
|
113
|
+
import Alert from '../../base/components/Alert.vue'
|
|
114
|
+
import FormCheckbox from '../../base/components/FormCheckbox.vue'
|
|
115
|
+
import Loading from '../../base/components/Loading.vue'
|
|
116
|
+
import EvmSeedPhraseInput from './EvmSeedPhraseInput.vue'
|
|
117
|
+
import { prepareInAppWallet } from '../connectors/inAppWallet'
|
|
118
|
+
|
|
119
|
+
const emit = defineEmits<{
|
|
120
|
+
connected: []
|
|
121
|
+
back: []
|
|
122
|
+
}>()
|
|
123
|
+
|
|
124
|
+
const connectors = useConnectors()
|
|
125
|
+
const { mutateAsync: connectAsync } = useConnect()
|
|
126
|
+
const inAppConnector = computed(() =>
|
|
127
|
+
connectors.value.find((c) => c.type === 'inAppWallet'),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
type Step = 'choose' | 'generate' | 'restore' | 'connecting'
|
|
131
|
+
const step = ref<Step>('choose')
|
|
132
|
+
|
|
133
|
+
// Generate state
|
|
134
|
+
const generatedMnemonic = ref('')
|
|
135
|
+
const generatedWords = ref<string[]>([])
|
|
136
|
+
const backupConfirmed = ref(false)
|
|
137
|
+
|
|
138
|
+
// Restore state
|
|
139
|
+
const restorePhrase = ref('')
|
|
140
|
+
const restoreValid = ref(false)
|
|
141
|
+
|
|
142
|
+
async function startGenerate() {
|
|
143
|
+
const { generateMnemonic, english } = await import('viem/accounts')
|
|
144
|
+
generatedMnemonic.value = generateMnemonic(english)
|
|
145
|
+
generatedWords.value = generatedMnemonic.value.split(' ')
|
|
146
|
+
backupConfirmed.value = false
|
|
147
|
+
step.value = 'generate'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function connectWithMnemonic(mnemonic: string) {
|
|
151
|
+
await prepareInAppWallet(mnemonic)
|
|
152
|
+
await connectAsync({ connector: inAppConnector.value! })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function confirmGenerated() {
|
|
156
|
+
step.value = 'connecting'
|
|
157
|
+
try {
|
|
158
|
+
await connectWithMnemonic(generatedMnemonic.value)
|
|
159
|
+
emit('connected')
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error('Failed to connect in-app wallet:', e)
|
|
162
|
+
step.value = 'generate'
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function restoreWallet() {
|
|
167
|
+
if (!restoreValid.value) return
|
|
168
|
+
step.value = 'connecting'
|
|
169
|
+
try {
|
|
170
|
+
await connectWithMnemonic(restorePhrase.value)
|
|
171
|
+
emit('connected')
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.error('Failed to restore in-app wallet:', e)
|
|
174
|
+
step.value = 'restore'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<style scoped>
|
|
180
|
+
.in-app-wallet-setup {
|
|
181
|
+
display: grid;
|
|
182
|
+
gap: var(--spacer);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.setup-step {
|
|
186
|
+
display: grid;
|
|
187
|
+
gap: var(--spacer);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.setup-options {
|
|
191
|
+
display: grid;
|
|
192
|
+
gap: var(--spacer);
|
|
193
|
+
|
|
194
|
+
:deep(button),
|
|
195
|
+
:deep(.button) {
|
|
196
|
+
width: 100%;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.generated-words {
|
|
201
|
+
display: grid;
|
|
202
|
+
grid-template-columns: repeat(3, 1fr);
|
|
203
|
+
gap: var(--spacer-sm);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@media (min-width: 600px) {
|
|
207
|
+
.generated-words {
|
|
208
|
+
grid-template-columns: repeat(4, 1fr);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.generated-word {
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
gap: var(--spacer-xs);
|
|
216
|
+
border: var(--border);
|
|
217
|
+
border-radius: var(--border-radius);
|
|
218
|
+
padding: var(--spacer-xs) var(--spacer-sm);
|
|
219
|
+
|
|
220
|
+
.word-number {
|
|
221
|
+
font-size: var(--font-xs);
|
|
222
|
+
color: var(--muted);
|
|
223
|
+
min-width: 1.5em;
|
|
224
|
+
text-align: right;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.word-text {
|
|
228
|
+
font-size: var(--font-sm);
|
|
229
|
+
font-family: var(--font-mono, monospace);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.link.muted {
|
|
234
|
+
justify-self: center;
|
|
235
|
+
font-size: var(--font-xs);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
</style>
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="seed-phrase-input">
|
|
3
|
+
<div class="seed-phrase-grid">
|
|
4
|
+
<div
|
|
5
|
+
v-for="(_, i) in 12"
|
|
6
|
+
:key="i"
|
|
7
|
+
class="seed-word"
|
|
8
|
+
:class="{ invalid: words[i] && !isValidWord(words[i]) }"
|
|
9
|
+
>
|
|
10
|
+
<label :for="`seed-word-${i}`">{{ i + 1 }}</label>
|
|
11
|
+
<input
|
|
12
|
+
:id="`seed-word-${i}`"
|
|
13
|
+
:ref="(el) => { if (el) inputRefs[i] = el as HTMLInputElement }"
|
|
14
|
+
v-model="words[i]"
|
|
15
|
+
type="text"
|
|
16
|
+
autocomplete="off"
|
|
17
|
+
autocapitalize="none"
|
|
18
|
+
spellcheck="false"
|
|
19
|
+
:disabled="disabled"
|
|
20
|
+
@keydown="onKeydown($event, i)"
|
|
21
|
+
@paste="onPaste($event, i)"
|
|
22
|
+
@input="onInput(i)"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import { ref, watch, computed, onMounted } from 'vue'
|
|
31
|
+
import { english } from 'viem/accounts'
|
|
32
|
+
|
|
33
|
+
const props = defineProps<{
|
|
34
|
+
modelValue?: string
|
|
35
|
+
disabled?: boolean
|
|
36
|
+
}>()
|
|
37
|
+
|
|
38
|
+
const emit = defineEmits<{
|
|
39
|
+
'update:modelValue': [value: string]
|
|
40
|
+
valid: [isValid: boolean]
|
|
41
|
+
submit: []
|
|
42
|
+
}>()
|
|
43
|
+
|
|
44
|
+
const wordSet = new Set(english)
|
|
45
|
+
|
|
46
|
+
const words = ref<string[]>(Array.from({ length: 12 }, () => ''))
|
|
47
|
+
const inputRefs = ref<HTMLInputElement[]>([])
|
|
48
|
+
|
|
49
|
+
function isValidWord(word: string): boolean {
|
|
50
|
+
return wordSet.has(word.trim().toLowerCase())
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isValid = computed(() =>
|
|
54
|
+
words.value.every((w) => w.trim() !== '' && isValidWord(w)),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const phrase = computed(() =>
|
|
58
|
+
words.value
|
|
59
|
+
.map((w) => w.trim().toLowerCase())
|
|
60
|
+
.join(' '),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
watch(
|
|
64
|
+
() => props.modelValue,
|
|
65
|
+
(val) => {
|
|
66
|
+
if (!val) return
|
|
67
|
+
const incoming = val.trim().split(/\s+/)
|
|
68
|
+
if (incoming.length === 12) {
|
|
69
|
+
for (let i = 0; i < 12; i++) {
|
|
70
|
+
words.value[i] = incoming[i]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{ immediate: true },
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
watch(phrase, (val) => {
|
|
78
|
+
emit('update:modelValue', val)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
watch(isValid, (val) => {
|
|
82
|
+
emit('valid', val)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
function focusInput(index: number) {
|
|
86
|
+
const el = inputRefs.value[index]
|
|
87
|
+
if (el) {
|
|
88
|
+
el.focus()
|
|
89
|
+
el.select()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function onKeydown(event: KeyboardEvent, index: number) {
|
|
94
|
+
if (event.key === ' ' || (event.key === 'Enter' && index < 11)) {
|
|
95
|
+
event.preventDefault()
|
|
96
|
+
focusInput(index + 1)
|
|
97
|
+
} else if (event.key === 'Enter' && index === 11 && isValid.value) {
|
|
98
|
+
event.preventDefault()
|
|
99
|
+
emit('submit')
|
|
100
|
+
} else if (
|
|
101
|
+
event.key === 'Backspace' &&
|
|
102
|
+
words.value[index] === '' &&
|
|
103
|
+
index > 0
|
|
104
|
+
) {
|
|
105
|
+
event.preventDefault()
|
|
106
|
+
focusInput(index - 1)
|
|
107
|
+
} else if (event.key === 'Tab' && !event.shiftKey && index === 11) {
|
|
108
|
+
// Allow natural tab out
|
|
109
|
+
} else if (event.key === 'Tab' && event.shiftKey && index === 0) {
|
|
110
|
+
// Allow natural tab out
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function onPaste(event: ClipboardEvent, index: number) {
|
|
115
|
+
const text = event.clipboardData?.getData('text')
|
|
116
|
+
if (!text) return
|
|
117
|
+
|
|
118
|
+
const pasted = text.trim().split(/\s+/)
|
|
119
|
+
if (pasted.length > 1) {
|
|
120
|
+
event.preventDefault()
|
|
121
|
+
for (let i = 0; i < pasted.length && index + i < 12; i++) {
|
|
122
|
+
words.value[index + i] = pasted[i].toLowerCase()
|
|
123
|
+
}
|
|
124
|
+
const nextIndex = Math.min(index + pasted.length, 11)
|
|
125
|
+
focusInput(nextIndex)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function onInput(index: number) {
|
|
130
|
+
// Auto-advance if the word contains a space (mobile autocomplete)
|
|
131
|
+
const val = words.value[index]
|
|
132
|
+
if (val.includes(' ')) {
|
|
133
|
+
const parts = val.trim().split(/\s+/)
|
|
134
|
+
words.value[index] = parts[0]
|
|
135
|
+
if (parts.length > 1 && index < 11) {
|
|
136
|
+
for (let i = 1; i < parts.length && index + i < 12; i++) {
|
|
137
|
+
words.value[index + i] = parts[i]
|
|
138
|
+
}
|
|
139
|
+
focusInput(Math.min(index + parts.length, 11))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
onMounted(() => {
|
|
145
|
+
if (!props.disabled) {
|
|
146
|
+
focusInput(0)
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<style scoped>
|
|
152
|
+
.seed-phrase-grid {
|
|
153
|
+
display: grid;
|
|
154
|
+
grid-template-columns: repeat(3, 1fr);
|
|
155
|
+
gap: var(--spacer-sm);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@media (min-width: 600px) {
|
|
159
|
+
.seed-phrase-grid {
|
|
160
|
+
grid-template-columns: repeat(4, 1fr);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.seed-word {
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
gap: var(--spacer-xs);
|
|
168
|
+
border: var(--border);
|
|
169
|
+
border-radius: var(--border-radius);
|
|
170
|
+
padding: var(--spacer-xs) var(--spacer-sm);
|
|
171
|
+
transition: border-color var(--speed);
|
|
172
|
+
|
|
173
|
+
&:focus-within {
|
|
174
|
+
border-color: var(--accent);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
&.invalid {
|
|
178
|
+
border-color: var(--error);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
label {
|
|
182
|
+
font-size: var(--font-xs);
|
|
183
|
+
color: var(--muted);
|
|
184
|
+
min-width: 1.5em;
|
|
185
|
+
text-align: right;
|
|
186
|
+
user-select: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
input {
|
|
190
|
+
all: unset;
|
|
191
|
+
width: 100%;
|
|
192
|
+
font-size: var(--font-sm);
|
|
193
|
+
font-family: var(--font-mono, monospace);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { createConnector } from '@wagmi/core'
|
|
2
|
+
import {
|
|
3
|
+
type Address,
|
|
4
|
+
type Hex,
|
|
5
|
+
createPublicClient,
|
|
6
|
+
createWalletClient,
|
|
7
|
+
custom,
|
|
8
|
+
getAddress,
|
|
9
|
+
hexToBigInt,
|
|
10
|
+
hexToNumber,
|
|
11
|
+
http,
|
|
12
|
+
numberToHex,
|
|
13
|
+
} from 'viem'
|
|
14
|
+
import { privateKeyToAccount, type PrivateKeyAccount } from 'viem/accounts'
|
|
15
|
+
|
|
16
|
+
const STORAGE_KEY = 'evm:in-app-wallet-pk'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Derive a private key from a BIP39 mnemonic and store it in localStorage.
|
|
20
|
+
* Call this before `connectAsync({ connector })`.
|
|
21
|
+
*/
|
|
22
|
+
export async function prepareInAppWallet(mnemonic: string): Promise<Address> {
|
|
23
|
+
const { mnemonicToAccount } = await import('viem/accounts')
|
|
24
|
+
const { bytesToHex } = await import('viem')
|
|
25
|
+
|
|
26
|
+
const normalized = mnemonic.trim().toLowerCase().replace(/\s+/g, ' ')
|
|
27
|
+
const hdAccount = mnemonicToAccount(normalized)
|
|
28
|
+
const hdKey = hdAccount.getHdKey()
|
|
29
|
+
const pk = bytesToHex(hdKey.privateKey!) as `0x${string}`
|
|
30
|
+
|
|
31
|
+
localStorage.setItem(STORAGE_KEY, pk)
|
|
32
|
+
return hdAccount.address
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type InAppWalletParameters = {
|
|
36
|
+
storageKey?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
inAppWallet.type = 'inAppWallet' as const
|
|
40
|
+
|
|
41
|
+
export function inAppWallet(parameters: InAppWalletParameters = {}) {
|
|
42
|
+
const key = parameters.storageKey ?? STORAGE_KEY
|
|
43
|
+
|
|
44
|
+
type Provider =
|
|
45
|
+
ReturnType<typeof custom> extends (...args: infer A) => infer R ? R : never
|
|
46
|
+
|
|
47
|
+
return createConnector<Provider>((config) => {
|
|
48
|
+
let account: PrivateKeyAccount | null = null
|
|
49
|
+
let currentChainId: number = config.chains[0].id
|
|
50
|
+
|
|
51
|
+
function loadAccount(): PrivateKeyAccount | null {
|
|
52
|
+
if (typeof window === 'undefined') return null
|
|
53
|
+
try {
|
|
54
|
+
const stored = localStorage.getItem(key)
|
|
55
|
+
if (stored?.startsWith('0x')) {
|
|
56
|
+
account = privateKeyToAccount(stored as `0x${string}`)
|
|
57
|
+
return account
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getChain(chainId?: number) {
|
|
64
|
+
return (
|
|
65
|
+
config.chains.find((c) => c.id === (chainId ?? currentChainId)) ??
|
|
66
|
+
config.chains[0]
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
id: 'inAppWallet',
|
|
72
|
+
name: 'In App',
|
|
73
|
+
type: inAppWallet.type,
|
|
74
|
+
|
|
75
|
+
async connect({ chainId } = {}) {
|
|
76
|
+
const acct = account ?? loadAccount()
|
|
77
|
+
if (!acct) throw new Error('No in-app wallet key found in storage')
|
|
78
|
+
|
|
79
|
+
if (chainId) currentChainId = chainId
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
accounts: [getAddress(acct.address)],
|
|
83
|
+
chainId: currentChainId,
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async disconnect() {
|
|
88
|
+
account = null
|
|
89
|
+
if (typeof window !== 'undefined') {
|
|
90
|
+
localStorage.removeItem(key)
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async getAccounts() {
|
|
95
|
+
const acct = account ?? loadAccount()
|
|
96
|
+
return acct ? [getAddress(acct.address)] : []
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async getChainId() {
|
|
100
|
+
return currentChainId
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
async getProvider() {
|
|
104
|
+
const chain = getChain()
|
|
105
|
+
const transport = config.transports?.[chain.id] ?? http()
|
|
106
|
+
|
|
107
|
+
const request = async ({
|
|
108
|
+
method,
|
|
109
|
+
params,
|
|
110
|
+
}: {
|
|
111
|
+
method: string
|
|
112
|
+
params?: unknown[]
|
|
113
|
+
}): Promise<unknown> => {
|
|
114
|
+
// Account methods
|
|
115
|
+
if (method === 'eth_accounts' || method === 'eth_requestAccounts') {
|
|
116
|
+
return account ? [account.address] : []
|
|
117
|
+
}
|
|
118
|
+
if (method === 'eth_chainId') {
|
|
119
|
+
return numberToHex(currentChainId)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Signing methods — handled locally
|
|
123
|
+
if (method === 'personal_sign') {
|
|
124
|
+
if (!account) throw new Error('Not connected')
|
|
125
|
+
const [data] = params as [Hex, Address]
|
|
126
|
+
return account.signMessage({ message: { raw: data } })
|
|
127
|
+
}
|
|
128
|
+
if (method === 'eth_signTypedData_v4') {
|
|
129
|
+
if (!account) throw new Error('Not connected')
|
|
130
|
+
const [, typedDataJson] = params as [Address, string]
|
|
131
|
+
const typedData = JSON.parse(typedDataJson)
|
|
132
|
+
return account.signTypedData(typedData)
|
|
133
|
+
}
|
|
134
|
+
if (method === 'eth_sign') {
|
|
135
|
+
if (!account) throw new Error('Not connected')
|
|
136
|
+
const [, data] = params as [Address, Hex]
|
|
137
|
+
return account.sign!({ hash: data })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Send transaction — sign locally, broadcast via RPC
|
|
141
|
+
if (method === 'eth_sendTransaction') {
|
|
142
|
+
if (!account) throw new Error('Not connected')
|
|
143
|
+
const [tx] = params as [Record<string, string>]
|
|
144
|
+
const walletClient = createWalletClient({
|
|
145
|
+
account,
|
|
146
|
+
chain,
|
|
147
|
+
transport,
|
|
148
|
+
})
|
|
149
|
+
return walletClient.sendTransaction({
|
|
150
|
+
chain,
|
|
151
|
+
to: tx.to as Address,
|
|
152
|
+
data: tx.data as Hex | undefined,
|
|
153
|
+
value: tx.value ? hexToBigInt(tx.value as Hex) : undefined,
|
|
154
|
+
gas: tx.gas ? hexToBigInt(tx.gas as Hex) : undefined,
|
|
155
|
+
nonce:
|
|
156
|
+
tx.nonce != null ? hexToNumber(tx.nonce as Hex) : undefined,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Chain switching
|
|
161
|
+
if (method === 'wallet_switchEthereumChain') {
|
|
162
|
+
const [{ chainId: hexChainId }] = params as [
|
|
163
|
+
{ chainId: `0x${string}` },
|
|
164
|
+
]
|
|
165
|
+
const newChainId = hexToNumber(hexChainId)
|
|
166
|
+
const chain = config.chains.find((c) => c.id === newChainId)
|
|
167
|
+
if (!chain) throw new Error('Chain not configured')
|
|
168
|
+
currentChainId = newChainId
|
|
169
|
+
config.emitter.emit('change', { chainId: newChainId })
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Everything else — forward to RPC
|
|
174
|
+
const publicClient = createPublicClient({ chain, transport })
|
|
175
|
+
return (
|
|
176
|
+
publicClient as unknown as {
|
|
177
|
+
request: (args: {
|
|
178
|
+
method: string
|
|
179
|
+
params?: unknown[]
|
|
180
|
+
}) => Promise<unknown>
|
|
181
|
+
}
|
|
182
|
+
).request({ method, params: params as unknown[] })
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return custom({ request })({ retryCount: 0 })
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
async isAuthorized() {
|
|
189
|
+
const acct = account ?? loadAccount()
|
|
190
|
+
return !!acct
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
async switchChain({ chainId }) {
|
|
194
|
+
const chain = config.chains.find((c) => c.id === chainId)
|
|
195
|
+
if (!chain) throw new Error('Chain not configured')
|
|
196
|
+
currentChainId = chainId
|
|
197
|
+
config.emitter.emit('change', { chainId })
|
|
198
|
+
return chain
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
onAccountsChanged(accounts) {
|
|
202
|
+
if (accounts.length === 0) this.onDisconnect()
|
|
203
|
+
else
|
|
204
|
+
config.emitter.emit('change', {
|
|
205
|
+
accounts: accounts.map((a) => getAddress(a)),
|
|
206
|
+
})
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
onChainChanged(chain) {
|
|
210
|
+
const chainId = Number(chain)
|
|
211
|
+
config.emitter.emit('change', { chainId })
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
onDisconnect() {
|
|
215
|
+
config.emitter.emit('disconnect')
|
|
216
|
+
account = null
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
}
|
package/src/evm/index.ts
CHANGED
|
@@ -34,6 +34,9 @@ export { usePriceFeed } from './composables/priceFeed'
|
|
|
34
34
|
export { useWalletExplorer } from './composables/walletExplorer'
|
|
35
35
|
export type { ExplorerWallet } from './composables/walletExplorer'
|
|
36
36
|
|
|
37
|
+
// Connectors
|
|
38
|
+
export { inAppWallet, prepareInAppWallet } from './connectors/inAppWallet'
|
|
39
|
+
|
|
37
40
|
// Components
|
|
38
41
|
export { default as EvmAccount } from './components/EvmAccount.vue'
|
|
39
42
|
export { default as EvmAvatar } from './components/EvmAvatar.vue'
|
|
@@ -45,3 +48,5 @@ export { default as EvmMetaMaskQR } from './components/EvmMetaMaskQR.vue'
|
|
|
45
48
|
export { default as EvmWalletConnectQR } from './components/EvmWalletConnectQR.vue'
|
|
46
49
|
export { default as EvmWalletConnectWallets } from './components/EvmWalletConnectWallets.vue'
|
|
47
50
|
export { default as EvmTransactionFlow } from './components/EvmTransactionFlow.vue'
|
|
51
|
+
export { default as EvmSeedPhraseInput } from './components/EvmSeedPhraseInput.vue'
|
|
52
|
+
export { default as EvmInAppWalletSetup } from './components/EvmInAppWalletSetup.vue'
|