diff --git a/.vscode/settings.json b/.vscode/settings.json
index 23e6b4c30d..a95f568ac3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,5 +4,22 @@
"mode": "auto"
}
],
- "typescript.tsdk": "./packages/core-mobile/node_modules/typescript/lib"
+ "typescript.tsdk": "./packages/core-mobile/node_modules/typescript/lib",
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[javascript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[javascriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[json]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ }
}
diff --git a/packages/core-mobile/.nvimlog b/packages/core-mobile/.nvimlog
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/core-mobile/android/app/src/main/AndroidManifest.xml b/packages/core-mobile/android/app/src/main/AndroidManifest.xml
index 37afe82b0f..ba85a7659d 100644
--- a/packages/core-mobile/android/app/src/main/AndroidManifest.xml
+++ b/packages/core-mobile/android/app/src/main/AndroidManifest.xml
@@ -20,6 +20,14 @@
+
+
+
+
+
+
+
+
diff --git a/packages/core-mobile/app/assets/lotties/connect-waves.json b/packages/core-mobile/app/assets/lotties/connect-waves.json
new file mode 100644
index 0000000000..4f768f2a8d
--- /dev/null
+++ b/packages/core-mobile/app/assets/lotties/connect-waves.json
@@ -0,0 +1,1192 @@
+{
+ "v": "5.7.5",
+ "fr": 100,
+ "ip": 0,
+ "op": 300,
+ "w": 180,
+ "h": 180,
+ "nm": "Comp 1",
+ "ddd": 0,
+ "metadata": { "backgroundColor": { "r": 255, "g": 255, "b": 255 } },
+ "assets": [],
+ "layers": [
+ {
+ "ddd": 0,
+ "ind": 1,
+ "ty": 4,
+ "nm": "Bluetooth-logo.svg 1",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 0, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "ao": 0,
+ "hd": true,
+ "shapes": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ty": "gr",
+ "it": [
+ {
+ "ty": "gr",
+ "nm": "SVG",
+ "it": [
+ {
+ "ty": "gr",
+ "nm": "Path",
+ "it": [
+ {
+ "ty": "sh",
+ "d": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "c": true,
+ "v": [
+ [14.144503, 18.121092],
+ [18.520503, 13.745092],
+ [14.140603, 9.367192],
+ [14.144503, 18.121092],
+ [14.144503, 18.121092]
+ ],
+ "i": [
+ [0, 0],
+ [-1.458666666666666, 1.4586666666666677],
+ [1.4599666666666682, 1.4593000000000007],
+ [-0.001300000000000523, -2.9179666666666666],
+ [0, 0]
+ ],
+ "o": [
+ [1.458666666666666, -1.458666666666666],
+ [-1.4599666666666664, -1.4593000000000007],
+ [0.001300000000000523, 2.9179666666666666],
+ [0, 0],
+ [0, 0]
+ ]
+ }
+ }
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "gr",
+ "nm": "Path",
+ "it": [
+ {
+ "ty": "sh",
+ "d": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "c": true,
+ "v": [
+ [14.140603, 36.172892],
+ [18.520503, 31.793992],
+ [14.144503, 27.417992],
+ [14.140603, 36.172892],
+ [14.140603, 36.172892]
+ ],
+ "i": [
+ [0, 0],
+ [-1.4599666666666664, 1.4596333333333327],
+ [1.458666666666666, 1.458666666666666],
+ [0.001300000000000523, -2.918300000000002],
+ [0, 0]
+ ],
+ "o": [
+ [1.4599666666666682, -1.4596333333333362],
+ [-1.458666666666666, -1.458666666666666],
+ [-0.001300000000000523, 2.9182999999999986],
+ [0, 0],
+ [0, 0]
+ ]
+ }
+ }
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "gr",
+ "nm": "Path",
+ "it": [
+ {
+ "ty": "sh",
+ "d": 1,
+ "ks": {
+ "a": 0,
+ "k": {
+ "c": true,
+ "v": [
+ [23.999003000000002, 13.713892],
+ [14.943403, 22.769492],
+ [23.999003000000002, 31.826192000000002],
+ [10.286103, 45.539092],
+ [10.286103, 27.460892],
+ [2.743163, 35.003892],
+ [0, 32.260791999999995],
+ [9.465823, 22.769492],
+ [0, 13.278291999999999],
+ [2.743163, 10.535191999999999],
+ [10.286103, 18.079092],
+ [10.286103, 0],
+ [23.999003000000002, 13.713892],
+ [23.999003000000002, 13.713892]
+ ],
+ "i": [
+ [0, 0],
+ [3.018533333333334, -3.018533333333334],
+ [-3.018533333333334, -3.018900000000002],
+ [4.570966666666667, -4.5709666666666635],
+ [0, 6.026066666666665],
+ [2.5143133333333347, -2.514333333333333],
+ [0.9143876666666669, 0.9143666666666661],
+ [-3.1552743333333337, 3.1637666666666675],
+ [3.1552743333333333, 3.163733333333335],
+ [-0.9143876666666668, 0.9143666666666661],
+ [-2.514313333333334, -2.514633333333334],
+ [0, 6.026364],
+ [-4.570966666666667, -4.571297333333334],
+ [0, 0]
+ ],
+ "o": [
+ [-3.018533333333334, 3.0185333333333357],
+ [3.018533333333334, 3.018900000000002],
+ [-4.570966666666667, 4.57096666666666],
+ [0, -6.026066666666665],
+ [-2.514313333333334, 2.514333333333333],
+ [-0.9143876666666668, -0.9143666666666661],
+ [3.1552743333333333, -3.163766666666664],
+ [-3.155274333333333, -3.163733333333333],
+ [0.9143876666666665, -0.9143666666666661],
+ [2.514313333333333, 2.5146333333333324],
+ [0, -6.026364000000001],
+ [4.570966666666667, 4.571297333333333],
+ [0, 0],
+ [0, 0]
+ ]
+ }
+ }
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "fl",
+ "c": {
+ "a": 0,
+ "k": [
+ 0.1568627450980392, 0.1568627450980392,
+ 0.1803921568627451
+ ],
+ "ix": 2
+ },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "r": 1,
+ "bm": 0
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "tr",
+ "p": {
+ "a": 0,
+ "k": [12.403797149658203, 23.570756912231445],
+ "ix": 2
+ },
+ "a": {
+ "a": 0,
+ "k": [11.99950122833252, 22.769546508789062],
+ "ix": 2
+ },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": {
+ "a": 0,
+ "k": [12.403796672821045, 23.57075595855713],
+ "ix": 2
+ },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 2,
+ "ty": 3,
+ "nm": "",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "ao": 0,
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 3,
+ "ty": 4,
+ "nm": "Wave-1 1:0-stroke-mask",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "td": 1,
+ "ao": 0,
+ "parent": 2,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1 1",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": { "a": 0, "k": [70, 70], "ix": 2 },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "fl",
+ "c": { "a": 0, "k": [0, 0, 0], "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "r": 1,
+ "bm": 0
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 4,
+ "ty": 4,
+ "nm": "Wave-1 1:0",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "tt": 1,
+ "ao": 0,
+ "parent": 2,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1 1",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": { "a": 0, "k": [70, 70], "ix": 2 },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [
+ 0.1568627450980392, 0.1568627450980392, 0.1803921568627451
+ ],
+ "ix": 2
+ },
+ "o": { "a": 0, "k": 0, "ix": 2 },
+ "w": { "a": 0, "k": 4, "ix": 2 },
+ "lc": 1,
+ "lj": 1,
+ "ml": 4
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 5,
+ "ty": 3,
+ "nm": "",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "ao": 0,
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 6,
+ "ty": 4,
+ "nm": "Wave-1 4:0-stroke-mask",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "td": 1,
+ "ao": 0,
+ "parent": 5,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1 4",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "t": 85,
+ "s": [70, 70],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 175,
+ "s": [175, 175],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [88, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "fl",
+ "c": { "a": 0, "k": [0, 0, 0], "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "r": 1,
+ "bm": 0
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 7,
+ "ty": 4,
+ "nm": "Wave-1 4:0",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "tt": 1,
+ "ao": 0,
+ "parent": 5,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1 4",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "t": 85,
+ "s": [70, 70],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 175,
+ "s": [175, 175],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [
+ 0.1568627450980392, 0.1568627450980392, 0.1803921568627451
+ ],
+ "ix": 2
+ },
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "t": 85,
+ "s": [0],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 100,
+ "s": [20],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 175,
+ "s": [0],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "w": { "a": 0, "k": 4, "ix": 2 },
+ "lc": 1,
+ "lj": 1,
+ "ml": 4
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [88, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 8,
+ "ty": 3,
+ "nm": "",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "ao": 0,
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 9,
+ "ty": 4,
+ "nm": "Wave-1 3:0-stroke-mask",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "td": 1,
+ "ao": 0,
+ "parent": 8,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1 3",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "t": 60,
+ "s": [70, 70],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 150,
+ "s": [175, 175],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "fl",
+ "c": { "a": 0, "k": [0, 0, 0], "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "r": 1,
+ "bm": 0
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 10,
+ "ty": 4,
+ "nm": "Wave-1 3:0",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "tt": 1,
+ "ao": 0,
+ "parent": 8,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1 3",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "t": 60,
+ "s": [70, 70],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 150,
+ "s": [175, 175],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [
+ 0.1568627450980392, 0.1568627450980392, 0.1803921568627451
+ ],
+ "ix": 2
+ },
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "t": 60,
+ "s": [0],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 75,
+ "s": [20],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 150,
+ "s": [0],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "w": { "a": 0, "k": 4, "ix": 2 },
+ "lc": 1,
+ "lj": 1,
+ "ml": 4
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 11,
+ "ty": 3,
+ "nm": "",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "ao": 0,
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 12,
+ "ty": 4,
+ "nm": "Wave-1 2:0-stroke-mask",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "td": 1,
+ "ao": 0,
+ "parent": 11,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1 2",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "t": 35,
+ "s": [70, 70],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 125,
+ "s": [175, 175],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "fl",
+ "c": { "a": 0, "k": [0, 0, 0], "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "r": 1,
+ "bm": 0
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 13,
+ "ty": 4,
+ "nm": "Wave-1 2:0",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "tt": 1,
+ "ao": 0,
+ "parent": 11,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1 2",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "t": 35,
+ "s": [70, 70],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 125,
+ "s": [175, 175],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [
+ 0.1568627450980392, 0.1568627450980392, 0.1803921568627451
+ ],
+ "ix": 2
+ },
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "t": 35,
+ "s": [0],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 50,
+ "s": [20],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 125,
+ "s": [0],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "w": { "a": 0, "k": 4, "ix": 2 },
+ "lc": 1,
+ "lj": 1,
+ "ml": 4
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 14,
+ "ty": 3,
+ "nm": "",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "ao": 0,
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 15,
+ "ty": 4,
+ "nm": "Wave-1:0-stroke-mask",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "td": 1,
+ "ao": 0,
+ "parent": 14,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "t": 10,
+ "s": [70, 70],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 100,
+ "s": [175, 175],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ },
+ {
+ "ty": "fl",
+ "c": { "a": 0, "k": [0, 0, 0], "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "r": 1,
+ "bm": 0
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ },
+ {
+ "ddd": 0,
+ "ind": 16,
+ "ty": 4,
+ "nm": "Wave-1:0",
+ "sr": 1,
+ "ks": {
+ "p": { "a": 0, "k": [0, 0], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ },
+ "tt": 1,
+ "ao": 0,
+ "parent": 14,
+ "shapes": [
+ {
+ "ty": "gr",
+ "nm": "Wave-1",
+ "it": [
+ {
+ "ty": "el",
+ "d": 1,
+ "s": {
+ "a": 1,
+ "k": [
+ {
+ "t": 10,
+ "s": [70, 70],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 100,
+ "s": [175, 175],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "p": { "a": 0, "k": [0, 0], "ix": 2 }
+ },
+ {
+ "ty": "st",
+ "c": {
+ "a": 0,
+ "k": [
+ 0.1568627450980392, 0.1568627450980392, 0.1803921568627451
+ ],
+ "ix": 2
+ },
+ "o": {
+ "a": 1,
+ "k": [
+ {
+ "t": 10,
+ "s": [0],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 25,
+ "s": [20],
+ "o": { "x": [0], "y": [0] },
+ "i": { "x": [0.58], "y": [1] }
+ },
+ {
+ "t": 100,
+ "s": [0],
+ "i": { "x": [0.75], "y": [0.75] },
+ "o": { "x": [0.25], "y": [0.25] }
+ }
+ ],
+ "ix": 2
+ },
+ "w": { "a": 0, "k": 4, "ix": 2 },
+ "lc": 1,
+ "lj": 1,
+ "ml": 4
+ },
+ {
+ "ty": "tr",
+ "p": { "a": 0, "k": [90, 90], "ix": 2 },
+ "a": { "a": 0, "k": [0, 0], "ix": 2 },
+ "s": { "a": 0, "k": [100, 100], "ix": 2 },
+ "r": { "a": 0, "k": 0, "ix": 2 },
+ "o": { "a": 0, "k": 100, "ix": 2 },
+ "sk": { "a": 0, "k": 0, "ix": 2 },
+ "sa": { "a": 0, "k": 0, "ix": 2 }
+ }
+ ]
+ }
+ ],
+ "ip": 0,
+ "op": 301,
+ "st": 0,
+ "bm": 0
+ }
+ ],
+ "markers": []
+}
diff --git a/packages/core-mobile/app/new/features/ledger/components/AnimatedIconWithText.tsx b/packages/core-mobile/app/new/features/ledger/components/AnimatedIconWithText.tsx
new file mode 100644
index 0000000000..2e152a4c27
--- /dev/null
+++ b/packages/core-mobile/app/new/features/ledger/components/AnimatedIconWithText.tsx
@@ -0,0 +1,122 @@
+import React from 'react'
+import { View } from 'react-native'
+import LottieView from 'lottie-react-native'
+import { Text, useTheme } from '@avalabs/k2-alpine'
+
+// Import animation at the top level
+const connectWavesAnimation = require('assets/lotties/connect-waves.json')
+
+interface AnimatedIconWithTextProps {
+ /** The icon component to display */
+ icon: React.ReactNode
+ /** The main title text */
+ title: string
+ /** The subtitle/description text */
+ subtitle: string
+ /** Whether to show the animation behind the icon */
+ showAnimation?: boolean
+ /** Custom animation source (defaults to connect-waves.json) */
+ animationSource?: any
+ /** Custom animation size (defaults to 220x220) */
+ animationSize?: { width: number; height: number }
+ /** Custom icon positioning offset for animation centering */
+ animationOffset?: { top: number; left: number }
+ /** Custom color for the animation (defaults to theme textPrimary) */
+ animationColor?: string
+}
+
+export const AnimatedIconWithText: React.FC = ({
+ icon,
+ title,
+ subtitle,
+ showAnimation = false,
+ animationSource = connectWavesAnimation,
+ animationSize = { width: 220, height: 220 },
+ animationColor
+}) => {
+ const {
+ theme: { colors }
+ } = useTheme()
+
+ // Calculate dynamic positioning based on animation size
+ const iconContainerHeight = 44 // Assuming standard icon size
+ const animationRadius = animationSize.width / 2
+ const iconRadius = iconContainerHeight / 2
+
+ // Calculate animation offset to center it around the icon
+ const dynamicAnimationOffset = {
+ top: -(animationRadius - iconRadius),
+ left: -(animationRadius - iconRadius)
+ }
+
+ // Calculate consistent text position regardless of animation state
+ const baseTopPosition = 160 // Base centering position
+ const textOverlapPosition = baseTopPosition + iconContainerHeight + 16 // Keep text close to icon for both states
+
+ return (
+
+
+ {showAnimation && (
+
+ )}
+ {icon}
+
+
+
+ {title}
+
+
+ {subtitle}
+
+
+
+ )
+}
diff --git a/packages/core-mobile/app/new/features/ledger/components/DerivationPathSelector.tsx b/packages/core-mobile/app/new/features/ledger/components/DerivationPathSelector.tsx
index 7863e863c3..9431b673bb 100644
--- a/packages/core-mobile/app/new/features/ledger/components/DerivationPathSelector.tsx
+++ b/packages/core-mobile/app/new/features/ledger/components/DerivationPathSelector.tsx
@@ -1,6 +1,6 @@
-import React, { useState, useCallback } from 'react'
-import { View } from 'react-native'
-import { Text, Button, useTheme, GroupList, Icons } from '@avalabs/k2-alpine'
+import React from 'react'
+import { View, TouchableOpacity } from 'react-native'
+import { Text, useTheme, Icons } from '@avalabs/k2-alpine'
import { ScrollScreen } from 'common/components/ScrollScreen'
import { LedgerDerivationPathType } from 'services/ledger/types'
@@ -17,15 +17,195 @@ interface DerivationPathOption {
interface DerivationPathSelectorProps {
onSelect: (derivationPathType: LedgerDerivationPathType) => void
+ onCancel?: () => void
+}
+
+interface ListItemProps {
+ text: string
+ type: 'benefit' | 'consideration'
+ isFirst?: boolean
+}
+
+const ListItem: React.FC = ({ text, type, isFirst = false }) => {
+ const {
+ theme: { colors }
+ } = useTheme()
+
+ const iconProps =
+ type === 'benefit'
+ ? {
+ Icon: Icons.Custom.CheckSmall,
+ color: colors.$textSuccess
+ }
+ : {
+ Icon: Icons.Custom.RedExclamation,
+ color: colors.$textDanger,
+ width: 16,
+ height: 12
+ }
+
+ return (
+
+
+
+ {text}
+
+
+ )
+}
+
+interface OptionCardProps {
+ option: DerivationPathOption
+ onPress?: () => void
+}
+
+const OptionCard: React.FC = ({ option, onPress }) => {
+ const {
+ theme: { colors }
+ } = useTheme()
+
+ return (
+
+
+
+
+
+
+ {option.title}
+
+
+ {option.subtitle}
+
+
+
+ {onPress && (
+
+ )}
+
+
+ {/* Divider that spans from text start to card end */}
+
+
+
+
+ Benefits
+
+ {option.benefits.map((benefit, index) => (
+
+ ))}
+
+
+ {option.warnings.length > 0 && (
+
+
+ Considerations
+
+ {option.warnings.map((warning, index) => (
+
+ ))}
+
+ )}
+
+ )
}
const derivationPathOptions: DerivationPathOption[] = [
{
type: LedgerDerivationPathType.BIP44,
- title: 'BIP44 (Recommended)',
- subtitle: 'Standard approach for most users',
+ title: 'BIP44',
+ subtitle: 'Recommended for most users',
benefits: [
- 'Faster setup (~15 seconds)',
+ 'Faster setup, about 15 seconds',
'Create new accounts without device',
'Industry standard approach',
'Better for multiple accounts'
@@ -54,210 +234,25 @@ const derivationPathOptions: DerivationPathOption[] = [
export const DerivationPathSelector: React.FC = ({
onSelect
}) => {
- const {
- theme: { colors }
- } = useTheme()
- const [selectedType, setSelectedType] =
- useState(null)
-
- const selectedOption = derivationPathOptions.find(
- option => option.type === selectedType
- )
-
- const renderFooter = useCallback(() => {
- return (
-
-
-
- )
- }, [selectedType, onSelect])
-
- const groupListData = derivationPathOptions.map(option => ({
- title: option.title,
- subtitle: (
-
- {option.subtitle}
-
- ),
- leftIcon: (
-
-
-
- ),
- accessory:
- selectedType === option.type ? (
-
- ) : (
-
- ),
- onPress: () => setSelectedType(option.type)
- }))
-
return (
-
-
-
+ contentContainerStyle={{
+ padding: 16,
+ gap: 16,
+ paddingBottom: 100
+ }}>
+
- {selectedOption && (
-
- {/* Recommendation badge */}
- {selectedOption.recommended && (
-
-
- RECOMMENDED
-
-
- )}
-
- {/* Quick stats */}
-
-
-
-
- Setup Time
-
-
- {selectedOption.setupTime}
-
-
-
-
- New Accounts
-
-
- {selectedOption.newAccountRequirement}
-
-
-
-
-
- {/* Benefits */}
-
-
- ✓ Benefits
-
- {selectedOption.benefits.map((benefit, index) => (
-
-
- •
-
-
- {benefit}
-
-
- ))}
-
-
- {/* Considerations */}
- {selectedOption.warnings.length > 0 && (
-
-
- ⚠️ Considerations
-
- {selectedOption.warnings.map((warning, index) => (
-
-
- •
-
-
- {warning}
-
-
- ))}
-
- )}
-
- )}
+ {derivationPathOptions.map(option => (
+ onSelect(option.type)}
+ />
+ ))}
)
}
diff --git a/packages/core-mobile/app/new/features/ledger/components/EnhancedLedgerSetup.tsx b/packages/core-mobile/app/new/features/ledger/components/EnhancedLedgerSetup.tsx
index 854d2260c3..9b78aa37db 100644
--- a/packages/core-mobile/app/new/features/ledger/components/EnhancedLedgerSetup.tsx
+++ b/packages/core-mobile/app/new/features/ledger/components/EnhancedLedgerSetup.tsx
@@ -2,15 +2,15 @@ import React, { useState, useCallback, useEffect } from 'react'
import { View, Alert } from 'react-native'
import { Text, Button, useTheme, GroupList, Icons } from '@avalabs/k2-alpine'
import { ScrollScreen } from 'common/components/ScrollScreen'
-import { LoadingState } from 'common/components/LoadingState'
import {
LedgerDerivationPathType,
WalletCreationOptions
} from 'services/ledger/types'
-import { useLedgerWallet } from '../hooks/useLedgerWallet'
+import { useLedgerSetupContext } from '../contexts/LedgerSetupContext'
import { DerivationPathSelector } from './DerivationPathSelector'
import { LedgerSetupProgress } from './LedgerSetupProgress'
import { LedgerAppConnection } from './LedgerAppConnection'
+import { AnimatedIconWithText } from './AnimatedIconWithText'
type SetupStep =
| 'path-selection'
@@ -33,16 +33,17 @@ export const EnhancedLedgerSetup: React.FC = ({
theme: { colors }
} = useTheme()
const [currentStep, setCurrentStep] = useState('path-selection')
- const [selectedDerivationPath, setSelectedDerivationPath] =
- useState(null)
- const [connectedDeviceId, setConnectedDeviceId] = useState(
- null
- )
- const [connectedDeviceName, setConnectedDeviceName] =
- useState('Ledger Device')
- const [isCreatingWallet, setIsCreatingWallet] = useState(false)
const {
+ // Context state
+ selectedDerivationPath,
+ connectedDeviceId,
+ connectedDeviceName,
+ isCreatingWallet,
+ setSelectedDerivationPath,
+ setConnectedDevice,
+ setIsCreatingWallet,
+ // Ledger wallet functionality
devices,
isScanning,
isConnecting,
@@ -54,8 +55,9 @@ export const EnhancedLedgerSetup: React.FC = ({
getAvalancheKeys,
createLedgerWallet,
setupProgress,
- keys
- } = useLedgerWallet()
+ keys,
+ resetSetup
+ } = useLedgerSetupContext()
// Check if keys are available and auto-progress to setup
useEffect(() => {
@@ -73,8 +75,7 @@ export const EnhancedLedgerSetup: React.FC = ({
async (deviceId: string, deviceName: string) => {
try {
await connectToDevice(deviceId)
- setConnectedDeviceId(deviceId)
- setConnectedDeviceName(deviceName)
+ setConnectedDevice(deviceId, deviceName)
// Move to app connection step instead of getting keys immediately
setCurrentStep('app-connection')
@@ -86,7 +87,7 @@ export const EnhancedLedgerSetup: React.FC = ({
)
}
},
- [connectToDevice]
+ [connectToDevice, setConnectedDevice]
)
// Start wallet setup (called from setup-progress step)
@@ -140,6 +141,7 @@ export const EnhancedLedgerSetup: React.FC = ({
selectedDerivationPath,
isCreatingWallet,
createLedgerWallet,
+ setIsCreatingWallet,
onComplete,
onCancel
])
@@ -167,15 +169,16 @@ export const EnhancedLedgerSetup: React.FC = ({
setSelectedDerivationPath(derivationPathType)
setCurrentStep('device-connection')
},
- []
+ [setSelectedDerivationPath]
)
const handleCancel = useCallback(async () => {
if (connectedDeviceId) {
await disconnectDevice()
}
+ resetSetup()
onCancel?.()
- }, [connectedDeviceId, disconnectDevice, onCancel])
+ }, [connectedDeviceId, disconnectDevice, resetSetup, onCancel])
const renderCurrentStep = (): React.ReactNode => {
switch (currentStep) {
@@ -339,43 +342,33 @@ const DeviceConnectionStep: React.FC<{
renderFooter={renderFooter}
contentContainerStyle={{ padding: 16, flex: 1 }}>
{isScanning && (
-
-
-
- Searching for Ledger devices...
-
-
+
+ }
+ title="Looking for devices..."
+ subtitle="Make sure your Ledger device is unlocked and the Avalanche app is open"
+ showAnimation={true}
+ />
)}
{!isScanning && devices.length === 0 && (
-
-
-
- No devices found. Make sure your Ledger is connected and unlocked.
-
-
+
+ }
+ title="No devices found"
+ subtitle="Make sure your Ledger is connected and unlocked."
+ showAnimation={false}
+ />
)}
{devices.length > 0 && (
diff --git a/packages/core-mobile/app/new/features/ledger/components/index.ts b/packages/core-mobile/app/new/features/ledger/components/index.ts
index ac676ca023..bca1a150d4 100644
--- a/packages/core-mobile/app/new/features/ledger/components/index.ts
+++ b/packages/core-mobile/app/new/features/ledger/components/index.ts
@@ -1,3 +1,5 @@
export { DerivationPathSelector } from './DerivationPathSelector'
export { LedgerSetupProgress } from './LedgerSetupProgress'
export { EnhancedLedgerSetup } from './EnhancedLedgerSetup'
+export { LedgerAppConnection } from './LedgerAppConnection'
+export { AnimatedIconWithText } from './AnimatedIconWithText'
diff --git a/packages/core-mobile/app/new/features/ledger/contexts/LedgerSetupContext.tsx b/packages/core-mobile/app/new/features/ledger/contexts/LedgerSetupContext.tsx
new file mode 100644
index 0000000000..83f515a3b5
--- /dev/null
+++ b/packages/core-mobile/app/new/features/ledger/contexts/LedgerSetupContext.tsx
@@ -0,0 +1,140 @@
+import React, {
+ createContext,
+ useContext,
+ useState,
+ useCallback,
+ useMemo,
+ ReactNode
+} from 'react'
+import { LedgerDerivationPathType } from 'services/ledger/types'
+import {
+ WalletCreationOptions,
+ useLedgerWallet
+} from '../hooks/useLedgerWallet'
+
+interface LedgerSetupState {
+ selectedDerivationPath: LedgerDerivationPathType | null
+ connectedDeviceId: string | null
+ connectedDeviceName: string
+ isCreatingWallet: boolean
+ hasStartedSetup: boolean
+}
+
+interface LedgerSetupContextValue extends LedgerSetupState {
+ // State setters
+ setSelectedDerivationPath: (path: LedgerDerivationPathType) => void
+ setConnectedDevice: (deviceId: string, deviceName: string) => void
+ setIsCreatingWallet: (creating: boolean) => void
+ setHasStartedSetup: (started: boolean) => void
+
+ // Ledger wallet hook values
+ devices: any[]
+ isScanning: boolean
+ isConnecting: boolean
+ transportState: unknown
+ scanForDevices: () => void
+ connectToDevice: (deviceId: string) => Promise
+ disconnectDevice: () => Promise
+ getSolanaKeys: () => Promise
+ getAvalancheKeys: () => Promise
+ createLedgerWallet: (options: WalletCreationOptions) => Promise
+ setupProgress: any
+ keys: any
+
+ // Helper methods
+ resetSetup: () => void
+}
+
+const LedgerSetupContext = createContext(null)
+
+interface LedgerSetupProviderProps {
+ children: ReactNode
+}
+
+export const LedgerSetupProvider: React.FC = ({
+ children
+}) => {
+ const [state, setState] = useState({
+ selectedDerivationPath: null,
+ connectedDeviceId: null,
+ connectedDeviceName: 'Ledger Device',
+ isCreatingWallet: false,
+ hasStartedSetup: false
+ })
+
+ // Use the existing ledger wallet hook
+ const ledgerWallet = useLedgerWallet()
+
+ const setSelectedDerivationPath = useCallback(
+ (path: LedgerDerivationPathType) => {
+ setState(prev => ({ ...prev, selectedDerivationPath: path }))
+ },
+ []
+ )
+
+ const setConnectedDevice = useCallback(
+ (deviceId: string, deviceName: string) => {
+ setState(prev => ({
+ ...prev,
+ connectedDeviceId: deviceId,
+ connectedDeviceName: deviceName
+ }))
+ },
+ []
+ )
+
+ const setIsCreatingWallet = useCallback((creating: boolean) => {
+ setState(prev => ({ ...prev, isCreatingWallet: creating }))
+ }, [])
+
+ const setHasStartedSetup = useCallback((started: boolean) => {
+ setState(prev => ({ ...prev, hasStartedSetup: started }))
+ }, [])
+
+ const resetSetup = useCallback(() => {
+ setState({
+ selectedDerivationPath: null,
+ connectedDeviceId: null,
+ connectedDeviceName: 'Ledger Device',
+ isCreatingWallet: false,
+ hasStartedSetup: false
+ })
+ }, [])
+
+ const contextValue: LedgerSetupContextValue = useMemo(
+ () => ({
+ ...state,
+ ...ledgerWallet,
+ setSelectedDerivationPath,
+ setConnectedDevice,
+ setIsCreatingWallet,
+ setHasStartedSetup,
+ resetSetup
+ }),
+ [
+ state,
+ ledgerWallet,
+ setSelectedDerivationPath,
+ setConnectedDevice,
+ setIsCreatingWallet,
+ setHasStartedSetup,
+ resetSetup
+ ]
+ )
+
+ return (
+
+ {children}
+
+ )
+}
+
+export const useLedgerSetupContext = (): LedgerSetupContextValue => {
+ const context = useContext(LedgerSetupContext)
+ if (!context) {
+ throw new Error(
+ 'useLedgerSetupContext must be used within a LedgerSetupProvider'
+ )
+ }
+ return context
+}
diff --git a/packages/core-mobile/app/new/features/ledger/index.ts b/packages/core-mobile/app/new/features/ledger/index.ts
new file mode 100644
index 0000000000..18023eec97
--- /dev/null
+++ b/packages/core-mobile/app/new/features/ledger/index.ts
@@ -0,0 +1,2 @@
+export * from './components'
+export * from './contexts/LedgerSetupContext'
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/_layout.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/_layout.tsx
index 9be54fb03a..14730cfecd 100644
--- a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/_layout.tsx
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/_layout.tsx
@@ -28,6 +28,7 @@ export default function AccountSettingsLayout(): JSX.Element {
+
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/importWallet.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/importWallet.tsx
index a0e1c173e5..d57a979592 100644
--- a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/importWallet.tsx
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/importWallet.tsx
@@ -72,7 +72,7 @@ const ImportWalletScreen = (): JSX.Element => {
const handleImportLedger = (): void => {
// @ts-ignore TODO: make routes typesafe
- navigate({ pathname: '/accountSettings/ledger/enhancedSetup' })
+ navigate({ pathname: '/accountSettings/ledger' })
}
const baseData = [
@@ -125,7 +125,7 @@ const ImportWalletScreen = (): JSX.Element => {
),
leftIcon: (
-
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/appConnection.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/appConnection.tsx
new file mode 100644
index 0000000000..86cebed141
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/appConnection.tsx
@@ -0,0 +1,48 @@
+import React, { useCallback, useEffect } from 'react'
+import { useRouter } from 'expo-router'
+import { LedgerAppConnection } from 'new/features/ledger/components/LedgerAppConnection'
+import { useLedgerSetupContext } from 'new/features/ledger/contexts/LedgerSetupContext'
+
+export default function AppConnectionScreen(): JSX.Element {
+ const { push, back } = useRouter()
+
+ const {
+ getSolanaKeys,
+ getAvalancheKeys,
+ connectedDeviceName,
+ selectedDerivationPath,
+ keys,
+ resetSetup,
+ disconnectDevice
+ } = useLedgerSetupContext()
+
+ // Check if keys are available and auto-progress to setup
+ useEffect(() => {
+ if (keys.avalancheKeys && keys.solanaKeys.length > 0) {
+ // @ts-ignore TODO: make routes typesafe
+ push('/accountSettings/ledger/setupProgress')
+ }
+ }, [keys.avalancheKeys, keys.solanaKeys, push])
+
+ const handleComplete = useCallback(() => {
+ // @ts-ignore TODO: make routes typesafe
+ push('/accountSettings/ledger/setupProgress')
+ }, [push])
+
+ const handleCancel = useCallback(async () => {
+ await disconnectDevice()
+ resetSetup()
+ back()
+ }, [disconnectDevice, resetSetup, back])
+
+ return (
+
+ )
+}
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/complete.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/complete.tsx
new file mode 100644
index 0000000000..746fce5308
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/complete.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import { View } from 'react-native'
+import { useRouter } from 'expo-router'
+import { Text, Button, useTheme } from '@avalabs/k2-alpine'
+import { useLedgerSetupContext } from 'new/features/ledger/contexts/LedgerSetupContext'
+
+export default function CompleteScreen(): JSX.Element {
+ const { push } = useRouter()
+ const {
+ theme: { colors }
+ } = useTheme()
+
+ const { resetSetup } = useLedgerSetupContext()
+
+ const handleComplete = (): void => {
+ resetSetup()
+ // Navigate to account management after successful wallet creation
+ // @ts-ignore TODO: make routes typesafe
+ push('/accountSettings/manageAccounts')
+ }
+
+ return (
+
+
+ 🎉 Wallet Created Successfully!
+
+
+ Your Ledger wallet has been set up and is ready to use.
+
+
+
+ )
+}
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/confirmAddresses.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/confirmAddresses.tsx
new file mode 100644
index 0000000000..b8a43c1ef7
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/confirmAddresses.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+import { useRouter } from 'expo-router'
+
+/**
+ * @deprecated This route is deprecated. Use enhancedSetup.tsx instead.
+ * This file redirects to the new enhanced Ledger setup flow.
+ */
+export default function ConfirmAddresses(): JSX.Element {
+ const router = useRouter()
+
+ // Redirect to enhanced setup - this route is deprecated
+ React.useEffect(() => {
+ // @ts-ignore TODO: make routes typesafe
+ router.replace('/accountSettings/ledger')
+ }, [router])
+
+ // Return null while redirecting
+ return <>>
+}
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/connectWallet.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/connectWallet.tsx
new file mode 100644
index 0000000000..c68f73617e
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/connectWallet.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+import { useRouter } from 'expo-router'
+
+/**
+ * @deprecated This route is deprecated. Use enhancedSetup.tsx instead.
+ * This file redirects to the new enhanced Ledger setup flow.
+ */
+export default function ConnectWallet(): JSX.Element {
+ const router = useRouter()
+
+ // Redirect to enhanced setup - this route is deprecated
+ React.useEffect(() => {
+ // @ts-ignore TODO: make routes typesafe
+ router.replace('/accountSettings/ledger')
+ }, [router])
+
+ // Return null while redirecting
+ return <>>
+}
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/deviceConnection.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/deviceConnection.tsx
new file mode 100644
index 0000000000..dfe5b7cb89
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/deviceConnection.tsx
@@ -0,0 +1,177 @@
+import React, { useCallback } from 'react'
+import { View, Alert, ActivityIndicator } from 'react-native'
+import { useRouter } from 'expo-router'
+import { Text, Button, useTheme, GroupList, Icons } from '@avalabs/k2-alpine'
+import { ScrollScreen } from 'common/components/ScrollScreen'
+import { useLedgerSetupContext } from 'new/features/ledger/contexts/LedgerSetupContext'
+import { AnimatedIconWithText } from 'new/features/ledger/components/AnimatedIconWithText'
+
+interface LedgerDevice {
+ id: string
+ name: string
+}
+
+export default function DeviceConnectionScreen(): JSX.Element {
+ const { push, back } = useRouter()
+ const {
+ theme: { colors }
+ } = useTheme()
+
+ const {
+ devices,
+ isScanning,
+ isConnecting,
+ scanForDevices,
+ connectToDevice,
+ setConnectedDevice,
+ resetSetup
+ } = useLedgerSetupContext()
+
+ // Handle device connection
+ const handleDeviceConnection = useCallback(
+ async (deviceId: string, deviceName: string) => {
+ try {
+ await connectToDevice(deviceId)
+ setConnectedDevice(deviceId, deviceName)
+
+ // Navigate to app connection step
+ // @ts-ignore TODO: make routes typesafe
+ push('/accountSettings/ledger/appConnection')
+ } catch (error) {
+ Alert.alert(
+ 'Connection Failed',
+ 'Failed to connect to Ledger device. Please try again.',
+ [{ text: 'OK' }]
+ )
+ }
+ },
+ [connectToDevice, setConnectedDevice, push]
+ )
+
+ const handleCancel = useCallback(() => {
+ resetSetup()
+ back()
+ }, [resetSetup, back])
+
+ const deviceListData = devices.map((device: LedgerDevice) => ({
+ title: device.name || 'Ledger Device',
+ subtitle: (
+
+ Found over Bluetooth
+
+ ),
+ leftIcon: (
+
+
+
+ ),
+ accessory: (
+
+ ),
+ onPress: () => handleDeviceConnection(device.id, device.name)
+ }))
+
+ const renderFooter = useCallback(() => {
+ return (
+
+ {!isScanning && devices.length === 0 && (
+
+ )}
+
+ {isScanning && devices.length === 0 && (
+
+
+
+ )}
+
+
+
+ )
+ }, [
+ isScanning,
+ scanForDevices,
+ devices.length,
+ colors.$textPrimary,
+ handleCancel
+ ])
+
+ return (
+
+ {isScanning && (
+
+ }
+ title="Looking for devices..."
+ subtitle="Make sure your Ledger device is unlocked and the Avalanche app is open"
+ showAnimation={true}
+ />
+ )}
+
+ {!isScanning && devices.length === 0 && (
+
+ }
+ title="Get your Ledger ready"
+ subtitle="Make sure your Ledger device is unlocked and ready to connect"
+ showAnimation={false}
+ />
+ )}
+
+ {devices.length > 0 && (
+
+
+
+ )}
+
+ )
+}
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/index.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/index.tsx
new file mode 100644
index 0000000000..815879afff
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/index.tsx
@@ -0,0 +1,19 @@
+import React, { useEffect } from 'react'
+import { useRouter } from 'expo-router'
+
+/**
+ * Index route for Ledger setup - redirects to path selection
+ * This ensures that navigating to /accountSettings/ledger shows the path selection screen
+ */
+export default function LedgerIndex(): JSX.Element {
+ const router = useRouter()
+
+ useEffect(() => {
+ // Redirect to path selection screen immediately
+ // @ts-ignore TODO: make routes typesafe
+ router.replace('/accountSettings/ledger/pathSelection')
+ }, [router])
+
+ // Return null while redirecting
+ return <>>
+}
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/pathSelection.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/pathSelection.tsx
new file mode 100644
index 0000000000..18c1681a49
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/pathSelection.tsx
@@ -0,0 +1,32 @@
+import React, { useCallback } from 'react'
+import { useRouter } from 'expo-router'
+import { DerivationPathSelector } from 'new/features/ledger/components/DerivationPathSelector'
+import { useLedgerSetupContext } from 'new/features/ledger/contexts/LedgerSetupContext'
+import { LedgerDerivationPathType } from 'services/ledger/types'
+
+export default function PathSelectionScreen(): JSX.Element {
+ const { push, back } = useRouter()
+ const { setSelectedDerivationPath, resetSetup } = useLedgerSetupContext()
+
+ const handleDerivationPathSelect = useCallback(
+ (derivationPathType: LedgerDerivationPathType) => {
+ setSelectedDerivationPath(derivationPathType)
+ // Navigate to device connection step
+ // @ts-ignore TODO: make routes typesafe
+ push('/accountSettings/ledger/deviceConnection')
+ },
+ [setSelectedDerivationPath, push]
+ )
+
+ const handleCancel = useCallback(() => {
+ resetSetup()
+ back()
+ }, [resetSetup, back])
+
+ return (
+
+ )
+}
diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/setupProgress.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/setupProgress.tsx
new file mode 100644
index 0000000000..05dd5f4550
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/ledger/setupProgress.tsx
@@ -0,0 +1,141 @@
+import React, { useCallback, useEffect } from 'react'
+import { View, Alert } from 'react-native'
+import { useRouter } from 'expo-router'
+import { Text, useTheme } from '@avalabs/k2-alpine'
+import { LedgerDerivationPathType } from 'services/wallet/LedgerWallet'
+import { LedgerSetupProgress } from 'new/features/ledger/components/LedgerSetupProgress'
+import { useLedgerSetupContext } from 'new/features/ledger/contexts/LedgerSetupContext'
+import { WalletCreationOptions } from 'new/features/ledger/hooks/useLedgerWallet'
+
+export default function SetupProgressScreen(): JSX.Element {
+ const { push, back } = useRouter()
+ const {
+ theme: { colors }
+ } = useTheme()
+
+ const {
+ connectedDeviceId,
+ connectedDeviceName,
+ selectedDerivationPath,
+ isCreatingWallet,
+ hasStartedSetup,
+ setIsCreatingWallet,
+ setHasStartedSetup,
+ createLedgerWallet,
+ setupProgress,
+ resetSetup,
+ disconnectDevice
+ } = useLedgerSetupContext()
+
+ // Start wallet setup when entering this screen
+ const handleStartSetup = useCallback(async () => {
+ if (!connectedDeviceId || !selectedDerivationPath || isCreatingWallet) {
+ return
+ }
+
+ try {
+ setIsCreatingWallet(true)
+
+ const walletCreationOptions: WalletCreationOptions = {
+ deviceId: connectedDeviceId,
+ deviceName: connectedDeviceName,
+ derivationPathType: selectedDerivationPath,
+ accountCount: 3, // Standard 3 accounts for both BIP44 and Ledger Live
+ progressCallback: (_step, _progress, _totalSteps) => {
+ // Progress callback for UI updates
+ }
+ }
+
+ await createLedgerWallet(walletCreationOptions)
+
+ // Navigate to completion screen
+ // @ts-ignore TODO: make routes typesafe
+ push('/accountSettings/ledger/complete')
+ } catch (error) {
+ // Wallet creation failed
+ Alert.alert(
+ 'Setup Failed',
+ 'Failed to create Ledger wallet. Please try again.',
+ [
+ {
+ text: 'Try Again',
+ onPress: () => {
+ setIsCreatingWallet(false)
+ // @ts-ignore TODO: make routes typesafe
+ back() // Go back to app connection
+ }
+ },
+ {
+ text: 'Cancel',
+ onPress: async () => {
+ setIsCreatingWallet(false)
+ await disconnectDevice()
+ resetSetup()
+ // Navigate back to import wallet screen
+ // @ts-ignore TODO: make routes typesafe
+ push('/accountSettings/importWallet')
+ }
+ }
+ ]
+ )
+ } finally {
+ setIsCreatingWallet(false)
+ }
+ }, [
+ connectedDeviceId,
+ connectedDeviceName,
+ selectedDerivationPath,
+ isCreatingWallet,
+ setIsCreatingWallet,
+ createLedgerWallet,
+ push,
+ back,
+ disconnectDevice,
+ resetSetup
+ ])
+
+ // Auto-start wallet creation when entering this screen
+ useEffect(() => {
+ if (
+ selectedDerivationPath &&
+ connectedDeviceId &&
+ !isCreatingWallet &&
+ !hasStartedSetup
+ ) {
+ console.log('Starting wallet setup...')
+ setHasStartedSetup(true)
+ handleStartSetup()
+ }
+ }, [selectedDerivationPath, connectedDeviceId, isCreatingWallet, hasStartedSetup, setHasStartedSetup, handleStartSetup])
+
+ const handleCancel = useCallback(async () => {
+ await disconnectDevice()
+ resetSetup()
+ // @ts-ignore TODO: make routes typesafe
+ push('/accountSettings/importWallet')
+ }, [disconnectDevice, resetSetup, push])
+
+ if (!setupProgress) {
+ return (
+
+ Initializing setup...
+
+ )
+ }
+
+ return (
+
+ )
+}
diff --git a/packages/core-mobile/app/services/ledger/LedgerService.ts b/packages/core-mobile/app/services/ledger/LedgerService.ts
index e3345668b7..480d0c2401 100644
--- a/packages/core-mobile/app/services/ledger/LedgerService.ts
+++ b/packages/core-mobile/app/services/ledger/LedgerService.ts
@@ -1,5 +1,4 @@
import TransportBLE from '@ledgerhq/react-native-hw-transport-ble'
-import Transport from '@ledgerhq/hw-transport'
import AppAvalanche from '@avalabs/hw-app-avalanche'
import AppSolana from '@ledgerhq/hw-app-solana'
import { NetworkVMType } from '@avalabs/core-chains-sdk'
@@ -13,48 +12,62 @@ import {
import { networks } from 'bitcoinjs-lib'
import Logger from 'utils/Logger'
import bs58 from 'bs58'
-import {
- LEDGER_TIMEOUTS,
- getSolanaDerivationPath
-} from 'new/features/ledger/consts'
-import { assertNotNull } from 'utils/assertions'
-import {
- AddressInfo,
- ExtendedPublicKey,
- PublicKeyInfo,
- LedgerAppType,
- LedgerReturnCode,
- AppInfo
-} from './types'
+
+export interface AddressInfo {
+ id: string
+ address: string
+ derivationPath: string
+ network: string
+}
+
+export interface ExtendedPublicKey {
+ path: string
+ key: string
+ chainCode: string
+}
+
+export interface PublicKeyInfo {
+ key: string
+ derivationPath: string
+ curve: 'secp256k1' | 'ed25519'
+}
+
+export enum LedgerAppType {
+ AVALANCHE = 'Avalanche',
+ SOLANA = 'Solana',
+ ETHEREUM = 'Ethereum',
+ UNKNOWN = 'Unknown'
+}
+
+export const LedgerReturnCode = {
+ SUCCESS: 0x9000,
+ USER_REJECTED: 0x6985,
+ APP_NOT_OPEN: 0x6a80,
+ DEVICE_LOCKED: 0x5515,
+ INVALID_PARAMETER: 0x6b00,
+ COMMAND_NOT_ALLOWED: 0x6986
+} as const
+
+export type LedgerReturnCodeType =
+ typeof LedgerReturnCode[keyof typeof LedgerReturnCode]
+
+export interface AppInfo {
+ applicationName: string
+ version: string
+}
export class LedgerService {
- #transport: TransportBLE | null = null
+ private transport: TransportBLE | null = null
private currentAppType: LedgerAppType = LedgerAppType.UNKNOWN
private appPollingInterval: number | null = null
private appPollingEnabled = false
- // Transport getter/setter with automatic error handling
- private get transport(): TransportBLE {
- assertNotNull(
- this.#transport,
- 'Ledger transport is not initialized. Please connect to a device first.'
- )
- return this.#transport
- }
-
- private set transport(transport: TransportBLE) {
- this.#transport = transport
- }
-
// Connect to Ledger device (transport only, no apps)
async connect(deviceId: string): Promise {
try {
Logger.info('Starting BLE connection attempt with deviceId:', deviceId)
- // Use a longer timeout for connection
- this.transport = await TransportBLE.open(
- deviceId,
- LEDGER_TIMEOUTS.CONNECTION_TIMEOUT
- )
+ // Use a longer timeout for connection (30 seconds)
+ this.transport = await TransportBLE.open(deviceId, 30000)
Logger.info('BLE transport connected successfully')
this.currentAppType = LedgerAppType.UNKNOWN
@@ -79,7 +92,7 @@ export class LedgerService {
this.appPollingEnabled = true
this.appPollingInterval = setInterval(async () => {
try {
- if (!this.#transport || !this.#transport.isConnected) {
+ if (!this.transport || !this.transport.isConnected) {
this.stopAppPolling()
return
}
@@ -97,7 +110,7 @@ export class LedgerService {
Logger.error('Error polling app info', error)
// Don't stop polling on error, just log it
}
- }, LEDGER_TIMEOUTS.APP_POLLING_INTERVAL) // Poll every 2 seconds like the extension
+ }, 2000) // Poll every 2 seconds like the extension
}
// Stop passive app detection polling
@@ -111,7 +124,11 @@ export class LedgerService {
// Get current app info from device
private async getCurrentAppInfo(): Promise {
- return await getLedgerAppInfo(this.transport as Transport)
+ if (!this.transport) {
+ throw new Error('Transport not initialized')
+ }
+
+ return await getLedgerAppInfo(this.transport as any)
}
// Map app name to our enum
@@ -134,10 +151,7 @@ export class LedgerService {
}
// Wait for specific app to be open (passive approach)
- async waitForApp(
- appType: LedgerAppType,
- timeoutMs = LEDGER_TIMEOUTS.APP_WAIT_TIMEOUT
- ): Promise {
+ async waitForApp(appType: LedgerAppType, timeoutMs = 30000): Promise {
const startTime = Date.now()
Logger.info(`Waiting for ${appType} app (timeout: ${timeoutMs}ms)...`)
@@ -151,10 +165,8 @@ export class LedgerService {
return
}
- // Wait before next check
- await new Promise(resolve =>
- setTimeout(resolve, LEDGER_TIMEOUTS.APP_CHECK_DELAY)
- )
+ // Wait 1 second before next check
+ await new Promise(resolve => setTimeout(resolve, 1000))
}
Logger.error(`Timeout waiting for ${appType} app after ${timeoutMs}ms`)
@@ -179,7 +191,7 @@ export class LedgerService {
private async reconnectIfNeeded(deviceId: string): Promise {
Logger.info('Checking if reconnection is needed')
- if (!this.#transport || !this.#transport.isConnected) {
+ if (!this.transport || !this.transport.isConnected) {
Logger.info('Transport is disconnected, attempting reconnection')
try {
await this.connect(deviceId)
@@ -198,6 +210,10 @@ export class LedgerService {
evm: ExtendedPublicKey
avalanche: ExtendedPublicKey
}> {
+ if (!this.transport) {
+ throw new Error('Transport not initialized')
+ }
+
Logger.info('=== getExtendedPublicKeys STARTED ===')
Logger.info('Current app type:', this.currentAppType)
@@ -207,7 +223,7 @@ export class LedgerService {
Logger.info('Avalanche app detected, creating app instance...')
// Create Avalanche app instance
- const avalancheApp = new AppAvalanche(this.transport as Transport)
+ const avalancheApp = new AppAvalanche(this.transport as any)
Logger.info('Avalanche app instance created')
try {
@@ -320,14 +336,13 @@ export class LedgerService {
// Check if Solana app is open
async checkSolanaApp(): Promise {
- if (!this.#transport) {
+ if (!this.transport) {
return false
}
try {
// Create fresh Solana app instance
- const transport = await this.getTransport()
- const solanaApp = new AppSolana(transport as Transport)
+ const solanaApp = new AppSolana(this.transport)
// Try to get a simple address to check if app is open
// Use a standard Solana derivation path
const testPath = "m/44'/501'/0'"
@@ -339,28 +354,23 @@ export class LedgerService {
}
}
- // Get Solana address for a specific derivation path
- async getSolanaAddress(derivationPath: string): Promise<{ address: Buffer }> {
- await this.waitForApp(LedgerAppType.SOLANA)
- const transport = await this.getTransport()
- const solanaApp = new AppSolana(transport as Transport)
- return await solanaApp.getAddress(derivationPath, false)
- }
-
// Get Solana public keys using SDK function (like extension)
async getSolanaPublicKeys(
startIndex: number,
count: number
): Promise {
+ if (!this.transport) {
+ throw new Error('Transport not initialized')
+ }
+
// Create a fresh AppSolana instance for each call (like the SDK does)
- const transport = await this.getTransport()
- const freshSolanaApp = new AppSolana(transport as Transport)
+ const freshSolanaApp = new AppSolana(this.transport)
const publicKeys: PublicKeyInfo[] = []
try {
for (let i = startIndex; i < startIndex + count; i++) {
// Use correct Solana derivation path format
- const derivationPath = getSolanaDerivationPath(i)
+ const derivationPath = `44'/501'/0'/0'/${i}`
// Simple direct call to get Solana address using fresh instance
const result = await freshSolanaApp.getAddress(derivationPath, false)
@@ -392,17 +402,21 @@ export class LedgerService {
startIndex: number,
_count: number
): Promise {
+ if (!this.transport) {
+ throw new Error('Transport not initialized')
+ }
+
try {
// Use the SDK function directly (like the extension does)
const publicKey = await getSolanaPublicKeyFromLedger(
startIndex,
- this.transport as Transport
+ this.transport as any
)
const publicKeys: PublicKeyInfo[] = [
{
key: publicKey.toString('hex'),
- derivationPath: getSolanaDerivationPath(startIndex),
+ derivationPath: `44'/501'/0'/0'/${startIndex}`,
curve: 'ed25519'
}
]
@@ -455,11 +469,15 @@ export class LedgerService {
startIndex: number,
count: number
): Promise {
+ if (!this.transport) {
+ throw new Error('Transport not initialized')
+ }
+
// Connect to Avalanche app
await this.waitForApp(LedgerAppType.AVALANCHE)
// Create Avalanche app instance
- const avalancheApp = new AppAvalanche(this.transport as Transport)
+ const avalancheApp = new AppAvalanche(this.transport as any)
const publicKeys: PublicKeyInfo[] = []
@@ -525,11 +543,15 @@ export class LedgerService {
startIndex: number,
count: number
): Promise {
+ if (!this.transport) {
+ throw new Error('Transport not initialized')
+ }
+
// Connect to Avalanche app
await this.waitForApp(LedgerAppType.AVALANCHE)
// Create Avalanche app instance
- const avalancheApp = new AppAvalanche(this.transport as Transport)
+ const avalancheApp = new AppAvalanche(this.transport as any)
const addresses: AddressInfo[] = []
@@ -640,28 +662,31 @@ export class LedgerService {
// Disconnect from Ledger device
async disconnect(): Promise {
- if (this.#transport) {
- await this.#transport.close()
- this.#transport = null
+ if (this.transport) {
+ await this.transport.close()
+ this.transport = null
this.currentAppType = LedgerAppType.UNKNOWN
this.stopAppPolling() // Stop polling on disconnect
}
}
+ // Get current transport (for wallet usage)
+ getTransport(): TransportBLE {
+ if (!this.transport) {
+ throw new Error('Transport not initialized. Call connect() first.')
+ }
+ return this.transport
+ }
+
// Check if transport is available and connected
isConnected(): boolean {
- return this.#transport !== null && this.#transport.isConnected
+ return this.transport !== null && this.transport.isConnected
}
// Ensure connection is established for a specific device
async ensureConnection(deviceId: string): Promise {
await this.reconnectIfNeeded(deviceId)
- return this.transport
- }
-
- // Get the current transport (for compatibility with existing code)
- async getTransport(): Promise {
- return this.transport
+ return this.getTransport()
}
}
diff --git a/packages/core-mobile/index.js b/packages/core-mobile/index.js
index 38a27a1f0a..d8db055924 100644
--- a/packages/core-mobile/index.js
+++ b/packages/core-mobile/index.js
@@ -13,7 +13,6 @@ import DevDebuggingConfig from 'utils/debugging/DevDebuggingConfig'
import SentryService from 'services/sentry/SentryService'
import NewApp from 'new/ContextApp'
import { expo } from './app.json'
-import { server } from './tests/msw/native/server'
if (__DEV__) {
require('./ReactotronConfig')
diff --git a/packages/core-mobile/ios/AvaxWallet/Info.plist b/packages/core-mobile/ios/AvaxWallet/Info.plist
index 9907e44c87..99f320da77 100644
--- a/packages/core-mobile/ios/AvaxWallet/Info.plist
+++ b/packages/core-mobile/ios/AvaxWallet/Info.plist
@@ -51,6 +51,11 @@
$(CURRENT_PROJECT_VERSION)
ITSAppUsesNonExemptEncryption
+ LSApplicationQueriesSchemes
+
+ twitter
+ mailto
+
LSMinimumSystemVersion
13.3.0
LSRequiresIPhoneOS
diff --git a/packages/k2-alpine/src/assets/icons/bluetooth.svg b/packages/k2-alpine/src/assets/icons/bluetooth.svg
new file mode 100644
index 0000000000..b3563f99bf
--- /dev/null
+++ b/packages/k2-alpine/src/assets/icons/bluetooth.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/k2-alpine/src/assets/icons/ledger_logo.svg b/packages/k2-alpine/src/assets/icons/ledger_logo.svg
new file mode 100644
index 0000000000..c8f53582a5
--- /dev/null
+++ b/packages/k2-alpine/src/assets/icons/ledger_logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/k2-alpine/src/theme/tokens/Icons.ts b/packages/k2-alpine/src/theme/tokens/Icons.ts
index 15b07845e0..36ba0b61ce 100644
--- a/packages/k2-alpine/src/theme/tokens/Icons.ts
+++ b/packages/k2-alpine/src/theme/tokens/Icons.ts
@@ -97,6 +97,7 @@ import IconAch from '../../assets/icons/ach.svg'
import IconDownload from '../../assets/icons/download.svg'
import IconEncrypted from '../../assets/icons/shield.svg'
import IconSwapProviderAuto from '../../assets/icons/swap_auto.svg'
+import IconLedger from '../../assets/icons/ledger_logo.svg'
// Transaction types
import IconTxTypeAdd from '../../assets/icons/tx-type-add.svg'
import IconTxTypeAdvanceTime from '../../assets/icons/advance-time.svg'
@@ -111,6 +112,7 @@ import IconTxTypeSubnet from '../../assets/icons/transaction-subnet.svg'
import IconTxTypeUnwrap from '../../assets/icons/unwrap.svg'
import IconTxTypeUnknown from '../../assets/icons/unknown.svg'
import IconPsychiatry from '../../assets/icons/psychiatry.svg'
+import IconBluetooth from '../../assets/icons/bluetooth.svg'
// token logos
import AAVE from '../../assets/tokenLogos/AAVE.svg'
@@ -332,7 +334,9 @@ export const Icons = {
ShopeePay: IconShopeePay,
Ach: IconAch,
Download: IconDownload,
- SwapProviderAuto: IconSwapProviderAuto
+ SwapProviderAuto: IconSwapProviderAuto,
+ Ledger: IconLedger,
+ Bluetooth: IconBluetooth
},
RecoveryMethod: {
Passkey: IconPasskey,
diff --git a/yarn.lock b/yarn.lock
index 6d01214640..5f09e1ea48 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -33199,7 +33199,7 @@ react-native-webview@ava-labs/react-native-webview:
peerDependencies:
react: "*"
react-native: "*"
- checksum: 44ee8c8ebc4dc4d3423e9045e1aebac31829eb518824e24f41b2bd10ab1e8343e824e9d912f259bfec7bfa798e96513cc05dbdcdf36087b2a43806f74a3b0fa2
+ checksum: 57322e733d20b38881da9514a1ebb03a24a2457ed59683e4f714630c2a0ab29eb9981ad9acb02d7b7b32d7a0f5c02791146fc23c13a39c8254f7d0437844dcb4
languageName: node
linkType: hard