Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ Each Layer2 follows a specific pattern:
**Bitcoin (UTXO-based)**
- Uses `WatchOnlyWallet` wrapping `HDSegwitBech32Wallet`
- BIP84 derivation: `m/84'/0'/{account}'`
- Electrum server (electrum.layerzwallet.com) for balance/tx/UTXO queries
- Electrum server (electrum2.layerzwallet.com:50005) for balance/tx/UTXO queries
- Gap limit of 20 for address discovery

**EVM Chains (Rootstock, Botanix, Citrea, Alpen)**
Expand Down
22 changes: 22 additions & 0 deletions mobile/.maestro/subflows/input-spark-address.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ appId: com.layerzwallet.mobile
- tapOn:
id: 'send-address-input'
- waitForAnimationToEnd
# Extra wait so the iOS soft keyboard has time to fully present before the first
# eraseText/inputText. Maestro's `Keyboard (First Match)` probe hard-fails after 92s
# when the keyboard isn't up yet; the default ~300ms waitForAnimationToEnd is not
# always enough on CI simulators (especially when the simulator is in a state where
# the hardware keyboard suppresses the soft keyboard on first focus).
- runFlow:
file: sleep.yml
env:
TIMEOUT: 1500
- eraseText: 1
- inputText: 'spark1pgssyjcyyyp'
- runFlow:
Expand All @@ -27,3 +36,16 @@ appId: com.layerzwallet.mobile
env:
TIMEOUT: 1000
- assertNotVisible: 'Invalid address'
# Dismiss the soft keyboard so the bottom Next button is reachable.
# (Without this the IME covers send-address-next-button on Android, and on iOS the
# button sits below the keyboard since send-address.tsx no longer uses KeyboardAvoidingView.)
# We tap an empty area instead of `hideKeyboard` because that command hard-fails on
# iOS when the keyboard has no inputAccessoryView/Done button, even after the keyboard
# successfully dismisses. Mirrors the convention in swap.yml of tapping a non-input area.
# y=22% lands in the padding between the address card (ends ~y=24% of screen) and the
# Tokens section header (starts ~y=32%). Critically, this point is *above* any soft
# keyboard on both iOS (top ~y=65%) and Android (top ~y=55%), so the tap can't accidentally
# land on a keyboard key like 50%,50% would on Android.
- tapOn:
point: '50%,22%'
- waitForAnimationToEnd
71 changes: 33 additions & 38 deletions mobile/app/send/send-address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Ionicons } from '@expo/vector-icons';
import * as bip21 from 'bip21';
import { Stack, useRouter } from 'expo-router';
import React, { useCallback, useContext, useRef, useState } from 'react';
import { KeyboardAvoidingView, Platform, StyleSheet, TextInput, View } from 'react-native';
import { StyleSheet, TextInput, View } from 'react-native';
import Pressable from '../../components/Pressable';

import RadialGradientScreen from '@/components/RadialGradientScreen';
Expand Down Expand Up @@ -91,51 +91,46 @@ const SendAddress: React.FC = () => {
<Stack.Screen options={{ headerShown: false }} />
<ScreenSendHeader network={network} title={`Send ${getTickerByNetwork(network)}`} />

<KeyboardAvoidingView style={styles.keyboardAvoidingView} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}>
<View style={styles.container}>
<View style={styles.inputSection}>
<View style={styles.inputContainer}>
<Pressable style={styles.inputWrapper} onPress={handleInputWrapperPress} activeOpacity={1} testID="send-address-input">
<ThemedText style={styles.inputLabel}>To</ThemedText>
<TextInput
ref={inputRef}
style={styles.input}
placeholder="Enter address"
placeholderTextColor="rgba(255, 255, 255, 0.8)"
autoCapitalize="none"
autoCorrect={false}
onChangeText={setLocalAddress}
value={localAddress}
/>
</Pressable>
<Pressable style={styles.scanButton} onPress={handleScanQR}>
<Ionicons name="scan-outline" size={24} color="rgba(255, 255, 255, 0.8)" />
</Pressable>
</View>

{errorMessage && (
<View style={styles.errorContainer}>
<Ionicons name="close" size={16} color="white" />
<ThemedText style={styles.errorText}>{errorMessage}</ThemedText>
</View>
)}
<View style={styles.container}>
<View style={styles.inputSection}>
<View style={styles.inputContainer}>
<Pressable style={styles.inputWrapper} onPress={handleInputWrapperPress} activeOpacity={1} testID="send-address-input">
<ThemedText style={styles.inputLabel}>To</ThemedText>
<TextInput
ref={inputRef}
style={styles.input}
placeholder="Enter address"
placeholderTextColor="rgba(255, 255, 255, 0.8)"
autoCapitalize="none"
autoCorrect={false}
onChangeText={setLocalAddress}
value={localAddress}
/>
</Pressable>
<Pressable style={styles.scanButton} onPress={handleScanQR}>
<Ionicons name="scan-outline" size={24} color="rgba(255, 255, 255, 0.8)" />
</Pressable>
</View>

<TokensView onTokenPress={handleTokenPress} selectedToken={token} />

<Pressable style={[styles.continueButton, !localAddress.trim() && styles.disabledButton]} onPress={handleContinue} disabled={!localAddress.trim()} testID="send-address-next-button">
<ThemedText style={styles.continueButtonText}>Next</ThemedText>
</Pressable>
{errorMessage && (
<View style={styles.errorContainer}>
<Ionicons name="close" size={16} color="white" />
<ThemedText style={styles.errorText}>{errorMessage}</ThemedText>
</View>
)}
</View>
</KeyboardAvoidingView>

<TokensView onTokenPress={handleTokenPress} selectedToken={token} />

<Pressable style={[styles.continueButton, !localAddress.trim() && styles.disabledButton]} onPress={handleContinue} disabled={!localAddress.trim()} testID="send-address-next-button">
<ThemedText style={styles.continueButtonText}>Next</ThemedText>
</Pressable>
</View>
</RadialGradientScreen>
);
};

const styles = StyleSheet.create({
keyboardAvoidingView: {
flex: 1,
},
container: {
flex: 1,
paddingHorizontal: 16,
Expand Down
2 changes: 1 addition & 1 deletion shared/blue_modules/BlueElectrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ type Peer = {
path?: string;
};

export const hardcodedPeers: Peer[] = [{ host: 'electrum.layerzwallet.com', port: 50004 }];
export const hardcodedPeers: Peer[] = [{ host: 'electrum2.layerzwallet.com', port: 50005 }];

let mainClient: WsElectrumClient;
export let mainConnected: boolean = false;
Expand Down
4 changes: 2 additions & 2 deletions shared/tests/integration-vi/blue-electrum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ beforeAll(async () => {
});

test.skip('can do websocket electrum request', async () => {
const socket = new WebSocket('wss://electrum.layerzwallet.com:50004');
const socket = new WebSocket('wss://electrum2.layerzwallet.com:50005');
let gotMessage = false;

socket.addEventListener('open', (event) => {
Expand Down Expand Up @@ -118,7 +118,7 @@ describe('BlueElectrum', () => {
assert.ok(!(await BlueElectrum.testConnection('joyreactor.cc', 443)));
assert.ok(!(await BlueElectrum.testConnection('joyreactor.cc', 80)));

assert.ok(await BlueElectrum.testConnection('electrum.layerzwallet.com', 50004));
assert.ok(await BlueElectrum.testConnection('electrum2.layerzwallet.com', 50005));
});

it('ElectrumClient can estimate fees', async () => {
Expand Down
Loading