diff --git a/.changes/bech32-hex-conversion.md b/.changes/bech32-hex-conversion.md
new file mode 100644
index 000000000..b7d1ed534
--- /dev/null
+++ b/.changes/bech32-hex-conversion.md
@@ -0,0 +1,5 @@
+---
+"nodejs-binding": minor
+---
+
+Added functions to convert addresses from bech32 to hex and vice versa.
\ No newline at end of file
diff --git a/bindings/nodejs/README.md b/bindings/nodejs/README.md
index e7b6a8008..67e24bb02 100644
--- a/bindings/nodejs/README.md
+++ b/bindings/nodejs/README.md
@@ -242,6 +242,27 @@ Get the balance in iotas for the given addresses.
**Returns** A promise resolving to the list of `{ address, balance }` pairs.
+#### bech32ToHex(bech32)
+
+Returns a parsed hex String from bech32.
+
+| Param | Type | Description |
+| ------- | ------------------- | ------------------------- |
+| bech32 | string
| The address Bech32 string |
+
+**Returns** A String
+
+#### hexToBech32(hex, bech32_hrp (optional))
+
+Returns a parsed bech32 String from hex.
+
+| Param | Type | Description |
+| ----------- | ------------------- | ------------------------- |
+| bech32 | string
| The address Bech32 string |
+| bech32_hrp | string
| The Bech32 hrp string |
+
+**Returns** A String
+
#### isAddressValid(address: string): boolean
Checks if a given address is valid.
@@ -669,7 +690,7 @@ Sets the initial address index. Defaults to 0 if the function isn't called.
#### gapLimit(amount): BalanceGetter
-Sets the gapLimit to specify how many addresses will be checked each round.
+Sets the gapLimit to specify how many addresses will be checked each round.
If gap_limit amount of addresses in a row have no balance the BalanceGetter will return. Defaults to 20 if the function isn't called.
| Param | Type | Description |
diff --git a/bindings/nodejs/lib/index.d.ts b/bindings/nodejs/lib/index.d.ts
index b4569bf8a..b3d2a0bff 100644
--- a/bindings/nodejs/lib/index.d.ts
+++ b/bindings/nodejs/lib/index.d.ts
@@ -81,7 +81,7 @@ export declare class Client {
subscriber(): TopicSubscriber
message(): MessageSender
getUnspentAddress(seed: string): UnspentAddressGetter
- getAddresses(seed: string): AddressGetter
+ getAddresses(seed: string): Promise
findMessages(indexationKeys: string[], messageIds: string[]): Promise
getBalance(seed: string): BalanceGetter
getAddressBalances(addresses: string[]): Promise
@@ -97,6 +97,8 @@ export declare class Client {
findOutputs(outputIds: string[], addresses: string[]): Promise
getAddressOutputs(address: string, options?: AddressOutputsOptions): Promise
getAddressBalance(address: string): Promise
+ bech32ToHex(address: string): string
+ hexToBech32(address: string, bech32_hrp?: string): Promise
isAddressValid(address: string): boolean
getMilestone(index: number): Promise
getMilestoneUtxoChanges(index: number): Promise
diff --git a/bindings/nodejs/lib/index.js b/bindings/nodejs/lib/index.js
index 4781d5316..5b739615d 100644
--- a/bindings/nodejs/lib/index.js
+++ b/bindings/nodejs/lib/index.js
@@ -112,6 +112,7 @@ Client.prototype.retryUntilIncluded = function (msg_id, interval, maxAttempts) {
}
Client.prototype.reattach = promisify(Client.prototype.reattach)
Client.prototype.promote = promisify(Client.prototype.promote)
+Client.prototype.hexToBech32 = promisify(Client.prototype.hexToBech32)
const messageGetterIndexSetter = promisify(MessageGetter.prototype.index)
MessageGetter.prototype.index = function (index) {
diff --git a/bindings/nodejs/native/src/classes/client/api.rs b/bindings/nodejs/native/src/classes/client/api.rs
index 57264f648..21e8ebd66 100644
--- a/bindings/nodejs/native/src/classes/client/api.rs
+++ b/bindings/nodejs/native/src/classes/client/api.rs
@@ -74,6 +74,7 @@ pub(crate) enum Api {
RetryUntilIncluded(MessageId, Option, Option),
Reattach(MessageId),
Promote(MessageId),
+ HexToBech32(String, Option),
}
pub(crate) struct ClientTask {
@@ -90,7 +91,7 @@ impl Task for ClientTask {
fn perform(&self) -> Result {
crate::block_on(crate::convert_async_panics(|| async move {
let client = crate::get_client(&self.client_id);
- let client = client.read().unwrap();
+ let client = client.read().expect("Failed to read client");
let res = match &self.api {
// High level API
Api::Send {
@@ -138,8 +139,7 @@ impl Task for ClientTask {
serde_json::to_string(&MessageWrapper {
message_id: message.id().0,
message,
- })
- .unwrap()
+ })?
}
Api::GetUnspentAddress {
seed,
@@ -154,7 +154,7 @@ impl Task for ClientTask {
getter = getter.with_initial_address_index(*initial_address_index);
}
let (address, index) = getter.get().await?;
- serde_json::to_string(&(address, index)).unwrap()
+ serde_json::to_string(&(address, index))?
}
Api::GetAddresses {
seed,
@@ -175,7 +175,7 @@ impl Task for ClientTask {
}
let addresses = getter.finish().await?;
- serde_json::to_string(&addresses).unwrap()
+ serde_json::to_string(&addresses)?
}
Api::FindMessages {
indexation_keys,
@@ -189,7 +189,7 @@ impl Task for ClientTask {
message,
})
.collect();
- serde_json::to_string(&message_wrappers).unwrap()
+ serde_json::to_string(&message_wrappers)?
}
Api::GetBalance {
seed,
@@ -208,20 +208,20 @@ impl Task for ClientTask {
getter = getter.with_gap_limit(*gap_limit);
}
let balance = getter.finish().await?;
- serde_json::to_string(&balance).unwrap()
+ serde_json::to_string(&balance)?
}
Api::GetAddressBalances(bech32_addresses) => {
let balances = client.get_address_balances(&bech32_addresses[..]).await?;
let balances: Vec = balances.into_iter().map(|b| b.into()).collect();
- serde_json::to_string(&balances).unwrap()
+ serde_json::to_string(&balances)?
}
// Node APIs
- Api::GetInfo => serde_json::to_string(&client.get_info().await?).unwrap(),
- Api::GetNetworkInfo => serde_json::to_string(&client.get_network_info().await?).unwrap(),
- Api::GetPeers => serde_json::to_string(&client.get_peers().await?).unwrap(),
+ Api::GetInfo => serde_json::to_string(&client.get_info().await?)?,
+ Api::GetNetworkInfo => serde_json::to_string(&client.get_network_info().await?)?,
+ Api::GetPeers => serde_json::to_string(&client.get_peers().await?)?,
Api::GetTips => {
let tips = client.get_tips().await?;
- serde_json::to_string(&tips).unwrap()
+ serde_json::to_string(&tips)?
}
Api::PostMessage(message) => {
let parent_msg_ids = match message.parents.as_ref() {
@@ -243,82 +243,79 @@ impl Task for ClientTask {
.with_payload(message.payload.clone().try_into()?)
.finish()?;
let message = client.post_message(&message).await?;
- serde_json::to_string(&message).unwrap()
+ serde_json::to_string(&message)?
}
Api::GetMessagesByIndexation(index) => {
let messages = client.get_message().index(index).await?;
- serde_json::to_string(&messages).unwrap()
+ serde_json::to_string(&messages)?
}
Api::GetMessage(id) => {
let message = client.get_message().data(&id).await?;
serde_json::to_string(&MessageWrapper {
message_id: message.id().0,
message,
- })
- .unwrap()
+ })?
}
Api::GetMessageMetadata(id) => {
let metadata = client.get_message().metadata(&id).await?;
- serde_json::to_string(&metadata).unwrap()
+ serde_json::to_string(&metadata)?
}
Api::GetRawMessage(id) => client.get_message().raw(&id).await?,
Api::GetMessageChildren(id) => {
let messages = client.get_message().children(&id).await?;
- serde_json::to_string(&messages).unwrap()
+ serde_json::to_string(&messages)?
}
Api::GetOutput(id) => {
let output = client.get_output(id).await?;
let output: super::OutputMetadataDto = output.into();
- serde_json::to_string(&output).unwrap()
+ serde_json::to_string(&output)?
}
Api::FindOutputs { outputs, addresses } => {
let outputs = client.find_outputs(outputs, &addresses[..]).await?;
let outputs: Vec = outputs.into_iter().map(|o| o.into()).collect();
- serde_json::to_string(&outputs).unwrap()
+ serde_json::to_string(&outputs)?
}
Api::GetAddressBalance(address) => {
let balance = client.get_address().balance(address).await?;
- serde_json::to_string(&balance).unwrap()
+ serde_json::to_string(&balance)?
}
Api::GetAddressOutputs(address, options) => {
let output_ids = client.get_address().outputs(address, options.clone()).await?;
- serde_json::to_string(&output_ids).unwrap()
+ serde_json::to_string(&output_ids)?
}
Api::GetMilestone(index) => {
let milestone = client.get_milestone(*index).await?;
- serde_json::to_string(&milestone).unwrap()
+ serde_json::to_string(&milestone)?
}
Api::GetMilestoneUtxoChanges(index) => {
let milestone_utxo_changes = client.get_milestone_utxo_changes(*index).await?;
- serde_json::to_string(&milestone_utxo_changes).unwrap()
+ serde_json::to_string(&milestone_utxo_changes)?
}
Api::GetReceipts() => {
let receipts = client.get_receipts().await?;
- serde_json::to_string(&receipts).unwrap()
+ serde_json::to_string(&receipts)?
}
Api::GetReceiptsMigratedAt(index) => {
let receipts = client.get_receipts_migrated_at(*index).await?;
- serde_json::to_string(&receipts).unwrap()
+ serde_json::to_string(&receipts)?
}
Api::GetTreasury() => {
let treasury = client.get_treasury().await?;
- serde_json::to_string(&treasury).unwrap()
+ serde_json::to_string(&treasury)?
}
Api::GetIncludedMessage(transaction_id) => {
let message = client.get_included_message(&*transaction_id).await?;
serde_json::to_string(&MessageWrapper {
message_id: message.id().0,
message,
- })
- .unwrap()
+ })?
}
Api::Retry(message_id) => {
let message = client.retry(message_id).await?;
serde_json::to_string(&MessageWrapper {
message_id: message.0,
message: message.1,
- })
- .unwrap()
+ })?
}
Api::RetryUntilIncluded(message_id, interval, max_attempts) => {
let messages = client
@@ -331,25 +328,27 @@ impl Task for ClientTask {
message_id: msg.0,
message: msg.1,
})
- .unwrap()
})
- .collect()
+ .collect::>()?
}
Api::Reattach(message_id) => {
let message = client.reattach(message_id).await?;
serde_json::to_string(&MessageWrapper {
message: message.1,
message_id: message.0,
- })
- .unwrap()
+ })?
}
Api::Promote(message_id) => {
let message = client.promote(message_id).await?;
serde_json::to_string(&MessageWrapper {
message: message.1,
message_id: message.0,
- })
- .unwrap()
+ })?
+ }
+ Api::HexToBech32(hex, bech32_hrp) => {
+ let opt = bech32_hrp.as_ref().map(|opt| opt.as_str());
+ let bech32 = client.hex_to_bech32(hex, opt).await?;
+ serde_json::to_string(&bech32)?
}
};
Ok(res)
diff --git a/bindings/nodejs/native/src/classes/client/mod.rs b/bindings/nodejs/native/src/classes/client/mod.rs
index 4f4453a0e..8aed1bf69 100644
--- a/bindings/nodejs/native/src/classes/client/mod.rs
+++ b/bindings/nodejs/native/src/classes/client/mod.rs
@@ -3,7 +3,7 @@
use iota::{
message::prelude::{Address, MessageId, TransactionId, UtxoInput},
- AddressOutputsOptions, OutputType, Seed,
+ AddressOutputsOptions, Client, OutputType, Seed,
};
use neon::prelude::*;
use serde::Deserialize;
@@ -595,10 +595,40 @@ declare_types! {
Ok(cx.undefined().upcast())
}
- method isAddressValid(mut cx) -> JsResult {
+ method bech32ToHex(mut cx) {
+ let bech32 = cx.argument::(0)?.value();
+ let hex = Client::bech32_to_hex(bech32.as_str()).unwrap();
+ Ok(cx.string(hex).upcast())
+ }
+
+ method hexToBech32(mut cx) {
+ let hex = cx.argument::(0)?.value();
+ let bech32_hrp: Option = match cx.argument_opt(1) {
+ Some(arg) => {
+ Some(arg.downcast::().or_throw(&mut cx)?.value())
+ },
+ None => Default::default(),
+ };
+
+ let cb = cx.argument::(cx.len()-1)?;
+ {
+ let this = cx.this();
+ let guard = cx.lock();
+ let id = &this.borrow(&guard).0;
+ let client_task = ClientTask {
+ client_id: id.clone(),
+ api: Api::HexToBech32(hex, bech32_hrp),
+ };
+ client_task.schedule(cb);
+ }
+
+ Ok(cx.undefined().upcast())
+ }
+
+ method isAddressValid(mut cx) {
let address = cx.argument::(0)?.value();
- let b = cx.boolean(Api::IsAddressValid(address.as_str()));
- Ok(b)
+ let is_valid = Client::is_address_valid(address.as_str());
+ Ok(cx.boolean(is_valid).upcast())
}
}
}
diff --git a/bindings/nodejs/native/src/lib.rs b/bindings/nodejs/native/src/lib.rs
index bed12685c..903471317 100644
--- a/bindings/nodejs/native/src/lib.rs
+++ b/bindings/nodejs/native/src/lib.rs
@@ -36,6 +36,8 @@ pub enum Error {
Panic(String),
#[error("`{0}`")]
Message(iota::message::Error),
+ #[error("`{0}`")]
+ SerdeJson(serde_json::Error),
}
impl From for Error {
@@ -44,6 +46,12 @@ impl From for Error {
}
}
+impl From for Error {
+ fn from(error: serde_json::Error) -> Self {
+ Self::SerdeJson(error)
+ }
+}
+
pub(crate) fn block_on(cb: C) -> C::Output {
static INSTANCE: OnceCell> = OnceCell::new();
let runtime = INSTANCE.get_or_init(|| Mutex::new(Runtime::new().unwrap()));
diff --git a/bindings/nodejs/package-lock.json b/bindings/nodejs/package-lock.json
index 7118f21ce..53129de83 100644
--- a/bindings/nodejs/package-lock.json
+++ b/bindings/nodejs/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@iota/client",
- "version": "0.0.0",
+ "version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/bindings/nodejs/tests/client.js b/bindings/nodejs/tests/client.js
index 3d07b05f6..cf6e532be 100644
--- a/bindings/nodejs/tests/client.js
+++ b/bindings/nodejs/tests/client.js
@@ -36,6 +36,13 @@ describe('Client', () => {
addresses.forEach(assertAddress)
})
+ it('convert address', async () => {
+ const address = "atoi1qpnrumvaex24dy0duulp4q07lpa00w20ze6jfd0xly422kdcjxzakzsz5kf"
+ let hexAddress = client.bech32ToHex(address)
+ let bech32Address = await client.hexToBech32(hexAddress, "atoi")
+ assert.strictEqual(address, bech32Address)
+ })
+
it('sends an indexation message with the high level API', async () => {
const message = await client
.message()
diff --git a/bindings/python/README.md b/bindings/python/README.md
index f9cb409ae..a0684d8f3 100644
--- a/bindings/python/README.md
+++ b/bindings/python/README.md
@@ -464,6 +464,27 @@ Get the balance in iotas for the given addresses.
**Returns** the list of [AddressBalancePair](#addressbalancepair).
+#### bech32_to_hex(bech32)
+
+Returns a parsed hex String from bech32.
+
+| Param | Type | Default | Description |
+| ------- | ------------------- | ---------------------- | ------------------------- |
+| bech32 | string
| undefined
| The address Bech32 string |
+
+**Returns** A String
+
+#### hex_to_bech32(hex, bech32_hrp (optional))
+
+Returns a parsed bech32 String from hex.
+
+| Param | Type | Default | Description |
+| ----------- | ------------------- | ---------------------- | ------------------------- |
+| bech32 | string
| undefined
| The address Bech32 string |
+| bech32_hrp | string
| undefined
| The Bech32 hrp string |
+
+**Returns** A String
+
#### is_address_valid(address): bool
Checks if a given address is valid.
diff --git a/bindings/python/native/src/client/high_level_api.rs b/bindings/python/native/src/client/high_level_api.rs
index 546af7d09..2a54269d5 100644
--- a/bindings/python/native/src/client/high_level_api.rs
+++ b/bindings/python/native/src/client/high_level_api.rs
@@ -289,6 +289,14 @@ impl Client {
})
.collect())
}
+ fn bech32_to_hex(&self, hex: &str) -> Result {
+ Ok(iota::Client::bech32_to_hex(hex)?)
+ }
+ fn hex_to_bech32(&self, hex: &str, bech32_hrp: Option<&str>) -> Result {
+ Ok(crate::block_on(async {
+ self.client.hex_to_bech32(hex, bech32_hrp).await
+ })?)
+ }
fn is_address_valid(&self, address: &str) -> bool {
iota::Client::is_address_valid(address)
}
diff --git a/fixtures/test_vectors.json b/fixtures/test_vectors.json
index 96263a41e..a919785b8 100644
--- a/fixtures/test_vectors.json
+++ b/fixtures/test_vectors.json
@@ -31,7 +31,7 @@
]
},
"UTXOINPUT": [
- "00000000000000000000000000000000000000000000000000000000000000000000"
+ "7702ea0f2cd6af3206b894c3f2fe4362b23f0f4828857d31e733103b09db25840000"
],
"MESSAGE_ID": [
"f541e53e98ccffdd94036f08af07f9eb060449ee7838c2279f230acc933de6d8"
diff --git a/iota-client/src/client.rs b/iota-client/src/client.rs
index 5932fce16..3bf2a5b95 100644
--- a/iota-client/src/client.rs
+++ b/iota-client/src/client.rs
@@ -9,7 +9,9 @@ use crate::{
node::*,
};
use bee_common::packable::Packable;
-use bee_message::prelude::{Address, Message, MessageBuilder, MessageId, Parents, TransactionId, UtxoInput};
+use bee_message::prelude::{
+ Address, Ed25519Address, Message, MessageBuilder, MessageId, Parents, TransactionId, UtxoInput,
+};
use bee_pow::providers::{MinerBuilder, Provider as PowProvider, ProviderBuilder as PowProviderBuilder};
use bee_rest_api::types::{
dtos::{MessageDto, PeerDto, ReceiptDto},
@@ -1109,6 +1111,25 @@ impl Client {
Ok(address_balance_pairs)
}
+ /// Transforms bech32 to hex
+ pub fn bech32_to_hex(bech32: &str) -> crate::Result {
+ let address = Address::try_from_bech32(bech32)?;
+ if let Address::Ed25519(ed) = address {
+ return Ok(ed.to_string());
+ }
+
+ Err(crate::Error::FailedToParseBech32ToHex)
+ }
+
+ /// Transforms hex to bech32
+ pub async fn hex_to_bech32(&self, hex: &str, bech32_hrp: Option<&str>) -> crate::Result {
+ let address: Ed25519Address = hex.parse::()?;
+ match bech32_hrp {
+ Some(hrp) => Ok(Address::Ed25519(address).to_bech32(hrp)),
+ None => Ok(Address::Ed25519(address).to_bech32(self.get_bech32_hrp().await?.as_str())),
+ }
+ }
+
/// Returns a valid Address parsed from a String.
pub fn parse_bech32_address(address: &str) -> crate::Result {
Ok(Address::try_from_bech32(address)?)
diff --git a/iota-client/src/error.rs b/iota-client/src/error.rs
index 9ba544134..0cf27ee7b 100644
--- a/iota-client/src/error.rs
+++ b/iota-client/src/error.rs
@@ -119,4 +119,7 @@ pub enum Error {
/// Output Error
#[error("Output error: {0}")]
OutputError(&'static str),
+ /// Error when parsing from bech32 to hex
+ #[error("Failed to parse bech32 to hex")]
+ FailedToParseBech32ToHex,
}
diff --git a/specs/iota-rs-ENGINEERING-SPEC-0000.md b/specs/iota-rs-ENGINEERING-SPEC-0000.md
index 17532c35f..306ffde14 100644
--- a/specs/iota-rs-ENGINEERING-SPEC-0000.md
+++ b/specs/iota-rs-ENGINEERING-SPEC-0000.md
@@ -14,6 +14,8 @@
* [`get_addresses`](#get_addresses)
* [`get_balance`](#get_balance)
* [`get_address_balances`](#get_address_balances)
+ * [`bech32_to_hex`](#bech32_to_hex)
+ * [`hex_to_bech32`](#hex_to_bech32)
* [`parse_bech32_address`](#parse_bech32_address)
* [`is_address_valid`](#is_address_valid)
* [`subscriber`](#subscriber)
@@ -274,6 +276,35 @@ Following are the steps for implementing this method:
* Get latest balance for the provided address using [`find_outputs()`](#find_outputs) with addresses as parameter;
* Return the list of Output which contains corresponding pairs of address and balance.
+## `bech32_to_hex()`
+
+Returns a parsed hex String from bech32.
+
+### Parameters
+
+| Parameter | Required | Type | Definition |
+| - | - | - | - |
+| **bech32** | ✔ | [String] | Bech32 encoded address. |
+
+### Return
+
+Parsed [String].
+
+## `hex_to_bech32()`
+
+Returns a parsed bech32 String from hex.
+
+### Parameters
+
+| Parameter | Required | Type | Definition |
+| - | - | - | - |
+| **hex** | ✔ | [String] | Hex encoded address. |
+| **bech32_hrp** | ✔ | [Option] | Optional bech32 hrp. |
+
+### Return
+
+Parsed [String].
+
## `parse_bech32_address()`
Returns a valid Address parsed from a String.