Skip to content

Commit

Permalink
Added hex_to_bech32 and bech32_to_hex methods (iotaledger#471)
Browse files Browse the repository at this point in the history
* Added hex_to_bech32 and bech32_to_hex methods in client

* Added spec documentation

* Added bindings bech32 to hex and hex to bech32 methods

* fmt fix

* added promisify for hexToBech32

* handle serde_json errors, add convert test

* fixed js binding

* fix test

* update output id

* add changes for covetor

Co-authored-by: Thoralf-M <[email protected]>
  • Loading branch information
melatron and Thoralf-M authored Apr 7, 2021
1 parent a5321ea commit 115184a
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 46 deletions.
5 changes: 5 additions & 0 deletions .changes/bech32-hex-conversion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nodejs-binding": minor
---

Added functions to convert addresses from bech32 to hex and vice versa.
23 changes: 22 additions & 1 deletion bindings/nodejs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | <code>string</code> | The address Bech32 string |

**Returns** A String

#### hexToBech32(hex, bech32_hrp (optional))

Returns a parsed bech32 String from hex.

| Param | Type | Description |
| ----------- | ------------------- | ------------------------- |
| bech32 | <code>string</code> | The address Bech32 string |
| bech32_hrp | <code>string</code> | The Bech32 hrp string |

**Returns** A String

#### isAddressValid(address: string): boolean

Checks if a given address is valid.
Expand Down Expand Up @@ -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 |
Expand Down
4 changes: 3 additions & 1 deletion bindings/nodejs/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export declare class Client {
subscriber(): TopicSubscriber
message(): MessageSender
getUnspentAddress(seed: string): UnspentAddressGetter
getAddresses(seed: string): AddressGetter
getAddresses(seed: string): Promise<AddressGetter>
findMessages(indexationKeys: string[], messageIds: string[]): Promise<MessageWrapper[]>
getBalance(seed: string): BalanceGetter
getAddressBalances(addresses: string[]): Promise<AddressBalance[]>
Expand All @@ -97,6 +97,8 @@ export declare class Client {
findOutputs(outputIds: string[], addresses: string[]): Promise<OutputMetadata[]>
getAddressOutputs(address: string, options?: AddressOutputsOptions): Promise<string[]>
getAddressBalance(address: string): Promise<AddressBalance>
bech32ToHex(address: string): string
hexToBech32(address: string, bech32_hrp?: string): Promise<string>
isAddressValid(address: string): boolean
getMilestone(index: number): Promise<MilestoneMetadata>
getMilestoneUtxoChanges(index: number): Promise<MilestoneUTXOChanges>
Expand Down
1 change: 1 addition & 0 deletions bindings/nodejs/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
73 changes: 36 additions & 37 deletions bindings/nodejs/native/src/classes/client/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub(crate) enum Api {
RetryUntilIncluded(MessageId, Option<u64>, Option<u64>),
Reattach(MessageId),
Promote(MessageId),
HexToBech32(String, Option<String>),
}

pub(crate) struct ClientTask {
Expand All @@ -90,7 +91,7 @@ impl Task for ClientTask {
fn perform(&self) -> Result<Self::Output, Self::Error> {
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 {
Expand Down Expand Up @@ -138,8 +139,7 @@ impl Task for ClientTask {
serde_json::to_string(&MessageWrapper {
message_id: message.id().0,
message,
})
.unwrap()
})?
}
Api::GetUnspentAddress {
seed,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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<super::AddressBalanceDto> = 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() {
Expand All @@ -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<super::OutputMetadataDto> = 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
Expand All @@ -331,25 +328,27 @@ impl Task for ClientTask {
message_id: msg.0,
message: msg.1,
})
.unwrap()
})
.collect()
.collect::<Result<String, serde_json::Error>>()?
}
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)
Expand Down
38 changes: 34 additions & 4 deletions bindings/nodejs/native/src/classes/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -595,10 +595,40 @@ declare_types! {
Ok(cx.undefined().upcast())
}

method isAddressValid(mut cx) -> JsResult<JsBoolean> {
method bech32ToHex(mut cx) {
let bech32 = cx.argument::<JsString>(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::<JsString>(0)?.value();
let bech32_hrp: Option<String> = match cx.argument_opt(1) {
Some(arg) => {
Some(arg.downcast::<JsString>().or_throw(&mut cx)?.value())
},
None => Default::default(),
};

let cb = cx.argument::<JsFunction>(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::<JsString>(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())
}
}
}
8 changes: 8 additions & 0 deletions bindings/nodejs/native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub enum Error {
Panic(String),
#[error("`{0}`")]
Message(iota::message::Error),
#[error("`{0}`")]
SerdeJson(serde_json::Error),
}

impl From<iota::message::Error> for Error {
Expand All @@ -44,6 +46,12 @@ impl From<iota::message::Error> for Error {
}
}

impl From<serde_json::Error> for Error {
fn from(error: serde_json::Error) -> Self {
Self::SerdeJson(error)
}
}

pub(crate) fn block_on<C: futures::Future>(cb: C) -> C::Output {
static INSTANCE: OnceCell<Mutex<Runtime>> = OnceCell::new();
let runtime = INSTANCE.get_or_init(|| Mutex::new(Runtime::new().unwrap()));
Expand Down
2 changes: 1 addition & 1 deletion bindings/nodejs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions bindings/nodejs/tests/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading

0 comments on commit 115184a

Please sign in to comment.