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
42 changes: 42 additions & 0 deletions packages/wasm-dot/js/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AddressNamespace } from "./wasm/wasm_dot.js";
import { AddressFormat } from "./types.js";

/**
* Result of decoding an SS58 address
*/
export interface DecodedAddress {
publicKey: Uint8Array;
prefix: number;
}

/**
* Encode a public key to SS58 address format.
*
* @param publicKey - 32-byte Ed25519 public key
* @param format - Address format (Polkadot, Kusama, or Substrate)
* @returns SS58-encoded address string
*/
export function encodeSs58(publicKey: Uint8Array, format: AddressFormat): string {
return AddressNamespace.encodeSs58(publicKey, format);
}

/**
* Decode an SS58 address to its public key and network prefix.
*
* @param address - SS58-encoded address string
* @returns The decoded public key and network prefix
*/
export function decodeSs58(address: string): DecodedAddress {
return AddressNamespace.decodeSs58(address) as DecodedAddress;
}

/**
* Validate an SS58 address.
*
* @param address - SS58-encoded address string
* @param format - Optional expected address format to check against
* @returns true if the address is valid (and matches format if provided)
*/
export function validateAddress(address: string, format?: AddressFormat): boolean {
return AddressNamespace.validateAddress(address, format);
}
3 changes: 3 additions & 0 deletions packages/wasm-dot/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
WasmTransaction,
ParserNamespace,
BuilderNamespace,
AddressNamespace,
MaterialJs,
ValidityJs,
ParseContextJs,
Expand All @@ -21,6 +22,7 @@ export {
WasmTransaction,
ParserNamespace,
BuilderNamespace,
AddressNamespace,
MaterialJs,
ValidityJs,
ParseContextJs,
Expand All @@ -31,3 +33,4 @@ export * from "./types.js";
export * from "./transaction.js";
export * from "./parser.js";
export * from "./builder.js";
export * from "./address.js";
52 changes: 52 additions & 0 deletions packages/wasm-dot/src/wasm/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! WASM bindings for address operations
//!
//! AddressNamespace provides static methods for SS58 address encoding,
//! decoding, and validation.

use crate::address;
use wasm_bindgen::prelude::*;

/// Namespace for address operations
#[wasm_bindgen]
pub struct AddressNamespace;

#[wasm_bindgen]
impl AddressNamespace {
/// Encode a public key to SS58 address format.
///
/// @param publicKey - 32-byte Ed25519 public key
/// @param prefix - Network prefix (0 = Polkadot, 2 = Kusama, 42 = Substrate)
/// @returns SS58-encoded address string
#[wasm_bindgen(js_name = encodeSs58)]
pub fn encode_ss58(public_key: &[u8], prefix: u16) -> Result<String, JsValue> {
address::encode_ss58(public_key, prefix).map_err(|e| JsValue::from_str(&e.to_string()))
}

/// Decode an SS58 address to its public key and network prefix.
///
/// Returns a JS object with `publicKey` (Uint8Array) and `prefix` (number).
///
/// @param address - SS58-encoded address string
/// @returns { publicKey: Uint8Array, prefix: number }
#[wasm_bindgen(js_name = decodeSs58)]
pub fn decode_ss58(addr: &str) -> Result<JsValue, JsValue> {
let (pubkey, prefix) =
address::decode_ss58(addr).map_err(|e| JsValue::from_str(&e.to_string()))?;

let obj = js_sys::Object::new();
let pubkey_array = js_sys::Uint8Array::from(pubkey.as_slice());
js_sys::Reflect::set(&obj, &"publicKey".into(), &pubkey_array)?;
js_sys::Reflect::set(&obj, &"prefix".into(), &JsValue::from(prefix))?;
Ok(obj.into())
}

/// Validate an SS58 address.
///
/// @param address - SS58-encoded address string
/// @param prefix - Optional expected network prefix to check against
/// @returns true if the address is valid (and matches prefix if provided)
#[wasm_bindgen(js_name = validateAddress)]
pub fn validate_address(addr: &str, prefix: Option<u16>) -> bool {
address::validate_address(addr, prefix)
}
}
2 changes: 2 additions & 0 deletions packages/wasm-dot/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
//! This module contains thin wrappers with #[wasm_bindgen] that delegate
//! to the core Rust implementations.

pub mod address;
pub mod builder;
pub mod parser;
pub mod transaction;
pub mod try_into_js_value;

// Re-export WASM types
pub use address::AddressNamespace;
pub use builder::BuilderNamespace;
pub use parser::ParserNamespace;
pub use transaction::{MaterialJs, ParseContextJs, ValidityJs, WasmTransaction};
81 changes: 81 additions & 0 deletions packages/wasm-dot/test/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as assert from "assert";
import { encodeSs58, decodeSs58, validateAddress, AddressFormat } from "../js/index.js";

describe("address", () => {
// Known test vector: public key → SS58 addresses
const PUBLIC_KEY = new Uint8Array(
Buffer.from("61b18c6dc02ddcabdeac56cb4f21a971cc41cc97640f6f85b073480008c53a0d", "hex"),
);
const SUBSTRATE_ADDRESS = "5EGoFA95omzemRssELLDjVenNZ68aXyUeqtKQScXSEBvVJkr";

describe("encodeSs58", () => {
it("should encode public key to Substrate address (prefix 42)", () => {
const address = encodeSs58(PUBLIC_KEY, AddressFormat.Substrate);
assert.strictEqual(address, SUBSTRATE_ADDRESS);
});

it("should encode public key to Polkadot address (prefix 0)", () => {
const address = encodeSs58(PUBLIC_KEY, AddressFormat.Polkadot);
assert.ok(address.startsWith("1"), "Polkadot addresses start with '1'");
});

it("should encode public key to Kusama address (prefix 2)", () => {
const address = encodeSs58(PUBLIC_KEY, AddressFormat.Kusama);
assert.ok(address.length > 0);
});

it("should throw for invalid public key length", () => {
const shortKey = new Uint8Array(16);
assert.throws(() => encodeSs58(shortKey, AddressFormat.Substrate));
});
});

describe("decodeSs58", () => {
it("should decode Substrate address to public key and prefix", () => {
const decoded = decodeSs58(SUBSTRATE_ADDRESS);
assert.strictEqual(decoded.prefix, 42);
assert.deepStrictEqual(new Uint8Array(decoded.publicKey), PUBLIC_KEY);
});

it("should roundtrip encode → decode for all formats", () => {
for (const format of [
AddressFormat.Polkadot,
AddressFormat.Kusama,
AddressFormat.Substrate,
]) {
const address = encodeSs58(PUBLIC_KEY, format);
const decoded = decodeSs58(address);
assert.strictEqual(decoded.prefix, format);
assert.deepStrictEqual(new Uint8Array(decoded.publicKey), PUBLIC_KEY);
}
});

it("should throw for invalid address", () => {
assert.throws(() => decodeSs58("invalid"));
});
});

describe("validateAddress", () => {
it("should return true for valid address without format check", () => {
assert.strictEqual(validateAddress(SUBSTRATE_ADDRESS), true);
});

it("should return true for valid address with correct format", () => {
assert.strictEqual(validateAddress(SUBSTRATE_ADDRESS, AddressFormat.Substrate), true);
});

it("should return false for valid address with wrong format", () => {
assert.strictEqual(validateAddress(SUBSTRATE_ADDRESS, AddressFormat.Polkadot), false);
});

it("should return false for invalid address", () => {
assert.strictEqual(validateAddress("invalid"), false);
});

it("should validate Polkadot mainnet addresses", () => {
const polkadotAddress = encodeSs58(PUBLIC_KEY, AddressFormat.Polkadot);
assert.strictEqual(validateAddress(polkadotAddress, AddressFormat.Polkadot), true);
assert.strictEqual(validateAddress(polkadotAddress, AddressFormat.Substrate), false);
});
});
});