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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## 0.0.10

* Expose `BitboxManager.getDeviceStatus()`, returning the SDK's cached firmware
status string (`uninitialized` / `seeded` / `initialized`). It reads the locally
cached status without a device round-trip, so the host app can tell an unseeded
device (no wallet set up yet) apart from a transient empty address read after
pairing instead of failing both the same way.

## 0.0.9

* Android: force 16 KB ELF page alignment on the gomobile-built native libs
(`-extldflags=-Wl,-z,max-page-size=16384`). `libgojni.so` was the only 4 KB-aligned
library in the bundle; Android 15+ devices using 16 KB memory pages (and Google
Play) require ≥ 16 KB alignment.

## 0.0.8

* Android: run the blocking `initBitBox` (Noise pairing handshake) off the serial
Expand Down
Binary file modified android/libs/api-sources.jar
Binary file not shown.
Binary file modified android/libs/api.aar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.cakewallet.bitbox_flutter.operations.ETHSignMessageOperation
import com.cakewallet.bitbox_flutter.operations.ETHSignTransactionOperation
import com.cakewallet.bitbox_flutter.operations.ETHSignTypedMessageOperation
import com.cakewallet.bitbox_flutter.operations.GetChannelHashOperation
import com.cakewallet.bitbox_flutter.operations.GetDeviceStatusOperation
import com.cakewallet.bitbox_flutter.operations.GetDevicesOperation
import com.cakewallet.bitbox_flutter.operations.ETHGetAddressOperation
import com.cakewallet.bitbox_flutter.operations.ETHSignRLPTransactionOperation
Expand Down Expand Up @@ -53,6 +54,7 @@ class BitboxFlutterPlugin : FlutterPlugin, MethodCallHandler {
registry.registerMethodCall("initBitBox", InitBitBoxOperation(bitboxManager))
registry.registerMethodCall("getChannelHash", GetChannelHashOperation(bitboxManager))
registry.registerMethodCall("channelHashVerify", ChannelHashVerifyOperation(bitboxManager))
registry.registerMethodCall("getDeviceStatus", GetDeviceStatusOperation(bitboxManager))
registry.registerMethodCall("getMasterFingerprint", GetMasterFingerprintOperation(bitboxManager))
registry.registerMethodCall("supportsETH", SupportsETHOperation(bitboxManager))
registry.registerMethodCall("supportsERC20", SupportsERC20Operation(bitboxManager))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.cakewallet.bitbox_flutter.operations

import android.content.Context
import api.Api
import com.cakewallet.bitbox_flutter.BitboxManager
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

class GetDeviceStatusOperation(manager: BitboxManager) : UsbMethodCallOperation(manager.usbManager) {
override fun onMethodCall(
context: Context,
methodCall: MethodCall,
result: MethodChannel.Result
) {
// Api.deviceStatus() reads the SDK's cached firmware status — no device
// round-trip — so it is safe to run on the serial queue like getChannelHash.
val status = Api.deviceStatus()
result.success(status)
}
}
17 changes: 17 additions & 0 deletions go/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,23 @@ func InitDevice() (success bool) {
return true
}

// DeviceStatus returns the firmware status of the paired device as a string
// (e.g. "uninitialized", "seeded", "initialized"). It reads the cached status
// the SDK maintains, so it does not perform a device round-trip and cannot
// block. An empty string is returned when there is no device. Callers use this
// after pairing to tell an unseeded device (no wallet set up yet) apart from a
// transient empty address read.
//
//export DeviceStatus
func DeviceStatus() (status string) {
defer recoverPanic("DeviceStatus")

if bitbox == nil {
return ""
}
return string(bitbox.Status())
}

//export SupportsETH
func SupportsETH(chainId int) (supported bool) {
defer recoverPanic("SupportsETH")
Expand Down
1 change: 1 addition & 0 deletions go/api/bitbox_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type bitboxDevice interface {
Init() error
ChannelHash() (string, bool)
ChannelHashVerify(ok bool)
Status() firmware.Status
DeviceInfo() (*firmware.DeviceInfo, error)
RootFingerprint() ([]byte, error)
SupportsETH(chainID uint64) bool
Expand Down
14 changes: 14 additions & 0 deletions go/api/fake_bitbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type fakeBitboxDevice struct {
channelHashOk bool
channelHashVerified *bool

status firmware.Status

deviceInfo *firmware.DeviceInfo
deviceInfoErr error
rootFingerprint []byte
Expand Down Expand Up @@ -67,6 +69,11 @@ func (f *fakeBitboxDevice) ChannelHashVerify(ok bool) {
f.channelHashVerified = &ok
}

func (f *fakeBitboxDevice) Status() firmware.Status {
f.calls = append(f.calls, "Status")
return f.status
}

func (f *fakeBitboxDevice) DeviceInfo() (*firmware.DeviceInfo, error) {
f.calls = append(f.calls, "DeviceInfo")
return f.deviceInfo, f.deviceInfoErr
Expand Down Expand Up @@ -155,6 +162,7 @@ func TestFakeBitboxHarnessSimulatesPairingAndCapabilities(t *testing.T) {
channelHash: "PAIR-CODE",
channelHashOk: true,
channelHashVerified: &verified,
status: firmware.StatusInitialized,
deviceInfo: &firmware.DeviceInfo{Name: "Simulated BitBox"},
rootFingerprint: []byte{0x01, 0x02, 0x03, 0x04},
supportsETH: true,
Expand All @@ -180,6 +188,9 @@ func TestFakeBitboxHarnessSimulatesPairingAndCapabilities(t *testing.T) {
if got := DeviceInfo().Name; got != "Simulated BitBox" {
t.Fatalf("expected simulated device info, got %q", got)
}
if got := DeviceStatus(); got != string(firmware.StatusInitialized) {
t.Fatalf("expected simulated device status, got %q", got)
}
if got := GetMasterFingerprint(); !reflect.DeepEqual(got, []byte{0x01, 0x02, 0x03, 0x04}) {
t.Fatalf("expected simulated root fingerprint, got %x", got)
}
Expand Down Expand Up @@ -297,6 +308,9 @@ func TestExportedAPIsReturnZeroValuesWithoutDeviceInsteadOfCrashing(t *testing.T
if got := DeviceInfo(); got.Name != "" {
t.Fatalf("expected zero device info without device, got %+v", got)
}
if got := DeviceStatus(); got != "" {
t.Fatalf("expected empty device status without device, got %q", got)
}
if got := ETHGetAddress(1, keypath, int(messages.ETHPubRequest_ADDRESS), false, nil); got != "" {
t.Fatalf("expected empty ETH address without device, got %q", got)
}
Expand Down
10 changes: 5 additions & 5 deletions ios/Api.xcframework/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,32 @@
<key>BinaryPath</key>
<string>Api.framework/Api</string>
<key>LibraryIdentifier</key>
<string>ios-arm64</string>
<string>ios-arm64_x86_64-simulator</string>
<key>LibraryPath</key>
<string>Api.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
<dict>
<key>BinaryPath</key>
<string>Api.framework/Api</string>
<key>LibraryIdentifier</key>
<string>ios-arm64_x86_64-simulator</string>
<string>ios-arm64</string>
<key>LibraryPath</key>
<string>Api.framework</string>
<key>SupportedArchitectures</key>
<array>
<string>arm64</string>
<string>x86_64</string>
</array>
<key>SupportedPlatform</key>
<string>ios</string>
<key>SupportedPlatformVariant</key>
<string>simulator</string>
</dict>
</array>
<key>CFBundlePackageType</key>
Expand Down
Binary file modified ios/Api.xcframework/ios-arm64/Api.framework/Api
Binary file not shown.
10 changes: 10 additions & 0 deletions ios/Api.xcframework/ios-arm64/Api.framework/Headers/Api.objc.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ FOUNDATION_EXPORT void ApiChannelHashVerify(BOOL ok);
// skipped function DeviceInfo with unsupported parameter or return types


/**
* DeviceStatus returns the firmware status of the paired device as a string
(e.g. "uninitialized", "seeded", "initialized"). It reads the cached status
the SDK maintains, so it does not perform a device round-trip and cannot
block. An empty string is returned when there is no device. Callers use this
after pairing to tell an unseeded device (no wallet set up yet) apart from a
transient empty address read.
*/
FOUNDATION_EXPORT NSString* _Nonnull ApiDeviceStatus(void);

FOUNDATION_EXPORT NSString* _Nonnull ApiETHGetAddress(long chainId, NSString* _Nullable keypath, long outputType, BOOL display, NSData* _Nullable contractAddress);

FOUNDATION_EXPORT NSData* _Nullable ApiETHSignEIP1559(long chainId, NSString* _Nullable keypath, long nonce, NSString* _Nullable maxPriorityFeePerGas, NSString* _Nullable maxFeePerGas, long gasLimit, NSData* _Nullable recipient, NSString* _Nullable value, NSData* _Nullable data, long recipientAddressCase);
Expand Down
4 changes: 2 additions & 2 deletions ios/Api.xcframework/ios-arm64/Api.framework/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<key>MinimumOSVersion</key>
<string>100.0</string>
<key>CFBundleShortVersionString</key>
<string>0.0.1779133764</string>
<string>0.0.1781025390</string>
<key>CFBundleVersion</key>
<string>0.0.1779133764</string>
<string>0.0.1781025390</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
</dict>
Expand Down
Binary file modified ios/Api.xcframework/ios-arm64_x86_64-simulator/Api.framework/Api
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ FOUNDATION_EXPORT void ApiChannelHashVerify(BOOL ok);
// skipped function DeviceInfo with unsupported parameter or return types


/**
* DeviceStatus returns the firmware status of the paired device as a string
(e.g. "uninitialized", "seeded", "initialized"). It reads the cached status
the SDK maintains, so it does not perform a device round-trip and cannot
block. An empty string is returned when there is no device. Callers use this
after pairing to tell an unseeded device (no wallet set up yet) apart from a
transient empty address read.
*/
FOUNDATION_EXPORT NSString* _Nonnull ApiDeviceStatus(void);

FOUNDATION_EXPORT NSString* _Nonnull ApiETHGetAddress(long chainId, NSString* _Nullable keypath, long outputType, BOOL display, NSData* _Nullable contractAddress);

FOUNDATION_EXPORT NSData* _Nullable ApiETHSignEIP1559(long chainId, NSString* _Nullable keypath, long nonce, NSString* _Nullable maxPriorityFeePerGas, NSString* _Nullable maxFeePerGas, long gasLimit, NSData* _Nullable recipient, NSString* _Nullable value, NSData* _Nullable data, long recipientAddressCase);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<key>MinimumOSVersion</key>
<string>100.0</string>
<key>CFBundleShortVersionString</key>
<string>0.0.1779133765</string>
<string>0.0.1781025390</string>
<key>CFBundleVersion</key>
<string>0.0.1779133765</string>
<string>0.0.1781025390</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
</dict>
Expand Down
8 changes: 8 additions & 0 deletions ios/Classes/BitboxFlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class BitboxFlutterPlugin: NSObject, FlutterPlugin {
getChannelHash(result: result)
case "channelHashVerify":
channelHashVerify(result: result)
case "getDeviceStatus":
getDeviceStatus(result: result)
case "supportsETH":
supportsETH(call: call, result: result)
case "supportsERC20":
Expand Down Expand Up @@ -188,6 +190,12 @@ public class BitboxFlutterPlugin: NSObject, FlutterPlugin {
}
}

private func getDeviceStatus(result: @escaping FlutterResult) {
// ApiDeviceStatus() reads the SDK's cached firmware status — no device
// round-trip — so it returns immediately without a background dispatch.
result(ApiDeviceStatus())
}

// MARK: - Feature Support

private func supportsETH(call: FlutterMethodCall, result: @escaping FlutterResult) {
Expand Down
3 changes: 3 additions & 0 deletions lib/bitbox_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class BitboxManager {
Future<bool> channelHashVerify() =>
BitboxUsbPlatform.instance.channelHashVerify();

Future<String> getDeviceStatus() =>
BitboxUsbPlatform.instance.getDeviceStatus();

Future<bool> supportsLTC() => BitboxUsbPlatform.instance.supportsLTC();

Future<bool> supportsETH(int chainId) =>
Expand Down
12 changes: 12 additions & 0 deletions lib/testing/bitbox_testkit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ final class SimulatedBitboxMethod {
static const getMasterFingerprint = 'getMasterFingerprint';
static const getChannelHash = 'getChannelHash';
static const channelHashVerify = 'channelHashVerify';
static const getDeviceStatus = 'getDeviceStatus';
static const supportsETH = 'supportsETH';
static const supportsERC20 = 'supportsERC20';
static const supportsLTC = 'supportsLTC';
Expand Down Expand Up @@ -66,6 +67,7 @@ class SimulatedBitboxPlatform extends BitboxUsbPlatform {
this.openResult = true,
this.initResult = true,
this.channelHashVerifyResult = true,
this.deviceStatus = 'initialized',
this.supportsETHResult = true,
this.supportsERC20Result = true,
this.supportsLTCResult = true,
Expand Down Expand Up @@ -156,6 +158,7 @@ class SimulatedBitboxPlatform extends BitboxUsbPlatform {
final bool openResult;
final bool initResult;
final bool channelHashVerifyResult;
final String deviceStatus;
final bool supportsETHResult;
final bool supportsERC20Result;
final bool supportsLTCResult;
Expand Down Expand Up @@ -276,6 +279,13 @@ class SimulatedBitboxPlatform extends BitboxUsbPlatform {
return result;
}

@override
Future<String> getDeviceStatus() => _run(
SimulatedBitboxMethod.getDeviceStatus,
const <String, Object?>{},
deviceStatus,
);

@override
Future<bool> supportsETH(int chainId) => _run(
SimulatedBitboxMethod.supportsETH,
Expand Down Expand Up @@ -522,6 +532,7 @@ SimulatedBitboxPlatform installSimulatedBitboxPlatform({
bool openResult = true,
bool initResult = true,
bool channelHashVerifyResult = true,
String deviceStatus = 'initialized',
bool supportsETHResult = true,
bool supportsERC20Result = true,
bool supportsLTCResult = true,
Expand Down Expand Up @@ -550,6 +561,7 @@ SimulatedBitboxPlatform installSimulatedBitboxPlatform({
openResult: openResult,
initResult: initResult,
channelHashVerifyResult: channelHashVerifyResult,
deviceStatus: deviceStatus,
supportsETHResult: supportsETHResult,
supportsERC20Result: supportsERC20Result,
supportsLTCResult: supportsLTCResult,
Expand Down
7 changes: 7 additions & 0 deletions lib/usb/bitbox_usb_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ class MethodChannelBitboxUsb extends BitboxUsbPlatform {
return result ?? '';
}

@override
Future<String> getDeviceStatus() async {
final result = await methodChannel.invokeMethod<String>('getDeviceStatus');

return result ?? '';
}

@override
Future<Uint8List> getMasterFingerprint() async {
final result =
Expand Down
2 changes: 2 additions & 0 deletions lib/usb/bitbox_usb_platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ abstract class BitboxUsbPlatform extends PlatformInterface {

Future<bool> channelHashVerify();

Future<String> getDeviceStatus();

Future<bool> supportsETH(int chainId);

Future<bool> supportsERC20(String contractAddress);
Expand Down
19 changes: 19 additions & 0 deletions test/bitbox_testkit_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,23 @@ void main() {
throwsA(isA<SimulatedBitboxStateException>()),
);
});

test('reports the simulated device status', () async {
installSimulatedBitboxPlatform();
final manager = BitboxManager();
await manager.connect((await manager.devices).single);

expect(await manager.getDeviceStatus(), 'initialized');
});

test('reports an unseeded device status', () async {
final platform = installSimulatedBitboxPlatform(
deviceStatus: 'uninitialized',
);
final manager = BitboxManager();
await manager.connect((await manager.devices).single);

expect(await manager.getDeviceStatus(), 'uninitialized');
expect(platform.count(SimulatedBitboxMethod.getDeviceStatus), 1);
});
}
Loading