wallet_adapter/wallet_ser_der/standard_features/
sign_tx.rs1use ed25519_dalek::Signature;
2use wallet_adapter_common::{clusters::Cluster, WalletCommonUtils};
3use web_sys::{
4 js_sys::{self, Function},
5 wasm_bindgen::JsValue,
6};
7
8use core::hash::Hash;
9
10use crate::{Commitment, Reflection, SemverVersion, WalletAccount, WalletError, WalletResult};
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct SignTransaction {
15 pub version: SemverVersion,
18 pub legacy: bool,
21 pub version_zero: bool,
23 callback: Function,
26}
27
28impl SignTransaction {
29 fn new(reflection: &Reflection, version: SemverVersion, key: &str) -> WalletResult<Self> {
30 let inner_value = reflection
31 .reflect_inner(key)
32 .or(Err(WalletError::MissingSignTransactionFunction))?;
33 let callback = Reflection::new(inner_value)?
34 .into_function()
35 .map_err(|error| {
36 WalletError::InternalError(format!("Namespace[`solana:{key}`]: {error}"))
37 })?;
38
39 let (legacy, version_zero) = Self::get_tx_version_support(reflection)?;
40
41 Ok(Self {
42 version,
43 callback,
44 legacy,
45 version_zero,
46 })
47 }
48
49 pub(crate) fn new_sign_tx(
51 reflection: &Reflection,
52 version: SemverVersion,
53 ) -> WalletResult<Self> {
54 Self::new(reflection, version, "signTransaction")
55 }
56
57 pub(crate) fn new_sign_and_send_tx(
59 reflection: &Reflection,
60 version: SemverVersion,
61 ) -> WalletResult<Self> {
62 Self::new(reflection, version, "signAndSendTransaction")
63 }
64
65 fn get_tx_version_support(inner_value: &Reflection) -> WalletResult<(bool, bool)> {
66 let tx_version_support_jsvalue = inner_value
67 .reflect_inner("supportedTransactionVersions")
68 .or(Err(WalletError::ExpectedValueNotFound(
69 "supportedTransactionVersions".to_string(),
70 )))?;
71 let tx_version_support = Reflection::new(tx_version_support_jsvalue)?.into_array()?;
72
73 let mut legacy = false;
74 let mut version_zero = false;
75
76 tx_version_support.iter().try_for_each(|value| {
77 if value == JsValue::from_str("legacy") {
78 legacy = true;
79 } else if value == 0 {
80 version_zero = true;
81 } else {
82 return Err(WalletError::UnsupportedTransactionVersion);
83 }
84
85 Ok(())
86 })?;
87
88 if !legacy {
89 return Err(WalletError::LegacyTransactionSupportRequired);
90 }
91
92 Ok((legacy, version_zero))
93 }
94
95 pub(crate) async fn call_sign_tx(
96 &self,
97 wallet_account: &WalletAccount,
98 transaction_bytes: &[u8],
99 cluster: Option<Cluster>,
100 ) -> WalletResult<Vec<Vec<u8>>> {
101 let tx_bytes_value: js_sys::Uint8Array = transaction_bytes.into();
102
103 let mut tx_object = Reflection::new_object();
104 tx_object.set_object(&"account".into(), &wallet_account.js_value)?;
105 tx_object.set_object(&"transaction".into(), &tx_bytes_value)?;
106 if let Some(cluster) = cluster {
107 tx_object.set_object(&"chain".into(), &cluster.chain().into())?;
108 }
109
110 let outcome = self.callback.call1(&JsValue::null(), &tx_object.take())?;
111
112 let outcome = js_sys::Promise::resolve(&outcome);
113
114 let success = wasm_bindgen_futures::JsFuture::from(outcome).await?;
115 Reflection::new(success)?.get_bytes_from_vec("signedTransaction")
116 }
117
118 pub(crate) async fn call_sign_and_send_transaction(
119 &self,
120 wallet_account: &WalletAccount,
121 transaction_bytes: &[u8],
122 cluster: Cluster,
123 options: SendOptions,
124 ) -> WalletResult<Signature> {
125 let tx_bytes_value: js_sys::Uint8Array = transaction_bytes.into();
126
127 let mut tx_object = Reflection::new_object();
128 tx_object.set_object(&"account".into(), &wallet_account.js_value)?;
129 tx_object.set_object(&"transaction".into(), &tx_bytes_value)?;
130 tx_object.set_object(&"chain".into(), &cluster.chain().into())?;
131 tx_object.set_object(&"options".into(), &options.to_object()?)?;
132
133 let outcome = self.callback.call1(&JsValue::null(), &tx_object.take())?;
134
135 let outcome = js_sys::Promise::resolve(&outcome);
136
137 let success = wasm_bindgen_futures::JsFuture::from(outcome).await?;
138
139 Reflection::new(success)?
140 .get_bytes_from_vec("signature")?
141 .first()
142 .map(|value| {
143 let bytes = WalletCommonUtils::to64byte_array(value)?;
144 Ok(WalletCommonUtils::signature(&bytes))
145 })
146 .ok_or(WalletError::SendAndSignTransactionSignatureEmpty)?
147 }
148}
149
150#[allow(clippy::non_canonical_partial_ord_impl)]
151impl PartialOrd for SignTransaction {
152 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
153 Some(self.version.cmp(&other.version))
154 }
155}
156
157impl Ord for SignTransaction {
158 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
159 self.version.cmp(&other.version)
160 }
161}
162
163impl Hash for SignTransaction {
164 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
165 self.version.hash(state);
166 }
167}
168
169#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
175pub struct SendOptions {
176 preflight_commitment: Commitment,
177 skip_preflight: bool,
178 max_retries: u8,
179}
180
181impl SendOptions {
182 pub fn to_object(&self) -> WalletResult<JsValue> {
186 let mut reflection = Reflection::new_object();
187 reflection.set_object_str("preflightCommitment", self.preflight_commitment.as_str())?;
188 reflection.set_object(&"skipPreflight".into(), &JsValue::from(self.skip_preflight))?;
189 reflection.set_object(&"maxRetries".into(), &JsValue::from(self.max_retries))?;
190
191 Ok(reflection.take())
192 }
193}