miden_usdcx/note/
xreserve_mint.rs1use miden_protocol::account::AccountId;
22use miden_protocol::asset::FungibleAsset;
23use miden_protocol::crypto::SequentialCommit;
24use miden_protocol::crypto::dsa::ecdsa_k256_keccak::PublicKey;
25use miden_protocol::crypto::rand::FeltRng;
26use miden_protocol::errors::NoteError;
27use miden_protocol::note::{
28 Note,
29 NoteAttachment,
30 NoteAttachmentScheme,
31 NoteScript,
32 NoteScriptRoot,
33 NoteTag,
34};
35use miden_protocol::{Felt, WORD_SIZE, Word};
36use miden_standards::note::{
37 MintNote,
38 MintNoteStorage,
39 NetworkAccountTarget,
40 NoteExecutionHint,
41 P2idNoteStorage,
42 StandardNoteAttachment,
43};
44
45use crate::xreserve::encoding::{
46 BYTES_PER_PACKED_FELT,
47 CircleDomain,
48 DepositIntent,
49 DepositIntentHeader,
50 DepositNonce,
51 MintIntent,
52 Signature,
53};
54
55pub const XUSDC_MINT_TRANSPORT_ATTACHMENT_SCHEME: u16 =
58 StandardNoteAttachment::UsdcxMint.attachment_scheme().as_u16();
59
60pub const XUSDC_MINT_ATTESTATION_NUM_WORDS: usize = 9;
64
65pub const XUSDC_MINT_TRANSPORT_PAYLOAD_WORD_OFF: usize = XUSDC_MINT_ATTESTATION_NUM_WORDS;
68
69pub const XUSDC_MINT_TRANSPORT_HOOK_DATA_MAX_LEN: usize = (NoteAttachment::MAX_NUM_WORDS as usize
77 * WORD_SIZE
78 - XUSDC_MINT_ATTESTATION_NUM_WORDS * WORD_SIZE
79 - MintIntent::NUM_FELTS)
80 * BYTES_PER_PACKED_FELT;
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct DepositAttestation {
85 signature: Signature,
86 pubkey: PublicKey,
87}
88
89impl DepositAttestation {
90 pub fn new(signature: Signature, pubkey: PublicKey) -> Self {
92 Self { signature, pubkey }
93 }
94
95 pub fn signature(&self) -> Signature {
97 self.signature
98 }
99
100 pub fn pubkey(&self) -> &PublicKey {
102 &self.pubkey
103 }
104}
105
106pub struct XUsdcMintNoteStorage {
108 storage: MintNoteStorage,
109}
110
111impl XUsdcMintNoteStorage {
112 pub fn new(intent_header: &DepositIntentHeader, target: AccountId) -> Self {
114 let recipient_id = intent_header.remote_recipient();
115 let asset = FungibleAsset::new(target, intent_header.amount().as_u64())
116 .expect("asset amount should be valid");
117 let serial_num = intent_header.nonce().to_word();
118 let recipient = P2idNoteStorage::new(recipient_id).into_recipient(serial_num);
119 let tag = NoteTag::with_account_target(recipient_id);
120 let storage = MintNoteStorage::new_public(recipient, asset, tag)
121 .expect("p2id note storage should not exceed max number of storage items");
122
123 Self { storage }
124 }
125
126 pub fn as_mint_storage(&self) -> &MintNoteStorage {
128 &self.storage
129 }
130
131 pub fn into_mint_storage(self) -> MintNoteStorage {
133 self.storage
134 }
135}
136
137pub struct XUsdcDeposit {
144 intent: MintIntent,
145 attestation: DepositAttestation,
146}
147
148impl XUsdcDeposit {
149 pub fn new(intent: MintIntent, attestation: DepositAttestation) -> Self {
151 Self { intent, attestation }
152 }
153
154 pub fn intent(&self) -> &MintIntent {
156 &self.intent
157 }
158
159 pub fn attestation(&self) -> &DepositAttestation {
161 &self.attestation
162 }
163}
164
165impl From<&XUsdcDeposit> for NoteAttachment {
166 fn from(deposit: &XUsdcDeposit) -> Self {
173 let mut elements: Vec<Felt> = Vec::new();
174
175 elements.extend(deposit.attestation.pubkey().to_elements());
176 elements.extend(deposit.attestation.signature().to_elements());
177 elements.extend([Felt::ZERO; 3]);
178 debug_assert_eq!(elements.len(), XUSDC_MINT_TRANSPORT_PAYLOAD_WORD_OFF * 4);
179
180 elements.extend(deposit.intent.to_elements());
181 while !elements.len().is_multiple_of(Word::NUM_ELEMENTS) {
182 elements.push(Felt::ZERO);
183 }
184
185 let words: Vec<Word> = elements
186 .as_chunks::<{ Word::NUM_ELEMENTS }>()
187 .0
188 .iter()
189 .map(|chunk| Word::new(*chunk))
190 .collect();
191 NoteAttachment::with_words(
192 NoteAttachmentScheme::new(XUSDC_MINT_TRANSPORT_ATTACHMENT_SCHEME)
193 .expect("the transport scheme is neither reserved nor past the protocol maximum"),
194 words,
195 )
196 .expect("the hookData bound keeps the transport within the per-attachment word cap")
199 }
200}
201
202pub struct XUsdcMintNote {
205 sender: AccountId,
206 storage: XUsdcMintNoteStorage,
207 serial_number: Word,
208 deposit: XUsdcDeposit,
209 network_account_target: NetworkAccountTarget,
210}
211
212impl XUsdcMintNote {
213 pub fn script() -> NoteScript {
215 MintNote::script()
216 }
217
218 pub fn script_root() -> NoteScriptRoot {
220 MintNote::script_root()
221 }
222
223 pub fn deposit(&self) -> &XUsdcDeposit {
225 &self.deposit
226 }
227
228 pub fn nonce(&self) -> DepositNonce {
230 self.deposit.intent().nonce()
231 }
232}
233
234#[bon::bon]
235impl XUsdcMintNote {
236 #[builder]
247 pub fn new(
248 sender: AccountId,
249 target: AccountId,
250 remote_domain: CircleDomain,
251 deposit_intent: DepositIntent,
252 attestation: DepositAttestation,
253 serial_number: Word,
254 ) -> Result<Self, NoteError> {
255 let mint_intent = MintIntent::from_deposit_intent(&deposit_intent, target, remote_domain)
256 .map_err(|source| {
257 NoteError::other_with_source(
258 "deposit intent cannot be carried by the mint transport",
259 source,
260 )
261 })?;
262
263 let network_account_target = NetworkAccountTarget::new(target, NoteExecutionHint::Always)
264 .map_err(|err| {
265 NoteError::other_with_source("faucet id is not a public network account", err)
266 })?;
267
268 let storage = XUsdcMintNoteStorage::new(deposit_intent.header(), target);
269
270 Ok(Self {
271 sender,
272 storage,
273 serial_number,
274 deposit: XUsdcDeposit::new(mint_intent, attestation),
275 network_account_target,
276 })
277 }
278}
279
280impl<S: x_usdc_mint_note_builder::State> XUsdcMintNoteBuilder<S>
284where
285 S::SerialNumber: x_usdc_mint_note_builder::IsUnset,
286{
287 pub fn generate_serial_number(
289 self,
290 rng: &mut impl FeltRng,
291 ) -> XUsdcMintNoteBuilder<x_usdc_mint_note_builder::SetSerialNumber<S>> {
292 self.serial_number(rng.draw_word())
293 }
294}
295
296impl From<XUsdcMintNote> for Note {
300 fn from(note: XUsdcMintNote) -> Self {
302 MintNote::builder()
303 .sender(note.sender)
304 .mint_storage(note.storage.into_mint_storage())
305 .serial_number(note.serial_number)
306 .attachment(¬e.deposit)
307 .attachment(note.network_account_target)
308 .build()
309 .expect("two attachments are within the protocol's per-note limit")
310 .into()
311 }
312}