iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

commit c3f4480118ba27c913b7f6d29c328035a65ddad2
parent 914cd0f2145e3c26c52993b0e93a184418ad9bcf
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Sun, 27 Sep 2026 11:50:48 +0200

fix(wallet): include hybrid rewards in balance

Diffstat:
Msrc/adapters/http/ui.rs | 17++++++++++++++---
Msrc/adapters/wallet_endpoint.rs | 30+++++++++++++++++++++++-------
Msrc/domain/wallet.rs | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/lightweight-wallet.test.cjs | 24++++++++++++++++++++++++
Mwallet/app.js | 56+++++++++++++++++++++++++++++++++++++++++---------------
Mwallet/crypto-wasm/src/lib.rs | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mwallet/crypto/iuna_wallet_crypto.d.ts | 3+++
Mwallet/crypto/iuna_wallet_crypto.js | 35+++++++++++++++++++++++++++++++++++
Mwallet/crypto/iuna_wallet_crypto_bg.wasm | 0
Mwallet/crypto/iuna_wallet_crypto_bg.wasm.d.ts | 1+
10 files changed, 263 insertions(+), 35 deletions(-)

diff --git a/src/adapters/http/ui.rs b/src/adapters/http/ui.rs @@ -331,6 +331,15 @@ pub(super) fn populate_wallet_reward_flow(row: &mut WalletTransactionRow, block: .collect(); } +pub(crate) fn mark_wallet_reward_row(row: &mut WalletTransactionRow, block: Option<&Block>) { + row.kind = "reward"; + row.from = "fees".to_string(); + row.direction = "reward"; + if let Some(block) = block { + populate_wallet_reward_flow(row, block); + } +} + fn transaction_kind(transaction: &Transaction) -> &'static str { match transaction { Transaction::Transfer { .. } => "transfer", @@ -1052,7 +1061,7 @@ mod tests { }; use super::{ - block_lost_iuna, populate_wallet_reward_flow, ui_block, ui_blocks_from_indexes, + block_lost_iuna, mark_wallet_reward_row, ui_block, ui_blocks_from_indexes, ui_transaction_v2, wallet_transaction_row, wallet_transaction_v2_rows, }; use crate::adapters::http::types::{WalletTransactionContext, WalletTransactionFilters}; @@ -1244,7 +1253,6 @@ mod tests { }, ) .unwrap(); - row.kind = "reward"; let committee_row = wallet_transaction_row( "committee", &reward_projection, @@ -1288,8 +1296,11 @@ mod tests { hash: "block".to_string(), }; - populate_wallet_reward_flow(&mut row, &block); + mark_wallet_reward_row(&mut row, Some(&block)); + assert_eq!(row.kind, "reward"); + assert_eq!(row.from, "fees"); + assert_eq!(row.direction, "reward"); assert_eq!(row.reward_fee_inputs.len(), 2); assert_eq!(row.reward_total, Some(5)); assert_eq!(row.reward_fee_inputs[0].transaction_kind, "transfer"); diff --git a/src/adapters/wallet_endpoint.rs b/src/adapters/wallet_endpoint.rs @@ -22,9 +22,9 @@ use crate::{ http::{ types::{WalletTransactionContext, WalletTransactionFilters, WalletTransactionRow}, ui::{ - add_pending_outputs, add_pending_v2_outputs, transaction_v2_input_outpoints, - wallet_transaction_row_for_addresses, wallet_transaction_rows, - wallet_transaction_v2_row, wallet_transaction_v2_rows, + add_pending_outputs, add_pending_v2_outputs, mark_wallet_reward_row, + transaction_v2_input_outpoints, wallet_transaction_row_for_addresses, + wallet_transaction_rows, wallet_transaction_v2_row, wallet_transaction_v2_rows, }, }, p2p::GossipNetwork, @@ -45,7 +45,8 @@ use crate::{ // over twice the consensus block budget. const MAX_TRANSACTION_BODY_BYTES: usize = MAX_BLOCK_BYTES * 2 + 4 * 1024; const MAX_CONCURRENT_TRANSACTION_SUBMISSIONS: usize = 32; -const MAX_WALLET_ADDRESSES: usize = 10_001; +// One legacy address plus the maximum external and reward address branches. +const MAX_WALLET_ADDRESSES: usize = 20_001; #[derive(Clone)] struct WalletEndpointState { @@ -605,10 +606,22 @@ async fn wallet_transactions( .map_err(internal_error)? .map_err(internal_error)? }; + let reward_blocks = { + let node = state.node.lock().await; + legacy_rows + .iter() + .filter(|row| row.kind == "reward") + .filter_map(|row| { + let block = node.chain().get(row.block_height as usize)?.clone(); + Some((row.block_height, block)) + }) + .collect::<BTreeMap<_, _>>() + }; let mut confirmed = legacy_rows .into_iter() .filter_map(|row| { - wallet_transaction_row_for_addresses( + let is_reward = row.kind == "reward"; + let mut item = wallet_transaction_row_for_addresses( &addresses, &row.transaction, &confirmed_outputs, @@ -618,8 +631,11 @@ async fn wallet_transactions( timestamp_ms: Some(row.timestamp_ms), block_finalizer: Some(row.block_finalizer), }, - ) - .map(|item| (row.sort_key, item)) + )?; + if is_reward { + mark_wallet_reward_row(&mut item, reward_blocks.get(&row.block_height)); + } + Some((row.sort_key, item)) }) .collect::<Vec<_>>(); confirmed.extend(decoded_v2.into_iter().filter_map(|(row, transaction)| { diff --git a/src/domain/wallet.rs b/src/domain/wallet.rs @@ -485,6 +485,47 @@ mod tests { } #[test] + fn browser_reward_transfer_vector_matches_node_encoding_and_signatures() { + let wallet = Wallet::from_seed("hybrid-wallet-seed"); + let owner = wallet.hybrid_versioned_address_at(HybridAddressBranch::Reward, 0); + let recipient = wallet.hybrid_versioned_address_at(HybridAddressBranch::External, 1); + let change = wallet.hybrid_versioned_address_at(HybridAddressBranch::External, 2); + let domain = TransactionV2Domain::new("iuna-mainnet-candidate", [0x11; 32]).unwrap(); + let mut transaction = TransactionV2::Transfer { + inputs: vec![TransactionV2Input { + outpoint_txid: [0x44; 32], + outpoint_index: 8, + owner, + }], + outputs: vec![ + TransactionV2Output { + address: recipient, + amount: 1_000_000, + }, + TransactionV2Output { + address: change, + amount: 995_921, + }, + ], + fee: 4_079, + authorizations: Vec::new(), + }; + let payload = transaction.signing_bytes(&domain).unwrap(); + let authorization = wallet.sign_v2_authorization(owner, &payload).unwrap(); + let TransactionV2::Transfer { authorizations, .. } = &mut transaction else { + unreachable!(); + }; + authorizations.push(authorization); + + transaction.verify_authorizations(&domain).unwrap(); + assert_eq!(transaction.encoded_size_bytes(&domain).unwrap(), 4_079); + assert_eq!( + hex_encode(transaction.transaction_id(&domain).unwrap()), + "558cee1dfa1425e0b214681445e3d0641a898e15245f80d4524d2b98d762df88" + ); + } + + #[test] fn browser_v2_migration_vector_matches_node_encoding_and_signature() { let wallet = Wallet::from_seed("hybrid-wallet-seed"); let owner = wallet.legacy_versioned_address(); @@ -602,6 +643,14 @@ mod tests { ); assert_ne!(external_one, external_zero); assert_ne!(reward_zero, external_one); + assert_eq!( + Wallet::from_seed("hybrid-wallet-seed").hybrid_address_at( + HybridAddressBranch::Reward, + 0, + AddressNetwork::Mainnet, + ), + "iuna1pvxpc35gavamxw0tvqj3ms82gwtvchh7mdyzqdttx7ktx7pfkgz4qkaq24k" + ); let authorization = first .sign_v2_authorization(external_one, b"rotated child spend") diff --git a/tests/lightweight-wallet.test.cjs b/tests/lightweight-wallet.test.cjs @@ -104,6 +104,11 @@ test('browser crypto derives the same rotating hybrid address vectors as the nod { index: 0, address: 'iuna1py9qlrnw6cm3mpz26hwwkww9spaa96zrw2g34gu0p4y3ea5cqhg0qa82x9s' }, { index: 1, address: 'iuna1pkxdcktlzf5tc2p59xns2nccq7rg5zjhwg2zn5r9u73c5j9jxgk8s0e5l4e' }, ]); + const rewardAddresses = JSON.parse(quantum.derive_reward_addresses('hybrid-wallet-seed', 2, 'iuna-mainnet-v1')); + assert.deepEqual(rewardAddresses, [ + { index: 0, address: 'iuna1pvxpc35gavamxw0tvqj3ms82gwtvchh7mdyzqdttx7ktx7pfkgz4qkaq24k' }, + { index: 1, address: 'iuna1p6cj9c3ths75p7vnsdx9tgkvu4jlzs8cxpnh2lc4klsu5fvd0cpxq8ypw45' }, + ]); const built = JSON.parse(quantum.build_transfer(JSON.stringify({ seed: 'hybrid-wallet-seed', @@ -123,6 +128,25 @@ test('browser crypto derives the same rotating hybrid address vectors as the nod assert.equal(built.envelope.length / 2, 4079); assert.equal(built.transactionId, 'bda748b6ba9fd6550ca47d580f980a1c00d0050a20992750265a62d23b0b590d'); + const rewardBuilt = JSON.parse(quantum.build_transfer(JSON.stringify({ + seed: 'hybrid-wallet-seed', + chainId: 'iuna-mainnet-candidate', + genesisHash: '11'.repeat(32), + recipientAddress: addresses[1].address, + amount: '1000000', + feeRate: '1', + changeIndex: 2, + utxos: [{ + addressBranch: 'reward', + addressIndex: 0, + outpoint: { txid: '44'.repeat(32), index: 8 }, + output: { amount: 2000000 }, + }], + }))); + assert.equal(rewardBuilt.fee, '4079'); + assert.equal(rewardBuilt.inputCount, 1); + assert.equal(rewardBuilt.transactionId, '558cee1dfa1425e0b214681445e3d0641a898e15245f80d4524d2b98d762df88'); + const migration = JSON.parse(quantum.build_migration(JSON.stringify({ seed: 'hybrid-wallet-seed', chainId: 'iuna-mainnet-candidate', diff --git a/wallet/app.js b/wallet/app.js @@ -8,6 +8,7 @@ import initQuantumCrypto, { build_migration as buildQuantumMigration, build_transfer as buildQuantumTransfer, derive_external_addresses as deriveExternalAddresses, + derive_reward_addresses as deriveRewardAddresses, } from "./crypto/iuna_wallet_crypto.js"; const app = document.querySelector("#app"); @@ -17,7 +18,7 @@ const ALL_TRANSACTION_FILTERS = { transfer: true, mine: true, burn: true, reward const state = { store: null, wallet: null, walletMeta: null, status: null, address: "", balance: null, addresses: [], addressIndex: 0, legacyAddress: "", legacyUtxos: [], hybridUtxos: [], - derivedAddresses: [], derivedWalletId: null, + derivedAddresses: [], derivedRewardAddresses: [], derivedWalletId: null, hybridSpendable: 0, utxos: [], transactions: [], recentTransactions: [], view: "home", timer: null, transactionFilters: { ...ALL_TRANSACTION_FILTERS }, transactionPage: { offset: 0, total: 0, hasMore: true, loading: false, error: "" }, @@ -164,37 +165,62 @@ async function fetchWalletData() { if (Number(state.status.api_version || 0) < 2) throw new Error("The public iuna endpoint must be upgraded for rotating quantum-resistant wallets"); await ensureQuantumCrypto(); const gapLimit = Math.max(1, Number(state.status.hybrid_address_gap_limit || 20)); - let count = gapLimit + 1; + let externalCount = gapLimit + 1; + let rewardCount = gapLimit; let derived; + let rewardDerived; const derivationScope = `${state.walletMeta.id}:${state.status.chain_id}`; if (state.derivedWalletId !== derivationScope) { state.derivedAddresses = []; + state.derivedRewardAddresses = []; state.derivedWalletId = derivationScope; } for (;;) { - if (state.derivedAddresses.length < count) { - state.derivedAddresses = JSON.parse(deriveExternalAddresses(state.wallet.seedPhrase, count, state.status.chain_id)); + if (state.derivedAddresses.length < externalCount) { + state.derivedAddresses = JSON.parse(deriveExternalAddresses(state.wallet.seedPhrase, externalCount, state.status.chain_id)); } - derived = state.derivedAddresses.slice(0, count); - snapshot = await fetchSnapshot([state.legacyAddress, ...derived.map((item) => item.address)]); + if (state.derivedRewardAddresses.length < rewardCount) { + state.derivedRewardAddresses = JSON.parse(deriveRewardAddresses(state.wallet.seedPhrase, rewardCount, state.status.chain_id)); + } + derived = state.derivedAddresses.slice(0, externalCount); + rewardDerived = state.derivedRewardAddresses.slice(0, rewardCount); + snapshot = await fetchSnapshot([ + state.legacyAddress, + ...derived.map((item) => item.address), + ...rewardDerived.map((item) => item.address), + ]); const used = new Set(snapshot.addresses.filter((item) => item.used).map((item) => item.address)); - const highestUsed = derived.reduce((highest, item) => used.has(item.address) ? Math.max(highest, item.index) : highest, -1); - if (highestUsed < count - gapLimit || count >= 10_000) break; - count = Math.min(10_000, highestUsed + gapLimit + 1); + const highestExternal = derived.reduce((highest, item) => used.has(item.address) ? Math.max(highest, item.index) : highest, -1); + const highestReward = rewardDerived.reduce((highest, item) => used.has(item.address) ? Math.max(highest, item.index) : highest, -1); + const nextExternalCount = highestExternal < externalCount - gapLimit || externalCount >= 10_000 + ? externalCount + : Math.min(10_000, highestExternal + gapLimit + 1); + const nextRewardCount = highestReward < rewardCount - gapLimit || rewardCount >= 10_000 + ? rewardCount + : Math.min(10_000, highestReward + gapLimit + 1); + if (nextExternalCount === externalCount && nextRewardCount === rewardCount) break; + externalCount = nextExternalCount; + rewardCount = nextRewardCount; } const used = new Set(snapshot.addresses.filter((item) => item.used).map((item) => item.address)); const highestUsed = derived.reduce((highest, item) => used.has(item.address) ? Math.max(highest, item.index) : highest, -1); state.addressIndex = highestUsed + 1; const current = derived.find((item) => item.index === state.addressIndex); if (!current) throw new Error("Wallet address discovery exceeded its recovery limit"); - state.addresses = [state.legacyAddress, ...derived.map((item) => item.address)]; + state.addresses = [ + state.legacyAddress, + ...derived.map((item) => item.address), + ...rewardDerived.map((item) => item.address), + ]; state.address = current.address; - const indexByAddress = new Map(derived.map((item) => [item.address, item.index])); + const descriptorByAddress = new Map([ + ...derived.map((item) => [item.address, { addressIndex: item.index, addressBranch: "external" }]), + ...rewardDerived.map((item) => [item.address, { addressIndex: item.index, addressBranch: "reward" }]), + ]); state.legacyUtxos = (snapshot.utxos || []).filter((utxo) => utxo.address === state.legacyAddress); - state.hybridUtxos = (snapshot.utxos || []).filter((utxo) => indexByAddress.has(utxo.address)).map((utxo) => ({ - ...utxo, - addressIndex: indexByAddress.get(utxo.address), - })); + state.hybridUtxos = (snapshot.utxos || []) + .filter((utxo) => descriptorByAddress.has(utxo.address)) + .map((utxo) => ({ ...utxo, ...descriptorByAddress.get(utxo.address) })); state.hybridSpendable = snapshot.addresses.filter((item) => item.version === 1).reduce((sum, item) => sum + Number(item.spendable || 0), 0); } else { snapshot = await fetchSnapshot([state.legacyAddress]); diff --git a/wallet/crypto-wasm/src/lib.rs b/wallet/crypto-wasm/src/lib.rs @@ -45,10 +45,29 @@ struct TransferRequest { #[serde(rename_all = "camelCase")] struct HybridUtxo { address_index: u32, + #[serde(default)] + address_branch: HybridAddressBranch, outpoint: Outpoint, output: Output, } +#[derive(Clone, Copy, Default, Deserialize)] +#[serde(rename_all = "lowercase")] +enum HybridAddressBranch { + #[default] + External, + Reward, +} + +impl HybridAddressBranch { + fn domain_label(self) -> &'static str { + match self { + Self::External => "external", + Self::Reward => "reward", + } + } +} + #[derive(Deserialize, Clone)] struct Outpoint { txid: String, @@ -92,12 +111,30 @@ pub fn derive_external_addresses( count: u32, network_id: &str, ) -> Result<String, JsValue> { + derive_addresses(seed, count, network_id, HybridAddressBranch::External) +} + +#[wasm_bindgen] +pub fn derive_reward_addresses( + seed: &str, + count: u32, + network_id: &str, +) -> Result<String, JsValue> { + derive_addresses(seed, count, network_id, HybridAddressBranch::Reward) +} + +fn derive_addresses( + seed: &str, + count: u32, + network_id: &str, + branch: HybridAddressBranch, +) -> Result<String, JsValue> { if count == 0 || count > 10_000 { return Err(js_error("address count must be between 1 and 10000")); } let addresses = (0..count) .map(|index| { - let (_, _, address) = hybrid_key(seed, index); + let (_, _, address) = hybrid_key(seed, branch, index); DerivedAddress { index, address: encode_address(address, network_id), @@ -154,7 +191,11 @@ pub fn build_transfer(request_json: &str) -> Result<String, JsValue> { let mut outputs = vec![(recipient, amount)]; let change = total - required; if change > 0 { - let (_, _, change_address) = hybrid_key(&request.seed, request.change_index); + let (_, _, change_address) = hybrid_key( + &request.seed, + HybridAddressBranch::External, + request.change_index, + ); outputs.push((change_address, change)); } let size = transfer_encoded_size(&request.chain_id, selected.len(), outputs.len()); @@ -185,7 +226,11 @@ pub fn build_transfer(request_json: &str) -> Result<String, JsValue> { "v2 transaction ID", )?); signing.extend_from_slice(&input.outpoint.index.to_be_bytes()); - let (_, _, owner) = hybrid_key(&request.seed, input.address_index); + let (_, _, owner) = hybrid_key( + &request.seed, + input.address_branch, + input.address_index, + ); encode_address_bytes(&mut signing, owner); } encode_outputs(&mut signing, &outputs)?; @@ -194,7 +239,11 @@ pub fn build_transfer(request_json: &str) -> Result<String, JsValue> { let mut envelope = signing.clone(); push_u32(&mut envelope, selected.len())?; for input in &selected { - let (public_key, ml_seed, _) = hybrid_key(&request.seed, input.address_index); + let (public_key, ml_seed, _) = hybrid_key( + &request.seed, + input.address_branch, + input.address_index, + ); let signature = hybrid_signature(&request.seed, ml_seed, &signing)?; encode_authorization(&mut envelope, 2, &public_key, &signature)?; } @@ -232,7 +281,11 @@ pub fn build_migration(request_json: &str) -> Result<String, JsValue> { .ok_or_else(|| js_error("migration fee exceeds the available legacy balance"))?; let ed_seed = derive_seed(ED_SEED_DOMAIN, &request.seed); let ed_public = SigningKey::from_bytes(&ed_seed).verifying_key().to_bytes(); - let (_, _, destination) = hybrid_key(&request.seed, request.destination_index); + let (_, _, destination) = hybrid_key( + &request.seed, + HybridAddressBranch::External, + request.destination_index, + ); let mut signing = encode_prefix(&request.chain_id, genesis_hash)?; signing.push(4); @@ -270,13 +323,17 @@ pub fn build_migration(request_json: &str) -> Result<String, JsValue> { built_json(envelope, fee, request.utxos.len()) } -fn hybrid_key(seed: &str, index: u32) -> ([u8; HYBRID_PUBLIC_KEY_BYTES], [u8; 32], Address) { +fn hybrid_key( + seed: &str, + branch: HybridAddressBranch, + index: u32, +) -> ([u8; HYBRID_PUBLIC_KEY_BYTES], [u8; 32], Address) { let ed_seed = derive_seed(ED_SEED_DOMAIN, seed); let parent_ml_seed = derive_seed(ML_SEED_DOMAIN, seed); - let ml_seed = if index == 0 { + let ml_seed = if matches!(branch, HybridAddressBranch::External) && index == 0 { parent_ml_seed } else { - derive_child_seed(parent_ml_seed, index) + derive_child_seed(parent_ml_seed, branch, index) }; let ed_public = SigningKey::from_bytes(&ed_seed).verifying_key().to_bytes(); let ml_public = ml_public_key(ml_seed); @@ -333,10 +390,16 @@ fn derive_seed(domain: &str, seed: &str) -> [u8; 32] { hasher.finalize().into() } -fn derive_child_seed(parent: [u8; 32], index: u32) -> [u8; 32] { +fn derive_child_seed( + parent: [u8; 32], + branch: HybridAddressBranch, + index: u32, +) -> [u8; 32] { let mut hasher = Sha256::new(); hasher.update(ML_CHILD_DOMAIN.as_bytes()); - hasher.update(b":external:"); + hasher.update(b":"); + hasher.update(branch.domain_label().as_bytes()); + hasher.update(b":"); hasher.update(index.to_be_bytes()); hasher.update(b":"); hasher.update(parent); diff --git a/wallet/crypto/iuna_wallet_crypto.d.ts b/wallet/crypto/iuna_wallet_crypto.d.ts @@ -7,11 +7,14 @@ export function build_transfer(request_json: string): string; export function derive_external_addresses(seed: string, count: number, network_id: string): string; +export function derive_reward_addresses(seed: string, count: number, network_id: string): string; + export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; export interface InitOutput { readonly memory: WebAssembly.Memory; readonly derive_external_addresses: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly derive_reward_addresses: (a: number, b: number, c: number, d: number, e: number, f: number) => void; readonly build_transfer: (a: number, b: number, c: number) => void; readonly build_migration: (a: number, b: number, c: number) => void; readonly __wbindgen_export: (a: number, b: number) => number; diff --git a/wallet/crypto/iuna_wallet_crypto.js b/wallet/crypto/iuna_wallet_crypto.js @@ -96,6 +96,41 @@ export function derive_external_addresses(seed, count, network_id) { wasm.__wbindgen_export3(deferred4_0, deferred4_1, 1); } } + +/** + * @param {string} seed + * @param {number} count + * @param {string} network_id + * @returns {string} + */ +export function derive_reward_addresses(seed, count, network_id) { + let deferred4_0; + let deferred4_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(seed, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(network_id, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + wasm.derive_reward_addresses(retptr, ptr0, len0, count, ptr1, len1); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr3 = r0; + var len3 = r1; + if (r3) { + ptr3 = 0; len3 = 0; + throw takeObject(r2); + } + deferred4_0 = ptr3; + deferred4_1 = len3; + return getStringFromWasm0(ptr3, len3); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_export3(deferred4_0, deferred4_1, 1); + } +} function __wbg_get_imports() { const import0 = { __proto__: null, diff --git a/wallet/crypto/iuna_wallet_crypto_bg.wasm b/wallet/crypto/iuna_wallet_crypto_bg.wasm Binary files differ. diff --git a/wallet/crypto/iuna_wallet_crypto_bg.wasm.d.ts b/wallet/crypto/iuna_wallet_crypto_bg.wasm.d.ts @@ -2,6 +2,7 @@ /* eslint-disable */ export const memory: WebAssembly.Memory; export const derive_external_addresses: (a: number, b: number, c: number, d: number, e: number, f: number) => void; +export const derive_reward_addresses: (a: number, b: number, c: number, d: number, e: number, f: number) => void; export const build_transfer: (a: number, b: number, c: number) => void; export const build_migration: (a: number, b: number, c: number) => void; export const __wbindgen_export: (a: number, b: number) => number;