Repository: bitcoindevkit/bdk-ffi (language: python)

Issue:
Mirror `AddressInfo` API from Rust BDK
Our current `AddressInfo` is defined as such:
```rust
pub struct AddressInfo {
    pub index: u32,
    pub address: String,
}
```

Whereas the Rust BDK AddressInfo is
```rust
pub struct AddressInfo {
    pub index: u32,
    pub address: Address,
    pub keychain: KeychainKind,
}
```

We previously didn't have all the ingredients to mirror the Rust API but we now do; let's fix this type.

This will ensure that the `Wallet.get_address()` method returns an `AddressInfo` with a proper `Address` inside, with all goodies that come with it.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 56ecd85e..94420fa4 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,7 @@
 </p>
 
 The workspace in this repository creates the `libbdkffi` multi-language library for the Rust-based 
-[bdk] library from the [Bitcoin Dev Kit] project. The `bdk-ffi-bindgen` package builds a tool for 
-generating the actual language binding code used to access the `libbdkffi` library.
+[bdk] library from the [Bitcoin Dev Kit] project.
 
 Each supported language and the platform(s) it's packaged for has its own directory. The Rust code in this project is in the bdk-ffi directory and is a wrapper around the [bdk] library to expose its APIs in a uniform way using the [mozilla/uniffi-rs] bindings generator for each supported target language.
 
