1use celestia_proto::cosmos::crypto::ed25519::PubKey as Ed25519PubKey;
4use celestia_proto::cosmos::crypto::secp256k1::PubKey as Secp256k1PubKey;
5use prost::Message;
6use tendermint::public_key::PublicKey;
7use tendermint_proto::Protobuf;
8use tendermint_proto::google::protobuf::Any;
9
10use crate::Error;
11use crate::state::AccAddress;
12use crate::validation_error;
13
14pub use celestia_proto::cosmos::auth::v1beta1::BaseAccount as RawBaseAccount;
15pub use celestia_proto::cosmos::auth::v1beta1::ModuleAccount as RawModuleAccount;
16pub use celestia_proto::cosmos::auth::v1beta1::Params as AuthParams;
17
18const COSMOS_ED25519_PUBKEY: &str = "/cosmos.crypto.ed25519.PubKey";
19const COSMOS_SECP256K1_PUBKEY: &str = "/cosmos.crypto.secp256k1.PubKey";
20
21#[derive(Debug, PartialEq)]
24#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
25pub enum Account {
26 Base(BaseAccount),
28 Module(ModuleAccount),
30}
31
32impl std::ops::Deref for Account {
33 type Target = BaseAccount;
34
35 fn deref(&self) -> &Self::Target {
36 match self {
37 Account::Base(base) => base,
38 Account::Module(module) => &module.base_account,
39 }
40 }
41}
42
43impl std::ops::DerefMut for Account {
44 fn deref_mut(&mut self) -> &mut Self::Target {
45 match self {
46 Account::Base(base) => base,
47 Account::Module(module) => &mut module.base_account,
48 }
49 }
50}
51
52impl From<Account> for BaseAccount {
53 fn from(value: Account) -> Self {
54 match value {
55 Account::Base(acc) => acc,
56 Account::Module(acc) => acc.base_account,
57 }
58 }
59}
60
61#[derive(Debug, Clone, PartialEq)]
68pub struct BaseAccount {
69 pub address: AccAddress,
71 pub pub_key: Option<PublicKey>,
73 pub account_number: u64,
75 pub sequence: u64,
78}
79
80#[derive(Debug, Clone, PartialEq)]
82#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
83pub struct ModuleAccount {
84 pub base_account: BaseAccount,
86 pub name: String,
88 pub permissions: Vec<String>,
90}
91
92impl From<BaseAccount> for RawBaseAccount {
93 fn from(account: BaseAccount) -> Self {
94 RawBaseAccount {
95 address: account.address.to_string(),
96 pub_key: account.pub_key.map(any_from_public_key),
97 account_number: account.account_number,
98 sequence: account.sequence,
99 }
100 }
101}
102
103impl TryFrom<RawBaseAccount> for BaseAccount {
104 type Error = Error;
105
106 fn try_from(account: RawBaseAccount) -> Result<Self, Self::Error> {
107 let pub_key = account.pub_key.map(public_key_from_any).transpose()?;
108 Ok(BaseAccount {
109 address: account.address.parse()?,
110 pub_key,
111 account_number: account.account_number,
112 sequence: account.sequence,
113 })
114 }
115}
116
117impl From<ModuleAccount> for RawModuleAccount {
118 fn from(account: ModuleAccount) -> Self {
119 let base_account = Some(account.base_account.into());
120 RawModuleAccount {
121 base_account,
122 name: account.name,
123 permissions: account.permissions,
124 }
125 }
126}
127
128impl TryFrom<RawModuleAccount> for ModuleAccount {
129 type Error = Error;
130
131 fn try_from(account: RawModuleAccount) -> Result<Self, Self::Error> {
132 let base_account = account
133 .base_account
134 .ok_or_else(|| validation_error!("base account missing"))?
135 .try_into()?;
136 Ok(ModuleAccount {
137 base_account,
138 name: account.name,
139 permissions: account.permissions,
140 })
141 }
142}
143
144fn public_key_from_any(any: Any) -> Result<PublicKey, Error> {
145 match any.type_url.as_ref() {
146 COSMOS_ED25519_PUBKEY => {
147 PublicKey::from_raw_ed25519(&Ed25519PubKey::decode(&*any.value)?.key)
148 }
149 COSMOS_SECP256K1_PUBKEY => {
150 PublicKey::from_raw_secp256k1(&Secp256k1PubKey::decode(&*any.value)?.key)
151 }
152 other => return Err(Error::InvalidPublicKeyType(other.to_string())),
153 }
154 .ok_or(Error::InvalidPublicKey)
155}
156
157fn any_from_public_key(key: PublicKey) -> Any {
158 match key {
159 key @ PublicKey::Ed25519(_) => Any {
160 type_url: COSMOS_ED25519_PUBKEY.to_string(),
161 value: Ed25519PubKey {
162 key: key.to_bytes(),
163 }
164 .encode_to_vec(),
165 },
166 key @ PublicKey::Secp256k1(_) => Any {
167 type_url: COSMOS_SECP256K1_PUBKEY.to_string(),
168 value: Secp256k1PubKey {
169 key: key.to_bytes(),
170 }
171 .encode_to_vec(),
172 },
173 _ => unimplemented!("unexpected key type"),
174 }
175}
176
177impl Protobuf<RawBaseAccount> for BaseAccount {}
178
179impl Protobuf<RawModuleAccount> for ModuleAccount {}
180
181#[cfg(feature = "uniffi")]
182mod uniffi_types {
183 use std::str::FromStr;
184
185 use super::{BaseAccount as RustBaseAccount, PublicKey as TendermintPublicKey};
186
187 use tendermint::public_key::{Ed25519, Secp256k1};
188 use uniffi::{Enum, Record};
189
190 use crate::{error::UniffiConversionError, state::Address};
191
192 #[derive(Enum)]
193 pub enum PublicKey {
194 Ed25519 { bytes: Vec<u8> },
195 Secp256k1 { sec1_bytes: Vec<u8> },
196 }
197
198 impl TryFrom<PublicKey> for TendermintPublicKey {
199 type Error = UniffiConversionError;
200
201 fn try_from(value: PublicKey) -> Result<Self, Self::Error> {
202 Ok(match value {
203 PublicKey::Ed25519 { bytes } => TendermintPublicKey::Ed25519(
204 Ed25519::try_from(bytes.as_ref())
205 .map_err(|_| UniffiConversionError::InvalidPublicKey)?,
206 ),
207 PublicKey::Secp256k1 { sec1_bytes } => TendermintPublicKey::Secp256k1(
208 Secp256k1::from_sec1_bytes(&sec1_bytes)
209 .map_err(|_| UniffiConversionError::InvalidPublicKey)?,
210 ),
211 })
212 }
213 }
214
215 impl From<TendermintPublicKey> for PublicKey {
216 fn from(value: TendermintPublicKey) -> Self {
217 match value {
218 TendermintPublicKey::Ed25519(k) => PublicKey::Ed25519 {
219 bytes: k.as_bytes().to_vec(),
220 },
221 TendermintPublicKey::Secp256k1(k) => PublicKey::Secp256k1 {
222 sec1_bytes: k.to_sec1_bytes().to_vec(),
223 },
224 _ => unimplemented!("unexpected key type"),
225 }
226 }
227 }
228
229 uniffi::custom_type!(TendermintPublicKey, PublicKey, {
230 remote,
231 try_lift: |value| Ok(value.try_into()?),
232 lower: |value| value.into()
233 });
234
235 #[derive(Record)]
236 pub struct BaseAccount {
237 pub address: String,
239 pub pub_key: Option<PublicKey>,
241 pub account_number: u64,
243 pub sequence: u64,
246 }
247
248 impl TryFrom<BaseAccount> for RustBaseAccount {
249 type Error = UniffiConversionError;
250
251 fn try_from(value: BaseAccount) -> Result<Self, Self::Error> {
252 let address = Address::from_str(&value.address)
253 .map_err(|e| UniffiConversionError::InvalidAddress { msg: e.to_string() })?;
254 let Address::AccAddress(account_address) = address else {
255 return Err(UniffiConversionError::InvalidAddress {
256 msg: "Invalid address type, expected account address".to_string(),
257 });
258 };
259
260 Ok(RustBaseAccount {
261 address: account_address,
262 pub_key: value.pub_key.map(TryInto::try_into).transpose()?,
263 account_number: value.account_number,
264 sequence: value.sequence,
265 })
266 }
267 }
268
269 impl From<RustBaseAccount> for BaseAccount {
270 fn from(value: RustBaseAccount) -> Self {
271 BaseAccount {
272 address: value.address.to_string(),
273 pub_key: value.pub_key.map(Into::into),
274 account_number: value.account_number,
275 sequence: value.sequence,
276 }
277 }
278 }
279
280 uniffi::custom_type!(RustBaseAccount, BaseAccount);
281}
282
283#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
284pub use wbg::*;
285
286#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
287mod wbg {
288 use js_sys::{BigInt, Uint8Array};
289 use lumina_utils::make_object;
290 use tendermint::PublicKey;
291 use wasm_bindgen::{JsCast, prelude::*};
292
293 use super::{Account, AuthParams};
294
295 #[wasm_bindgen(typescript_custom_section)]
296 const _: &str = r#"
297 /**
298 * Public key
299 */
300 export interface PublicKey {
301 type: "ed25519" | "secp256k1",
302 value: Uint8Array
303 }
304
305 /**
306 * Common data of all account types
307 */
308 export interface BaseAccount {
309 address: string,
310 pubkey?: PublicKey,
311 accountNumber: bigint,
312 sequence: bigint
313 }
314
315 /**
316 * Auth module parameters
317 */
318 export interface AuthParams {
319 maxMemoCharacters: bigint,
320 txSigLimit: bigint,
321 txSizeCostPerByte: bigint,
322 sigVerifyCostEd25519: bigint,
323 sigVerifyCostSecp256k1: bigint
324 }
325 "#;
326
327 #[wasm_bindgen]
328 extern "C" {
329 #[derive(Clone, Debug)]
331 #[wasm_bindgen(typescript_type = "PublicKey")]
332 pub type JsPublicKey;
333
334 #[wasm_bindgen(typescript_type = "AuthParams")]
336 pub type JsAuthParams;
337
338 #[wasm_bindgen(typescript_type = "BaseAccount")]
340 pub type JsBaseAccount;
341 }
342
343 impl From<AuthParams> for JsAuthParams {
344 fn from(value: AuthParams) -> JsAuthParams {
345 let obj = make_object!(
346 "maxMemoCharacters" => BigInt::from(value.max_memo_characters),
347 "txSigLimit" => BigInt::from(value.tx_sig_limit),
348 "txSizeCostPerByte" => BigInt::from(value.tx_size_cost_per_byte),
349 "sigVerifyCostEd25519" => BigInt::from(value.sig_verify_cost_ed25519),
350 "sigVerifyCostSecp256k1" => BigInt::from(value.sig_verify_cost_secp256k1)
351 );
352
353 obj.unchecked_into()
354 }
355 }
356
357 impl From<PublicKey> for JsPublicKey {
358 fn from(value: PublicKey) -> JsPublicKey {
359 let algo = match value {
360 PublicKey::Ed25519(..) => "ed25519",
361 PublicKey::Secp256k1(..) => "secp256k1",
362 _ => unreachable!("unsupported pubkey algo found"),
363 };
364 let obj = make_object!(
365 "type" => algo.into(),
366 "value" => Uint8Array::from(value.to_bytes().as_ref())
367 );
368
369 obj.unchecked_into()
370 }
371 }
372
373 impl From<Account> for JsBaseAccount {
374 fn from(value: Account) -> JsBaseAccount {
375 let obj = make_object!(
376 "address" => value.address.to_string().into(),
377 "pubkey" => value.pub_key.map(JsPublicKey::from).into(),
378 "accountNumber" => BigInt::from(value.account_number),
379 "sequence" => BigInt::from(value.sequence)
380 );
381
382 obj.unchecked_into()
383 }
384 }
385}