eth_twc_sdk/send_transaction/
self_transfer.rs

1//! `msg.sender` による `transfer`。
2//!
3//! チェーン検証とコントラクト呼び出しに **同一の [`Provider`]** を使います([`crate::utils::assert_provider_supported_chain`])。
4//! ブロードキャスト前に [`call`](alloy::contract::SolCallBuilder::call) 相当の **`eth_call` シミュレーション**を行い、失敗時は送信しません。
5
6use alloy::primitives::{Address, TxHash};
7use alloy::providers::Provider;
8
9use crate::config::{self, assert_transfer_contract_configured};
10use crate::contract::{CommittedTransferDetail as SolCommitted, TransferDetail as SolDetail, TransferWithCommitment};
11use crate::error::SdkError;
12use crate::types::args::{
13    SelfBatchTransferWithCommitArgs, SelfSingleTransferArgs, SelfUniCommitTransfersArgs,
14};
15use crate::utils::assert_provider_supported_chain;
16
17/// `transfer(address,address,uint256,bytes32)` — 単一トークン送金。
18///
19/// # 引数
20///
21/// * `provider` — チェーン検証・`TransferWithCommitment` の `.call()`(シミュレーション)と `.send()` に使用。
22/// * `from` — トランザクション送信者(`msg.sender`)。送金元と一致させる必要があります。
23/// * `args` — トークン・送金先・数量・コミットメント。
24///
25/// # 戻り値
26///
27/// ブロードキャストされたトランザクションのハッシュ([`TxHash`])。
28///
29/// # エラー
30///
31/// [`crate::SdkError`] — 未設定(ゼロ)アドレス、TWC 未デプロイ(`eth_getCode` が空)、RPC/送信失敗など。
32pub async fn transfer<P: Provider>(
33    provider: &P,
34    from: Address,
35    args: SelfSingleTransferArgs,
36) -> Result<TxHash, SdkError> {
37    assert_transfer_contract_configured()?;
38    assert_provider_supported_chain(provider).await?;
39    let c = TransferWithCommitment::new(config::transfer_with_commitment_address(), provider);
40    c.transfer_0(args.token, args.to, args.value, args.commitment)
41        .from(from)
42        .call()
43        .await?;
44    let pending = c
45        .transfer_0(args.token, args.to, args.value, args.commitment)
46        .from(from)
47        .send()
48        .await?;
49    Ok(*pending.tx_hash())
50}
51
52/// `transfer(TransferDetail[],bytes32)` — ユニファイド明細送金。
53///
54/// # 引数・戻り値・エラー
55///
56/// [`transfer`] と同様。`args` は [`SelfUniCommitTransfersArgs`]。
57/// `args.details` が空のときは [`crate::SdkError::EmptyTransferDetails`](コントラクトと [`crate::sign::uni_commit_transfers`] と同じ事前チェック)。
58pub async fn unified_transfer<P: Provider>(
59    provider: &P,
60    from: Address,
61    args: SelfUniCommitTransfersArgs,
62) -> Result<TxHash, SdkError> {
63    if args.details.is_empty() {
64        return Err(SdkError::EmptyTransferDetails);
65    }
66    assert_transfer_contract_configured()?;
67    assert_provider_supported_chain(provider).await?;
68    let details: Vec<SolDetail> = args
69        .details
70        .iter()
71        .map(|d| SolDetail {
72            to: d.to,
73            token: d.token,
74            value: d.value,
75        })
76        .collect();
77    let c = TransferWithCommitment::new(config::transfer_with_commitment_address(), provider);
78    c.transfer_1(details.clone(), args.commitment)
79        .from(from)
80        .call()
81        .await?;
82    let pending = c
83        .transfer_1(details, args.commitment)
84        .from(from)
85        .send()
86        .await?;
87    Ok(*pending.tx_hash())
88}
89
90/// `transfer(CommittedTransferDetail[])` — バッチ送金。
91///
92/// # 引数・戻り値・エラー
93///
94/// [`transfer`] と同様。`args` は [`SelfBatchTransferWithCommitArgs`]。
95/// `args.details` が空のときは [`crate::SdkError::EmptyTransferDetails`](コントラクトと [`crate::sign::batch_transfer_with_commit`] と同じ事前チェック)。
96pub async fn batch_transfer<P: Provider>(
97    provider: &P,
98    from: Address,
99    args: SelfBatchTransferWithCommitArgs,
100) -> Result<TxHash, SdkError> {
101    if args.details.is_empty() {
102        return Err(SdkError::EmptyTransferDetails);
103    }
104    assert_transfer_contract_configured()?;
105    assert_provider_supported_chain(provider).await?;
106    let details: Vec<SolCommitted> = args
107        .details
108        .iter()
109        .map(|d| SolCommitted {
110            to: d.to,
111            token: d.token,
112            value: d.value,
113            commitment: d.commitment,
114        })
115        .collect();
116    let c = TransferWithCommitment::new(config::transfer_with_commitment_address(), provider);
117    c.transfer_2(details.clone())
118        .from(from)
119        .call()
120        .await?;
121    let pending = c.transfer_2(details).from(from).send().await?;
122    Ok(*pending.tx_hash())
123}
124
125#[cfg(all(test, not(feature = "test-config")))]
126mod tests {
127    use alloy::primitives::{address, B256, U256};
128
129    use alloy::providers::ProviderBuilder;
130
131    use crate::types::{
132        CommittedTransferDetail, SelfBatchTransferWithCommitArgs, SelfSingleTransferArgs,
133        SelfUniCommitTransfersArgs, TransferDetail,
134    };
135
136    use super::{batch_transfer, transfer, unified_transfer};
137
138    #[tokio::test]
139    async fn transfer_fails_when_rpc_unreachable() {
140        let provider = ProviderBuilder::new().connect_http("http://127.0.0.1:65531".parse().unwrap());
141        let from = address!("0x1111111111111111111111111111111111111111");
142        let args = SelfSingleTransferArgs {
143            token: from,
144            to: address!("0x3333333333333333333333333333333333333333"),
145            value: U256::from(50u64),
146            commitment: B256::repeat_byte(0xaa),
147        };
148        let r = transfer(&provider, from, args).await;
149        assert!(matches!(r, Err(crate::SdkError::Alloy(_))));
150    }
151
152    #[tokio::test]
153    async fn unified_transfer_errors_on_empty_details_before_contract_configured() {
154        let provider = ProviderBuilder::new().connect_http("http://127.0.0.1:65529".parse().unwrap());
155        let from = address!("0x1111111111111111111111111111111111111111");
156        let args = SelfUniCommitTransfersArgs {
157            details: vec![],
158            commitment: B256::ZERO,
159        };
160        let r = unified_transfer(&provider, from, args).await;
161        assert!(matches!(
162            r,
163            Err(crate::SdkError::EmptyTransferDetails)
164        ));
165    }
166
167    #[tokio::test]
168    async fn batch_transfer_errors_on_empty_details_before_contract_configured() {
169        let provider = ProviderBuilder::new().connect_http("http://127.0.0.1:65528".parse().unwrap());
170        let from = address!("0x1111111111111111111111111111111111111111");
171        let args = SelfBatchTransferWithCommitArgs { details: vec![] };
172        let r = batch_transfer(&provider, from, args).await;
173        assert!(matches!(
174            r,
175            Err(crate::SdkError::EmptyTransferDetails)
176        ));
177    }
178
179    #[tokio::test]
180    async fn unified_transfer_fails_with_alloy_when_rpc_unreachable_after_nonempty_details() {
181        let provider = ProviderBuilder::new().connect_http("http://127.0.0.1:65527".parse().unwrap());
182        let from = address!("0x1111111111111111111111111111111111111111");
183        let detail = TransferDetail {
184            to: address!("0x3333333333333333333333333333333333333333"),
185            token: from,
186            value: U256::from(1u64),
187        };
188        let args = SelfUniCommitTransfersArgs {
189            details: vec![detail],
190            commitment: B256::repeat_byte(0xbb),
191        };
192        let r = unified_transfer(&provider, from, args).await;
193        assert!(matches!(r, Err(crate::SdkError::Alloy(_))));
194    }
195
196    #[tokio::test]
197    async fn batch_transfer_fails_with_alloy_when_rpc_unreachable_after_nonempty_details() {
198        let provider = ProviderBuilder::new().connect_http("http://127.0.0.1:65526".parse().unwrap());
199        let from = address!("0x1111111111111111111111111111111111111111");
200        let row = CommittedTransferDetail {
201            to: address!("0x3333333333333333333333333333333333333333"),
202            token: from,
203            value: U256::from(1u64),
204            commitment: B256::repeat_byte(0xcc),
205        };
206        let args = SelfBatchTransferWithCommitArgs {
207            details: vec![row],
208        };
209        let r = batch_transfer(&provider, from, args).await;
210        assert!(matches!(r, Err(crate::SdkError::Alloy(_))));
211    }
212}