diff --git a/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt b/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt
index a4ddc152..31ecb7f0 100644
--- a/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt
+++ b/api-docs/kotlin/src/main/kotlin/org/bitcoindevkit/bdk.kt
@@ -24,12 +24,14 @@ enum class Network {
  *
  * @property index Child index of this address.
  * @property address Address.
+ * @property keychain Type of keychain.
  *
  * @sample org.bitcoindevkit.addressInfoSample
  */
 data class AddressInfo (
     var index: UInt,
-    var address: String
+    var address: Address,
+    var keychain: KeychainKind
 )
 
 /**
@@ -846,6 +848,9 @@ class Address(address: String) {
      * Quoting BIP 173 "inside QR codes uppercase SHOULD be used, as those permit the use of alphanumeric mode, which is 45% more compact than the normal byte mode."
      */
     fun toQrUri(): String
+
+    /** Return the address as a string. */
+    fun asString(): String
 }}
 
 /**
diff --git a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt
index 8d2f028c..842131d5 100644
--- a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt
+++ b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt
@@ -54,7 +54,7 @@ class AndroidLibTest {
     @Test
     fun memoryWalletNewAddress() {
         val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
-        val address = wallet.getAddress(AddressIndex.New).address
+        val address = wallet.getAddress(AddressIndex.New).address.asString()
         assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address)
     }
 
diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl
index c0fa8aa1..c586dd25 100644
--- a/bdk-ffi/src/bdk.udl
+++ b/bdk-ffi/src/bdk.udl
@@ -50,7 +50,8 @@ enum BdkError {
 
 dictionary AddressInfo {
   u32 index;
-  string address;
+  Address address;
+  KeychainKind keychain;
 };
 
 [Enum]
@@ -452,6 +453,8 @@ interface Address {
   Script script_pubkey();
 
   string to_qr_uri();
+
+  string as_string();
 };
 
 [Enum]
diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs
index 92c8230b..07c79cdf 100644
--- a/bdk-ffi/src/lib.rs
+++ b/bdk-ffi/src/lib.rs
@@ -30,9 +30,11 @@ use bdk::keys::bip39::WordCount;
 use bdk::wallet::AddressIndex as BdkAddressIndex;
 use bdk::wallet::AddressInfo as BdkAddressInfo;
 use bdk::LocalUtxo as BdkLocalUtxo;
+use bdk::TransactionDetails as BdkTransactionDetails;
 use bdk::{Balance as BdkBalance, BlockTime, Error as BdkError, FeeRate, KeychainKind};
 use std::convert::From;
 use std::fmt;
+use std::fmt::Debug;
 use std::io::Cursor;
 use std::str::FromStr;
 use std::sync::Arc;
@@ -47,17 +49,20 @@ pub struct ScriptAmount {
 
 /// A derived address and the index it was found at.
 pub struct AddressInfo {
-    /// Child index of this address
+    /// Child index of this address.
     pub index: u32,
-    /// Address
-    pub address: String,
+    /// Address.
+    pub address: Arc<Address>,
+    /// Type of keychain.
+    pub keychain: KeychainKind,
 }
 
 impl From<BdkAddressInfo> for AddressInfo {
-    fn from(x: bdk::wallet::AddressInfo) -> Self {
+    fn from(address_info: BdkAddressInfo) -> Self {
         AddressInfo {
-            index: x.index,
-            address: x.address.to_string(),
+            index: address_info.index,
+            address: Arc::new(Address::from(address_info.address)),
+            keychain: address_info.keychain,
         }
     }
 }
@@ -90,8 +95,8 @@ pub enum AddressIndex {
 }
 
 impl From<AddressIndex> for BdkAddressIndex {
-    fn from(x: AddressIndex) -> Self {
-        match x {
+    fn from(address_index: AddressIndex) -> Self {
+        match address_index {
             AddressIndex::New => BdkAddressIndex::New,
             AddressIndex::LastUnused => BdkAddressIndex::LastUnused,
             AddressIndex::Peek { index } => BdkAddressIndex::Peek(index),
@@ -122,8 +127,8 @@ pub struct TransactionDetails {
     pub confirmation_time: Option<BlockTime>,
 }
 
-impl From<bdk::TransactionDetails> for TransactionDetails {
-    fn from(tx_details: bdk::TransactionDetails) -> Self {
+impl From<BdkTransactionDetails> for TransactionDetails {
+    fn from(tx_details: BdkTransactionDetails) -> Self {
         let optional_tx: Option<Arc<Transaction>> =
             tx_details.transaction.map(|tx| Arc::new(tx.into()));
 
@@ -148,10 +153,10 @@ pub struct OutPoint {
 }
 
 impl From<&OutPoint> for BdkOutPoint {
-    fn from(x: &OutPoint) -> Self {
+    fn from(outpoint: &OutPoint) -> Self {
         BdkOutPoint {
-            txid: Txid::from_str(&x.txid).unwrap(),
-            vout: x.vout,
+            txid: Txid::from_str(&outpoint.txid).unwrap(),
+            vout: outpoint.vout,
         }
     }
 }
@@ -194,11 +199,11 @@ pub struct TxOut {
 }
 
 impl From<&BdkTxOut> for TxOut {
-    fn from(x: &BdkTxOut) -> Self {
+    fn from(tx_out: &BdkTxOut) -> Self {
         TxOut {
-            value: x.value,
+            value: tx_out.value,
             script_pubkey: Arc::new(Script {
-                script: x.script_pubkey.clone(),
+                script: tx_out.script_pubkey.clone(),
             }),
         }
     }
@@ -248,7 +253,7 @@ impl BdkProgress for ProgressHolder {
     }
 }
 
-impl fmt::Debug for ProgressHolder {
+impl Debug for ProgressHolder {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("ProgressHolder").finish_non_exhaustive()
     }
@@ -263,17 +268,17 @@ pub struct TxIn {
 }
 
 impl From<&BdkTxIn> for TxIn {
-    fn from(x: &BdkTxIn) -> Self {
+    fn from(tx_in: &BdkTxIn) -> Self {
         TxIn {
             previous_output: OutPoint {
-                txid: x.previous_output.txid.to_string(),
-                vout: x.previous_output.vout,
+                txid: tx_in.previous_output.txid.to_string(),
+                vout: tx_in.previous_output.vout,
             },
             script_sig: Arc::new(Script {
-                script: x.script_sig.clone(),
+                script: tx_in.script_sig.clone(),
             }),
-            sequence: x.sequence.0,
-            witness: x.witness.to_vec(),
+            sequence: tx_in.sequence.0,
+            witness: tx_in.witness.to_vec(),
         }
     }
 }
@@ -340,14 +345,15 @@ impl Transaction {
     }
 }
 
-impl From<bdk::bitcoin::Transaction> for Transaction {
-    fn from(tx: bdk::bitcoin::Transaction) -> Self {
+impl From<BdkTransaction> for Transaction {
+    fn from(tx: BdkTransaction) -> Self {
         Transaction { internal: tx }
     }
 }
 
 /// A Bitcoin address.
-struct Address {
+#[derive(Debug, PartialEq, Eq)]
+pub struct Address {
     address: BdkAddress,
 }
 
@@ -386,6 +392,16 @@ impl Address {
     fn to_qr_uri(&self) -> String {
         self.address.to_qr_uri()
     }
+
+    fn as_string(&self) -> String {
+        self.address.to_string()
+    }
+}
+
+impl From<BdkAddress> for Address {
+    fn from(address: BdkAddress) -> Self {
+        Address { address }
+    }
 }
 
 /// The method used to produce an address.
diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs
index 0eafeee1..a973b713 100644
--- a/bdk-ffi/src/wallet.rs
+++ b/bdk-ffi/src/wallet.rs
@@ -655,7 +655,8 @@ mod test {
             wallet
                 .get_address(AddressIndex::Peek { index: 2 })
                 .unwrap()
-                .address,
+                .address
+                .as_string(),
             "bcrt1q5g0mq6dkmwzvxscqwgc932jhgcxuqqkjv09tkj"
         );
 
@@ -663,25 +664,38 @@ mod test {
             wallet
                 .get_address(AddressIndex::Peek { index: 1 })
                 .unwrap()
-                .address,
+                .address
+                .as_string(),
             "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
         );
 
         // new index still 0
         assert_eq!(
-            wallet.get_address(AddressIndex::New).unwrap().address,
+            wallet
+                .get_address(AddressIndex::New)
+                .unwrap()
+                .address
+                .as_string(),
             "bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
         );
 
         // new index now 1
         assert_eq!(
-            wallet.get_address(AddressIndex::New).unwrap().address,
+            wallet
+                .get_address(AddressIndex::New)
+                .unwrap()
+                .address
+                .as_string(),
             "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
         );
 
         // new index now 2
         assert_eq!(
-            wallet.get_address(AddressIndex::New).unwrap().address,
+            wallet
+                .get_address(AddressIndex::New)
+                .unwrap()
+                .address
+                .as_string(),
             "bcrt1q5g0mq6dkmwzvxscqwgc932jhgcxuqqkjv09tkj"
         );
 
@@ -690,7 +704,8 @@ mod test {
             wallet
                 .get_address(AddressIndex::Peek { index: 1 })
                 .unwrap()
-                .address,
+                .address
+                .as_string(),
             "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
         );
 
@@ -699,13 +714,18 @@ mod test {
             wallet
                 .get_address(AddressIndex::Reset { index: 0 })
                 .unwrap()
-                .address,
+                .address
+                .as_string(),
             "bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
         );
 
         // new index 1 again
         assert_eq!(
-            wallet.get_address(AddressIndex::New).unwrap().address,
+            wallet
+                .get_address(AddressIndex::New)
+                .unwrap()
+                .address
+                .as_string(),
             "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
         );
     }
@@ -729,12 +749,20 @@ mod test {
         .unwrap();
 
         assert_eq!(
-            wallet.get_address(AddressIndex::New).unwrap().address,
+            wallet
+                .get_address(AddressIndex::New)
+                .unwrap()
+                .address
+                .as_string(),
             "bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
         );
 
         assert_eq!(
-            wallet.get_address(AddressIndex::New).unwrap().address,
+            wallet
+                .get_address(AddressIndex::New)
+                .unwrap()
+                .address
+                .as_string(),
             "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
         );
 
@@ -742,7 +770,8 @@ mod test {
             wallet
                 .get_address(AddressIndex::LastUnused)
                 .unwrap()
-                .address,
+                .address
+                .as_string(),
             "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
         );
 
@@ -750,7 +779,8 @@ mod test {
             wallet
                 .get_internal_address(AddressIndex::New)
                 .unwrap()
-                .address,
+                .address
+                .as_string(),
             "bcrt1qpmz73cyx00r4a5dea469j40ax6d6kqyd67nnpj"
         );
 
@@ -758,7 +788,8 @@ mod test {
             wallet
                 .get_internal_address(AddressIndex::New)
                 .unwrap()
-                .address,
+                .address
+                .as_string(),
             "bcrt1qaux734vuhykww9632v8cmdnk7z2mw5lsf74v6k"
         );
 
@@ -766,7 +797,8 @@ mod test {
             wallet
                 .get_internal_address(AddressIndex::LastUnused)
                 .unwrap()
-                .address,
+                .address
+                .as_string(),
             "bcrt1qaux734vuhykww9632v8cmdnk7z2mw5lsf74v6k"
         );
     }
diff --git a/bdk-swift/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift b/bdk-swift/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift
index c76c87b3..e845cc8e 100644
--- a/bdk-swift/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift
+++ b/bdk-swift/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift
@@ -10,6 +10,6 @@ final class BitcoinDevKitTests: XCTestCase {
         let databaseConfig = DatabaseConfig.memory
         let wallet = try Wallet.init(descriptor: desc, changeDescriptor: nil, network: Network.regtest, databaseConfig: databaseConfig)
         let addressInfo = try wallet.getAddress(addressIndex: AddressIndex.new)
-        XCTAssertEqual(addressInfo.address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs")
+        XCTAssertEqual(addressInfo.address.asString(), "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs")
     }
 }


Test patch:
diff --git a/api-docs/kotlin/src/test/kotlin/org/bitcoindevkit/Samples.kt b/api-docs/kotlin/src/test/kotlin/org/bitcoindevkit/Samples.kt
index 7ea4cda6..934354a4 100644
--- a/api-docs/kotlin/src/test/kotlin/org/bitcoindevkit/Samples.kt
+++ b/api-docs/kotlin/src/test/kotlin/org/bitcoindevkit/Samples.kt
@@ -87,7 +87,7 @@ fun addressInfoSample() {
 
     val newAddress: AddressInfo = wallet.getAddress(AddressIndex.New)
 
-    println("New address at index ${newAddress.index} is ${newAddress.address}")
+    println("New address at index ${newAddress.index} is ${newAddress.address.asString()}")
 }
 
 fun blockchainSample() {
diff --git a/bdk-ffi/tests/bindings/test.py b/bdk-ffi/tests/bindings/test.py
index 5b0c157a..e857dbc7 100644
--- a/bdk-ffi/tests/bindings/test.py
+++ b/bdk-ffi/tests/bindings/test.py
@@ -6,10 +6,5 @@ class TestBdk(unittest.TestCase):
     def test_some_enum(self):
         network = Network.TESTNET
 
-    def test_some_dict(self):
-        a = AddressInfo(index=42, address="testaddress")
-        self.assertEqual(42, a.index)
-        self.assertEqual("testaddress", a.address)
-
 if __name__=='__main__':
     unittest.main()
diff --git a/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt
index 60adc8b3..b9d06d8c 100644
--- a/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt
+++ b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt
@@ -46,7 +46,7 @@ class JvmLibTest {
     @Test
     fun memoryWalletNewAddress() {
         val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
-        val address = wallet.getAddress(AddressIndex.New).address
+        val address = wallet.getAddress(AddressIndex.New).address.asString()
         assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address)
     }
 
diff --git a/bdk-python/tests/test_bdk.py b/bdk-python/tests/test_bdk.py
index 0b8b3ef7..379f7f66 100644
--- a/bdk-python/tests/test_bdk.py
+++ b/bdk-python/tests/test_bdk.py
@@ -27,7 +27,7 @@ def test_address_bip84_testnet(self):
             database_config=db_config
         )
         address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED())
-        address = address_info.address
+        address = address_info.address.as_string()
         # print(f"New address is {address}")
         assert address == "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", f"Wrong address {address}, should be tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e"