1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use crate::market::base::{ GAS_FOR_FT_TRANSFER, MarketFeature };
use crate::market::metadata::{ TokenId, Bids, MarketOnNftApproveArgs };
use near_sdk::collections::UnorderedSet;
use std::collections::HashMap;
use near_sdk::{AccountId, env, CryptoHash, Promise, BorshStorageKey, PromiseOrValue};
use crate::market::{ Sale, MarketRemoveSale, MarketCreateSale };
use near_sdk::borsh::{ self, BorshSerialize };
use crate::ft::base::external::ext_ft;
use crate::utils::{ contract_token_id, hash_account_id, near_ft };

#[derive(BorshStorageKey, BorshSerialize)]
pub enum StorageKey {
    ByOwnerIdInner {
        account_id_hash: CryptoHash,
    },
    ByNFTContractIdInner {
        account_id_hash: CryptoHash,
    },
}

impl MarketFeature {
    /// refund the last bid of each token type, don't update sale because it's already been removed

    pub(crate) fn refund_all_bids(&mut self, bids: &Bids) {
        for (bid_ft, bid_vec) in bids {
            let bid = &bid_vec[bid_vec.len() - 1];
            if bid_ft == &near_ft() {
                Promise::new(bid.owner_id.clone()).transfer(u128::from(bid.price));
            } else {
                ext_ft
                    ::ext(bid_ft.clone())
                    .with_static_gas(GAS_FOR_FT_TRANSFER)
                    .with_attached_deposit(1)
                    .ft_transfer(bid.owner_id.clone(), bid.price, None);
            }
        }
    }

    pub(crate) fn internal_remove_sale(
        &mut self,
        nft_contract_id: &AccountId,
        token_id: &TokenId
    ) -> Sale {
        let contract_and_token_id = contract_token_id(&nft_contract_id, &token_id);
        let sale = self.sales.remove(&contract_and_token_id).expect("No sale");

        let mut by_owner_id = self.by_owner_id.get(&sale.owner_id).expect("No sale by_owner_id");
        by_owner_id.remove(&contract_and_token_id);
        if by_owner_id.is_empty() {
            self.by_owner_id.remove(&sale.owner_id);
        } else {
            self.by_owner_id.insert(&sale.owner_id, &by_owner_id);
        }

        let mut by_nft_contract_id = self.by_nft_contract_id
            .get(&nft_contract_id)
            .expect("No sale by nft_contract_id");
        by_nft_contract_id.remove(&token_id);
        if by_nft_contract_id.is_empty() {
            self.by_nft_contract_id.remove(&nft_contract_id);
        } else {
            self.by_nft_contract_id.insert(&nft_contract_id, &by_nft_contract_id);
        }

        (MarketRemoveSale {
            owner_id: &sale.owner_id,
            nft_contract_id: &nft_contract_id,
            token_id: &token_id,
        }).emit();

        sale
    }

    pub fn internal_on_nft_approve(
        &mut self,
        args: &MarketOnNftApproveArgs,
        nft_contract_id: &AccountId,
        token_id: &TokenId,
        owner_id: &AccountId,
        approval_id: &u64
    ) -> PromiseOrValue<String> {
        let MarketOnNftApproveArgs { is_auction, sale_conditions } = args;

        for (ft_token_id, _price) in sale_conditions.clone() {
            if !self.ft_token_ids.contains(&ft_token_id) {
                env::panic_str(
                    &format!("Token {} not supported by this market", ft_token_id).to_string()
                );
            }
        }

        let bids = HashMap::new();

        let contract_and_token_id = contract_token_id(nft_contract_id, token_id);

        let exists = self.sales.get(&contract_and_token_id);

        if exists.is_some() {
            env::panic_str("Token already listed");
        }

        let sale = Sale {
            owner_id: owner_id.clone().into(),
            approval_id: approval_id.clone(),
            nft_contract_id: nft_contract_id.clone(),
            token_id: token_id.clone(),
            sale_conditions: sale_conditions.clone(),
            bids,
            created_at: env::block_timestamp(),
            is_auction: is_auction.unwrap_or(false),
        };
        self.sales.insert(&contract_and_token_id, &sale);

        // extra for views

        let mut by_owner_id = self.by_owner_id.get(&owner_id).unwrap_or_else(|| {
            UnorderedSet::new(
                (StorageKey::ByOwnerIdInner {
                    account_id_hash: hash_account_id(&owner_id),
                })
                    .try_to_vec()
                    .unwrap()
            )
        });

        by_owner_id.insert(&contract_and_token_id);
        self.by_owner_id.insert(&owner_id, &by_owner_id);

        let mut by_nft_contract_id = self.by_nft_contract_id
            .get(&nft_contract_id)
            .unwrap_or_else(|| {
                UnorderedSet::new(
                    (StorageKey::ByNFTContractIdInner {
                        account_id_hash: hash_account_id(&nft_contract_id),
                    })
                        .try_to_vec()
                        .unwrap()
                )
            });
        by_nft_contract_id.insert(&token_id);
        self.by_nft_contract_id.insert(&nft_contract_id, &by_nft_contract_id);

        (MarketCreateSale {
            owner_id: &owner_id,
            nft_contract_id: &nft_contract_id,
            token_id: &token_id,
            sale: &sale,
        }).emit();

        PromiseOrValue::Value("true".to_string())
    }    
}