# Gear 合约开发案例学习: Escrow 钱包
上节回顾: [为 Gear 合约添加自定义状态查询函数](https://hackmd.io/@btwiuse/gear-metafns)
## Escrow 合约
担保 (Escrow) 是指交易双方不能够互相信任的条件下,通过第三方达成交易目标的一种制度。
买方: 担心卖家货物不合格
卖家: 担心买家不付钱
担保服务要求买方先将货款存到第三方,在买方确认货品品质后,第三方再将货款转给卖方。
<hr/>
https://github.com/gear-dapps/escrow
https://wiki.gear-tech.io/docs/examples/escrow/
担保服务: Escrow 合约
货款: Fungible Token
货物: 任意物品
买方: Alice
卖方: Bob
第三方: Escrow 钱包
## 合约 Metadata
./io/src/lib.rs
```
pub struct EscrowMetadata;
impl Metadata for EscrowMetadata {
type Init = In<InitEscrow>;
type Handle = InOut<EscrowAction, EscrowEvent>;
type Others = ();
type Reply = ();
type Signal = ();
type State = EscrowState;
}
/// Initializes an escrow program.
#[derive(Decode, Encode, TypeInfo)]
pub struct InitEscrow {
/// Address of a fungible token program.
pub ft_program_id: ActorId,
}
#[derive(Clone, Decode, Encode, TypeInfo)]
pub enum EscrowAction {
Create {
buyer: ActorId,
seller: ActorId,
amount: u128,
},
Deposit(
WalletId,
),
Confirm(
WalletId,
),
Refund(
WalletId,
),
Cancel(
WalletId,
),
Continue(
u64,
),
}
/// An enum that contains a result of processed [`EscrowAction`].
#[derive(Decode, Encode, TypeInfo)]
pub enum EscrowEvent {
...
}
#[derive(Default, Encode, Decode, Clone, TypeInfo)]
pub struct EscrowState {
pub ft_program_id: ActorId,
pub wallets: Vec<(WalletId, Wallet)>,
pub id_nonce: WalletId,
pub transaction_id: u64,
pub transactions: Vec<(u64, Option<EscrowAction>)>,
}
/// Escrow wallet information.
#[derive(Decode, Encode, TypeInfo, Clone, Copy, Debug, PartialEq, Eq)]
pub struct Wallet {
/// A buyer.
pub buyer: ActorId,
/// A seller.
pub seller: ActorId,
/// A wallet state.
pub state: WalletState,
/// An amount of tokens that a wallet can have. **Not** a current amount on a wallet balance!
pub amount: u128,
}
/// An escrow wallet state.
#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone, Copy, Debug)]
pub enum WalletState {
AwaitingDeposit,
AwaitingConfirmation,
Closed,
}
```
EscrowAction定义了一个枚举,用于指导程序的执行。根据这个枚举的处理结果,程序会返回 EscrowEvent
EscrowAction包含以下几种操作:
Create:创建一个托管钱包,并返回钱包ID。要求消息来源必须是买家或卖家,且地址不能为零。成功时返回EscrowEvent::Created
Deposit:买家向托管钱包存款,将钱包状态更改为AwaitingConfirmation。要求消息来源必须是买家,钱包不能已付款或关闭。成功时返回 EscrowEvent::Deposited
Confirm:确认交易,将托管钱包的资金转给卖家,并将钱包状态更改为Closed。要求消息来源必须是买家,钱包必须已付款且未关闭。成功时返回 EscrowEvent::Confirmed
Refund:将托管钱包的资金退还给买家,并将钱包状态更改回AwaitingDeposit。要求消息来源必须是卖家,钱包必须已付款且未关闭。成功时返回 EscrowEvent::Refunded
Cancel:取消交易并关闭托管钱包,将钱包状态更改为Closed。要求消息来源必须是买家或卖家,钱包不能已付款或关闭。成功时返回 EscrowEvent::Cancelled
Continue:在交易因燃料不足或代币合约错误而失败时,继续执行交易。要求transaction_id必须存在于transactions表中。当交易已处理时,返回 EscrowEvent::TransactionProcessed
## 消息处理
./src/contract.rs
```
#[async_main]
async fn main() {
let action: EscrowAction = msg::load().expect("Unable to decode EscrowAction");
let escrow = unsafe { ESCROW.get_or_insert(Default::default()) };
match action {
EscrowAction::Create {
buyer,
seller,
amount,
} => escrow.create(buyer, seller, amount),
EscrowAction::Deposit(wallet_id) => {
escrow
.transactions
.insert(escrow.transaction_id, Some(action));
escrow.deposit(None, wallet_id).await
}
EscrowAction::Confirm(wallet_id) => {
escrow
.transactions
.insert(escrow.transaction_id, Some(action));
escrow.confirm(None, wallet_id).await
}
EscrowAction::Refund(wallet_id) => {
escrow
.transactions
.insert(escrow.transaction_id, Some(action));
escrow.refund(None, wallet_id).await
}
EscrowAction::Cancel(wallet_id) => escrow.cancel(wallet_id).await,
EscrowAction::Continue(transaction_id) => escrow.continue_transaction(transaction_id).await,
}
}
```

## 自定义状态查询函数
./state/src/lib.rs
```
#[metawasm]
pub mod metafns {
pub type State = <EscrowMetadata as Metadata>::State;
pub fn info(state: State, wallet_id: U256) -> Wallet {
let (_, wallet) = *state
.wallets
.iter()
.find(|(id, _)| id == &wallet_id)
.unwrap_or_else(|| panic!("Wallet with the {wallet_id} ID doesn't exist"));
wallet
}
pub fn created_wallets(state: State) -> Vec<(WalletId, Wallet)> {
state
.wallets
.iter()
.map(|(wallet_id, wallet)| (*wallet_id, *wallet))
.collect()
}
}
```
# 作业
在 GitHub 上打开 https://github.com/gear-dapps/escrow, 编译合约, 并在 [Gear IDEA](https://idea.gear-tech.io) 上部署,(初始化 `ft_program_id` 参数可使用任意地址), 最后提交你的合约访问连接
作业提交频道: https://t.me/Gear_CN