Skip to main content

o2_tools/
trial_trade_account.rs

1use crate::{
2    call_data,
3    order_book::{
4        CreateOrderParams,
5        OrderBookManager,
6    },
7    parallel_nonce::{
8        generate_parallel_session_digest,
9        generate_parallel_session_signing_payload,
10    },
11    trial_trade_account_deploy::{
12        CallContractArg,
13        CallParams,
14        OrderBookCleanup,
15        ParallelCallContractArgs,
16        ParallelMultiCallContractArgs,
17        ParallelRevokeArgs,
18        ParallelSessionArgs,
19        Secp256k1,
20        Session,
21        Signature,
22        Time,
23        TrialCloseAccountArgs as TrialCloseAccountPayload,
24        TrialLinkAccountArgs as TrialLinkAccountPayload,
25        TrialTradeAccountDeploy,
26        TrialTradingAccount,
27        TrialTradingAccountProxy,
28        TrialWithdrawArgs as TrialWithdrawPayload,
29    },
30};
31use fuels::{
32    accounts::signers::private_key::PrivateKeySigner,
33    core::traits::Tokenizable,
34    crypto::Message,
35    prelude::*,
36    types::{
37        Bytes32,
38        Identity,
39        U256,
40    },
41};
42use rand::{
43    SeedableRng,
44    rngs::StdRng,
45};
46use sha2::{
47    Digest,
48    Sha256,
49};
50use std::time::{
51    SystemTime,
52    UNIX_EPOCH,
53};
54
55const TRADE_APPROVAL_DIGEST_TAG: u8 = 0x02;
56const PROXY_OWNER_OPERATION_DIGEST_TAG: u8 = 0x03;
57const LINK_DIGEST_TAG: u8 = 0x04;
58
59pub struct TrialCallContractParams {
60    pub contract_id: ContractId,
61    pub function_selector: Vec<u8>,
62    pub forward: CallParams,
63    pub args: Option<Vec<u8>>,
64    pub nonce: U256,
65    pub variable_outputs: Option<u16>,
66}
67
68#[derive(Debug, Clone)]
69pub enum TrialContractCalls {
70    Call(TrialCallContractArgs),
71    Calls(TrialCallContractsArgs),
72}
73
74impl TrialContractCalls {
75    pub fn contract_ids(&self) -> Vec<ContractId> {
76        match self {
77            TrialContractCalls::Call(call) => {
78                vec![call.call.call_contract_args.contract_id]
79            }
80            TrialContractCalls::Calls(calls) => calls
81                .calls
82                .call_contract_args
83                .iter()
84                .map(|c| c.contract_id)
85                .collect(),
86        }
87    }
88}
89
90#[derive(Debug, Clone)]
91pub struct TrialCallContractArgs {
92    pub user_signature: Signature,
93    pub cosigner_signature: Signature,
94    pub call: ParallelCallContractArgs,
95    pub variable_outputs: Option<u16>,
96    pub contracts: Option<Vec<ContractId>>,
97}
98
99#[derive(Debug, Clone)]
100pub struct TrialCallContractsArgs {
101    pub user_signature: Signature,
102    pub cosigner_signature: Signature,
103    pub calls: ParallelMultiCallContractArgs,
104    pub variable_outputs: Option<u16>,
105    pub contracts: Option<Vec<ContractId>>,
106}
107
108#[derive(Debug, Clone)]
109pub struct TrialCosignerCallRequest {
110    pub signature: Signature,
111    pub args: ParallelCallContractArgs,
112    pub variable_outputs: Option<u16>,
113    pub contracts: Option<Vec<ContractId>>,
114}
115
116#[derive(Debug, Clone)]
117pub struct TrialCosignerCallContractsRequest {
118    pub signature: Signature,
119    pub args: ParallelMultiCallContractArgs,
120    pub variable_outputs: Option<u16>,
121    pub contracts: Option<Vec<ContractId>>,
122}
123
124#[derive(Debug, Clone)]
125pub struct TrialCloseAccountRequest {
126    pub signature: Signature,
127    pub args: TrialCloseAccountPayload,
128    pub contracts: Option<Vec<ContractId>>,
129}
130
131#[derive(Debug, Clone)]
132pub struct TrialWithdrawRequest {
133    pub signature: Signature,
134    pub args: TrialWithdrawPayload,
135    pub contracts: Option<Vec<ContractId>>,
136}
137
138#[derive(Debug, Clone)]
139pub struct TrialSetSessionRequest {
140    pub args: ParallelSessionArgs,
141    pub contracts: Option<Vec<ContractId>>,
142}
143
144#[derive(Debug, Clone)]
145pub struct TrialRevokeSessionRequest {
146    pub args: ParallelRevokeArgs,
147    pub contracts: Option<Vec<ContractId>>,
148}
149
150#[derive(Debug, Clone)]
151pub struct TrialLinkAccountRequest {
152    pub session_signature: Signature,
153    pub args: TrialLinkAccountPayload,
154    pub contracts: Option<Vec<ContractId>>,
155}
156
157impl TrialCallContractParams {
158    pub fn new(
159        contract_id: ContractId,
160        function_selector: Vec<u8>,
161        forward: CallParams,
162        args: Option<Vec<u8>>,
163        nonce: U256,
164        variable_outputs: Option<u16>,
165    ) -> Self {
166        Self {
167            contract_id,
168            function_selector,
169            forward,
170            args,
171            nonce,
172            variable_outputs,
173        }
174    }
175
176    pub fn call_args(&self) -> CallContractArg {
177        CallContractArg {
178            contract_id: self.contract_id,
179            function_selector: Bytes(self.function_selector.clone()),
180            call_params: self.forward.clone(),
181            call_data: self.args.clone().map(Bytes),
182        }
183    }
184
185    pub fn user_message(&self) -> Message {
186        generate_parallel_session_signing_payload(self.nonce, self.call_args())
187    }
188
189    pub fn trade_approval_message(
190        &self,
191        chain_id: u64,
192        account_contract_id: ContractId,
193    ) -> Message {
194        generate_trial_trade_approval_signing_payload(
195            chain_id,
196            account_contract_id,
197            self.nonce,
198            self.call_args(),
199        )
200    }
201}
202
203fn sha256_digest(bytes: &[u8]) -> [u8; 32] {
204    Sha256::digest(bytes).into()
205}
206
207fn message_from_digest(digest: [u8; 32]) -> Message {
208    Message::from_bytes(digest)
209}
210
211pub fn generate_trial_trade_approval_digest<T: Tokenizable>(
212    chain_id: u64,
213    account_contract_id: ContractId,
214    nonce: U256,
215    call_contract_arg: T,
216) -> [u8; 32] {
217    let session_digest = generate_parallel_session_digest(nonce, call_contract_arg);
218    generate_trial_trade_approval_digest_from_session_digest(
219        chain_id,
220        account_contract_id,
221        session_digest,
222    )
223}
224
225pub fn generate_trial_trade_approval_digest_from_session_digest(
226    chain_id: u64,
227    account_contract_id: ContractId,
228    session_digest: [u8; 32],
229) -> [u8; 32] {
230    let mut bytes = Vec::new();
231    bytes.push(TRADE_APPROVAL_DIGEST_TAG);
232    bytes.extend_from_slice(&chain_id.to_be_bytes());
233    bytes.extend_from_slice(account_contract_id.as_slice());
234    bytes.extend_from_slice(&session_digest);
235    sha256_digest(&bytes)
236}
237
238pub fn generate_trial_trade_approval_signing_payload<T: Tokenizable>(
239    chain_id: u64,
240    account_contract_id: ContractId,
241    nonce: U256,
242    call_contract_arg: T,
243) -> Message {
244    message_from_digest(generate_trial_trade_approval_digest(
245        chain_id,
246        account_contract_id,
247        nonce,
248        call_contract_arg,
249    ))
250}
251
252pub fn generate_trial_proxy_owner_operation_digest<T: Tokenizable>(
253    chain_id: u64,
254    account_contract_id: ContractId,
255    args: T,
256) -> [u8; 32] {
257    let mut bytes = Vec::new();
258    bytes.push(PROXY_OWNER_OPERATION_DIGEST_TAG);
259    bytes.extend_from_slice(&chain_id.to_be_bytes());
260    bytes.extend_from_slice(account_contract_id.as_slice());
261    bytes.extend_from_slice(&call_data!(args));
262    sha256_digest(&bytes)
263}
264
265pub fn generate_trial_proxy_owner_operation_signing_payload<T: Tokenizable>(
266    chain_id: u64,
267    account_contract_id: ContractId,
268    args: T,
269) -> Message {
270    message_from_digest(generate_trial_proxy_owner_operation_digest(
271        chain_id,
272        account_contract_id,
273        args,
274    ))
275}
276
277pub fn generate_trial_link_account_digest<T: Tokenizable>(
278    chain_id: u64,
279    account_contract_id: ContractId,
280    args: T,
281) -> [u8; 32] {
282    let mut bytes = Vec::new();
283    bytes.push(LINK_DIGEST_TAG);
284    bytes.extend_from_slice(&chain_id.to_be_bytes());
285    bytes.extend_from_slice(account_contract_id.as_slice());
286    bytes.extend_from_slice(&call_data!(args));
287    sha256_digest(&bytes)
288}
289
290pub fn generate_trial_link_account_signing_payload<T: Tokenizable>(
291    chain_id: u64,
292    account_contract_id: ContractId,
293    args: T,
294) -> Message {
295    message_from_digest(generate_trial_link_account_digest(
296        chain_id,
297        account_contract_id,
298        args,
299    ))
300}
301
302#[derive(Clone)]
303pub struct TrialTradeAccountManager<Wallet: Account + Clone> {
304    pub owner: Wallet,
305    pub oracle_id: Option<ContractId>,
306    pub proxy: TrialTradingAccountProxy<Wallet>,
307    pub contract: TrialTradingAccount<Wallet>,
308    pub session_signer: Option<PrivateKeySigner>,
309    pub cosigner_signer: Option<PrivateKeySigner>,
310}
311
312impl TrialTradeAccountManager<Wallet> {
313    pub fn contract_id(&self) -> ContractId {
314        self.contract.id()
315    }
316
317    pub fn identity(&self) -> Identity {
318        Identity::ContractId(self.contract.contract_id())
319    }
320
321    pub fn session_signer(&self) -> anyhow::Result<PrivateKeySigner> {
322        self.session_signer
323            .clone()
324            .ok_or_else(|| anyhow::anyhow!("Session signer not initialized"))
325    }
326
327    pub fn cosigner_signer(&self) -> anyhow::Result<PrivateKeySigner> {
328        self.cosigner_signer
329            .clone()
330            .ok_or_else(|| anyhow::anyhow!("Cosigner signer not initialized"))
331    }
332
333    pub async fn new(owner: &Wallet, contract_id: ContractId) -> anyhow::Result<Self> {
334        let proxy = TrialTradingAccountProxy::new(contract_id, owner.clone());
335        let contract = TrialTradingAccount::new(contract_id, owner.clone());
336        Ok(Self {
337            owner: owner.clone(),
338            oracle_id: None,
339            contract,
340            proxy,
341            session_signer: None,
342            cosigner_signer: None,
343        })
344    }
345
346    pub fn create(
347        owner: &Wallet,
348        deployment: &TrialTradeAccountDeploy<Wallet>,
349    ) -> anyhow::Result<Self> {
350        if deployment.proxy_id.is_none() || deployment.proxy.is_none() {
351            return Err(anyhow::anyhow!(
352                "Trial trade account deploy proxy ID or instance is not set"
353            ));
354        }
355        let contract =
356            TrialTradingAccount::new(deployment.proxy_id.unwrap(), owner.clone());
357        Ok(Self {
358            owner: owner.clone(),
359            contract,
360            oracle_id: Some(deployment.oracle_id),
361            session_signer: None,
362            cosigner_signer: None,
363            proxy: deployment.proxy.clone().unwrap(),
364        })
365    }
366
367    pub fn create_with_signers(
368        owner: &Wallet,
369        deployment: &TrialTradeAccountDeploy<Wallet>,
370        contract_ids: &[ContractId],
371        cosigner_signer: PrivateKeySigner,
372    ) -> anyhow::Result<Self> {
373        let mut trial_account = Self::create(owner, deployment)?;
374        let mut rng = StdRng::seed_from_u64(2322u64);
375        let session_signer = PrivateKeySigner::random(&mut rng);
376        let session_address = Identity::Address(session_signer.address());
377        trial_account.session_signer = Some(session_signer);
378        trial_account.cosigner_signer = Some(cosigner_signer);
379
380        let _session = trial_account.new_session(session_address, contract_ids, None);
381
382        Ok(trial_account)
383    }
384
385    pub fn new_session(
386        &self,
387        session_address: Identity,
388        contract_ids: &[ContractId],
389        expiry: Option<u64>,
390    ) -> Session {
391        let default_expiry = SystemTime::now()
392            .duration_since(UNIX_EPOCH)
393            .expect("Time went backwards")
394            .as_millis() as u64
395            + (30 * 24 * 60 * 60 * 1000);
396        Session {
397            session_id: session_address,
398            expiry: Time {
399                unix: expiry.unwrap_or(default_expiry),
400            },
401            contract_ids: contract_ids.to_vec(),
402        }
403    }
404
405    async fn chain_id(&self) -> anyhow::Result<u64> {
406        Ok(*self
407            .owner
408            .provider()
409            .consensus_parameters()
410            .await?
411            .chain_id())
412    }
413
414    fn tx_contract_ids(
415        &self,
416        required: impl IntoIterator<Item = ContractId>,
417        extra: Option<Vec<ContractId>>,
418    ) -> Vec<ContractId> {
419        let mut contract_ids = extra.unwrap_or_default();
420
421        for contract_id in required {
422            if !contract_ids.contains(&contract_id) {
423                contract_ids.push(contract_id);
424            }
425        }
426
427        if let Some(oracle_id) = self.oracle_id
428            && !contract_ids.contains(&oracle_id)
429        {
430            contract_ids.push(oracle_id);
431        }
432
433        contract_ids
434    }
435
436    async fn create_proxy_owner_operation_signature<T: Tokenizable>(
437        &self,
438        args: T,
439    ) -> anyhow::Result<Signature> {
440        let chain_id = self.chain_id().await?;
441        let message = generate_trial_proxy_owner_operation_signing_payload(
442            chain_id,
443            self.contract_id(),
444            args,
445        );
446        let signature = self.cosigner_signer()?.sign(message).await?;
447        Ok(Signature::Secp256k1(Secp256k1 {
448            bits: signature.as_slice().try_into()?,
449        }))
450    }
451
452    pub async fn create_call_args(
453        &self,
454        params: TrialCallContractParams,
455    ) -> anyhow::Result<TrialCallContractArgs> {
456        let chain_id = self.chain_id().await?;
457        let call_arg = params.call_args();
458        let call = ParallelCallContractArgs {
459            nonce: params.nonce,
460            call_contract_args: call_arg,
461        };
462        let user_message = generate_parallel_session_signing_payload(
463            call.nonce,
464            call.call_contract_args.clone(),
465        );
466        let trade_approval_message = generate_trial_trade_approval_signing_payload(
467            chain_id,
468            self.contract_id(),
469            call.nonce,
470            call.call_contract_args.clone(),
471        );
472        let user_signature = self.session_signer()?.sign(user_message).await?;
473        let cosigner_signature =
474            self.cosigner_signer()?.sign(trade_approval_message).await?;
475
476        Ok(TrialCallContractArgs {
477            user_signature: Signature::Secp256k1(Secp256k1 {
478                bits: user_signature.as_slice().try_into()?,
479            }),
480            cosigner_signature: Signature::Secp256k1(Secp256k1 {
481                bits: cosigner_signature.as_slice().try_into()?,
482            }),
483            variable_outputs: params.variable_outputs,
484            call,
485            contracts: None,
486        })
487    }
488
489    pub async fn create_calls_args(
490        &self,
491        params: Vec<TrialCallContractParams>,
492    ) -> anyhow::Result<TrialCallContractsArgs> {
493        let call_contract_args = params
494            .iter()
495            .map(|param| param.call_args())
496            .collect::<Vec<_>>();
497        let nonce = params
498            .first()
499            .map(|param| param.nonce)
500            .unwrap_or_else(U256::zero);
501        let calls = ParallelMultiCallContractArgs {
502            nonce,
503            call_contract_args,
504        };
505        let chain_id = self.chain_id().await?;
506        let user_message = generate_parallel_session_signing_payload(
507            calls.nonce,
508            calls.call_contract_args.clone(),
509        );
510        let trade_approval_message = generate_trial_trade_approval_signing_payload(
511            chain_id,
512            self.contract_id(),
513            calls.nonce,
514            calls.call_contract_args.clone(),
515        );
516        let user_signature = self.session_signer()?.sign(user_message).await?;
517        let cosigner_signature =
518            self.cosigner_signer()?.sign(trade_approval_message).await?;
519
520        Ok(TrialCallContractsArgs {
521            user_signature: Signature::Secp256k1(Secp256k1 {
522                bits: user_signature.as_slice().try_into()?,
523            }),
524            cosigner_signature: Signature::Secp256k1(Secp256k1 {
525                bits: cosigner_signature.as_slice().try_into()?,
526            }),
527            calls,
528            variable_outputs: None,
529            contracts: None,
530        })
531    }
532
533    pub fn call_contract(
534        &self,
535        call_args: &TrialCallContractArgs,
536    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
537        let contracts = self.tx_contract_ids(
538            [call_args.call.call_contract_args.contract_id],
539            call_args.contracts.clone(),
540        );
541        self.contract
542            .methods()
543            .call_contract(
544                call_args.user_signature.clone(),
545                call_args.cosigner_signature.clone(),
546                call_args.call.clone(),
547            )
548            .with_contract_ids(&contracts)
549            .with_variable_output_policy(VariableOutputPolicy::Exactly(
550                call_args.variable_outputs.unwrap_or_default() as usize,
551            ))
552    }
553
554    pub fn call_contracts(
555        &self,
556        call_args: &TrialCallContractsArgs,
557    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
558        let contracts = self.tx_contract_ids(
559            call_args
560                .calls
561                .call_contract_args
562                .iter()
563                .map(|call| call.contract_id),
564            call_args.contracts.clone(),
565        );
566        self.contract
567            .methods()
568            .call_contracts(
569                call_args.user_signature.clone(),
570                call_args.cosigner_signature.clone(),
571                call_args.calls.clone(),
572            )
573            .with_contract_ids(&contracts)
574            .with_variable_output_policy(VariableOutputPolicy::Exactly(
575                call_args.variable_outputs.unwrap_or_default() as usize,
576            ))
577    }
578
579    pub async fn create_link_account_args(
580        &self,
581        nonce: U256,
582        parent_account: Identity,
583    ) -> anyhow::Result<TrialLinkAccountRequest> {
584        let args = TrialLinkAccountPayload {
585            nonce,
586            parent_account,
587        };
588        let message = generate_trial_link_account_signing_payload(
589            self.chain_id().await?,
590            self.contract_id(),
591            args.clone(),
592        );
593        let session_signature = self.session_signer()?.sign(message).await?;
594        Ok(TrialLinkAccountRequest {
595            session_signature: Signature::Secp256k1(Secp256k1 {
596                bits: session_signature.as_slice().try_into()?,
597            }),
598            args,
599            contracts: None,
600        })
601    }
602
603    pub fn link_account(
604        &self,
605        link_args: &TrialLinkAccountRequest,
606    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
607        let contracts = self.tx_contract_ids([], link_args.contracts.clone());
608        self.contract
609            .methods()
610            .link_account(link_args.session_signature.clone(), link_args.args.clone())
611            .with_contract_ids(&contracts)
612    }
613
614    pub async fn create_cosigner_call_args(
615        &self,
616        params: TrialCallContractParams,
617    ) -> anyhow::Result<TrialCosignerCallRequest> {
618        let call = params.call_args();
619        let args = ParallelCallContractArgs {
620            nonce: params.nonce,
621            call_contract_args: call,
622        };
623        let signature = self
624            .create_proxy_owner_operation_signature(args.clone())
625            .await?;
626        Ok(TrialCosignerCallRequest {
627            signature,
628            args,
629            variable_outputs: params.variable_outputs,
630            contracts: None,
631        })
632    }
633
634    pub async fn create_cosigner_calls_args(
635        &self,
636        params: Vec<TrialCallContractParams>,
637    ) -> anyhow::Result<TrialCosignerCallContractsRequest> {
638        let calls = params
639            .iter()
640            .map(|param| param.call_args())
641            .collect::<Vec<_>>();
642        let nonce = params
643            .first()
644            .map(|param| param.nonce)
645            .unwrap_or_else(U256::zero);
646        let args = ParallelMultiCallContractArgs {
647            nonce,
648            call_contract_args: calls,
649        };
650        let signature = self
651            .create_proxy_owner_operation_signature(args.clone())
652            .await?;
653        Ok(TrialCosignerCallContractsRequest {
654            signature,
655            args,
656            variable_outputs: None,
657            contracts: None,
658        })
659    }
660
661    pub fn cosigner_call(
662        &self,
663        call_args: &TrialCosignerCallRequest,
664    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
665        let contracts = self.tx_contract_ids(
666            [call_args.args.call_contract_args.contract_id],
667            call_args.contracts.clone(),
668        );
669        self.contract
670            .methods()
671            .cosigner_call(Some(call_args.signature.clone()), call_args.args.clone())
672            .with_contract_ids(&contracts)
673            .with_variable_output_policy(VariableOutputPolicy::Exactly(
674                call_args.variable_outputs.unwrap_or_default() as usize,
675            ))
676    }
677
678    pub fn cosigner_call_contracts(
679        &self,
680        call_args: &TrialCosignerCallContractsRequest,
681    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
682        let contracts = self.tx_contract_ids(
683            call_args
684                .args
685                .call_contract_args
686                .iter()
687                .map(|call| call.contract_id),
688            call_args.contracts.clone(),
689        );
690        self.contract
691            .methods()
692            .cosigner_call_contracts(
693                Some(call_args.signature.clone()),
694                call_args.args.clone(),
695            )
696            .with_contract_ids(&contracts)
697            .with_variable_output_policy(VariableOutputPolicy::Exactly(
698                call_args.variable_outputs.unwrap_or_default() as usize,
699            ))
700    }
701
702    pub async fn create_close_account_args(
703        &self,
704        order_book_cleanups: Vec<OrderBookCleanup>,
705    ) -> anyhow::Result<TrialCloseAccountRequest> {
706        let args = TrialCloseAccountPayload {
707            order_book_cleanups,
708        };
709        let signature = self
710            .create_proxy_owner_operation_signature(args.clone())
711            .await?;
712        Ok(TrialCloseAccountRequest {
713            signature,
714            args,
715            contracts: None,
716        })
717    }
718
719    pub fn close_account(
720        &self,
721        close_account_args: &TrialCloseAccountRequest,
722    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
723        let contracts = self.tx_contract_ids(
724            close_account_args
725                .args
726                .order_book_cleanups
727                .iter()
728                .map(|cleanup| cleanup.order_book_id),
729            close_account_args.contracts.clone(),
730        );
731        self.contract
732            .methods()
733            .close_account(
734                Some(close_account_args.signature.clone()),
735                close_account_args.args.clone(),
736            )
737            .with_contract_ids(&contracts)
738    }
739
740    pub async fn create_withdraw_args(
741        &self,
742        nonce: U256,
743        asset_id: AssetId,
744        amount: u64,
745        to: Identity,
746    ) -> anyhow::Result<TrialWithdrawRequest> {
747        let args = TrialWithdrawPayload {
748            nonce,
749            asset_id,
750            amount,
751            to,
752        };
753        let signature = self
754            .create_proxy_owner_operation_signature(args.clone())
755            .await?;
756        Ok(TrialWithdrawRequest {
757            signature,
758            args,
759            contracts: None,
760        })
761    }
762
763    pub fn withdraw(
764        &self,
765        withdraw_args: &TrialWithdrawRequest,
766    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
767        // The contract's `transfer(to, asset_id, amount)` needs the recipient
768        // wired into the transaction: an Address is paid through a variable
769        // output, a ContractId through an input contract.
770        let (recipient_contracts, variable_outputs) = match withdraw_args.args.to {
771            Identity::ContractId(contract_id) => (vec![contract_id], 0usize),
772            Identity::Address(_) => (vec![], 1usize),
773        };
774        let contracts =
775            self.tx_contract_ids(recipient_contracts, withdraw_args.contracts.clone());
776        self.contract
777            .methods()
778            .withdraw(
779                Some(withdraw_args.signature.clone()),
780                withdraw_args.args.clone(),
781            )
782            .with_contract_ids(&contracts)
783            .with_variable_output_policy(VariableOutputPolicy::Exactly(variable_outputs))
784    }
785
786    pub fn create_set_session_args(
787        &self,
788        nonce: U256,
789        session: Session,
790    ) -> TrialSetSessionRequest {
791        let args = ParallelSessionArgs {
792            nonce,
793            session_id: session.session_id,
794            expiry: session.expiry,
795            contract_ids: session.contract_ids,
796        };
797        TrialSetSessionRequest {
798            args,
799            contracts: None,
800        }
801    }
802
803    pub fn set_session(
804        &self,
805        session_args: &TrialSetSessionRequest,
806    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
807        let contracts = self.tx_contract_ids([], session_args.contracts.clone());
808        self.contract
809            .methods()
810            .set_session(session_args.args.clone())
811            .with_contract_ids(&contracts)
812    }
813
814    pub fn create_revoke_session_args(&self, nonce: U256) -> TrialRevokeSessionRequest {
815        let args = ParallelRevokeArgs { nonce };
816        TrialRevokeSessionRequest {
817            args,
818            contracts: None,
819        }
820    }
821
822    pub fn revoke_session(
823        &self,
824        revoke_args: &TrialRevokeSessionRequest,
825    ) -> CallHandler<Wallet, fuels::programs::calls::ContractCall, ()> {
826        let contracts = self.tx_contract_ids([], revoke_args.contracts.clone());
827        self.contract
828            .methods()
829            .revoke_session(revoke_args.args.clone())
830            .with_contract_ids(&contracts)
831    }
832
833    pub fn cancel_orders_args(
834        &self,
835        order_book: &OrderBookManager<Wallet>,
836        order_id: Bytes32,
837        gas: Option<u64>,
838        nonce: U256,
839    ) -> TrialCallContractParams {
840        TrialCallContractParams::new(
841            order_book.contract.contract_id(),
842            crate::fn_selector!(cancel_order(OrderId)),
843            CallParams {
844                coins: 0,
845                asset_id: AssetId::default(),
846                gas: gas.unwrap_or(u64::MAX),
847            },
848            Some(call_data!(*order_id)),
849            nonce,
850            None,
851        )
852    }
853
854    pub async fn cancel_order(
855        &mut self,
856        order_book: &OrderBookManager<Wallet>,
857        order_id: Bytes32,
858        gas: Option<u64>,
859        nonce: U256,
860    ) -> anyhow::Result<TrialCallContractArgs> {
861        self.create_call_args(self.cancel_orders_args(order_book, order_id, gas, nonce))
862            .await
863    }
864
865    pub fn create_order_args(
866        &self,
867        order_book: &OrderBookManager<Wallet>,
868        params: &CreateOrderParams,
869        gas: Option<u64>,
870        nonce: U256,
871    ) -> TrialCallContractParams {
872        let forward = order_book.create_call_params(params, gas);
873        TrialCallContractParams::new(
874            order_book.contract.contract_id(),
875            crate::fn_selector!(create_order(OrderArgs)),
876            CallParams {
877                coins: forward.coins,
878                asset_id: forward.asset_id,
879                gas: forward.gas,
880            },
881            Some(call_data!(params.to_order_args())),
882            nonce,
883            None,
884        )
885    }
886
887    pub async fn create_order(
888        &mut self,
889        order_book: &OrderBookManager<Wallet>,
890        params: &CreateOrderParams,
891        gas: Option<u64>,
892        nonce: U256,
893    ) -> anyhow::Result<TrialCallContractArgs> {
894        self.create_call_args(self.create_order_args(order_book, params, gas, nonce))
895            .await
896    }
897}