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