Rates Module
A module used to interact with a Rates contract, applying rates on fund transfers.
There are two type of rates:
Tax
The tax payment that will be attached to any agreed NFT transfers undertaken within the contract. Any rounding caused by the module favors the tax receiver.
Any payments generated by the module will be recorded in the response event and are added to the agreed transfer amount. A separate payment will be generated for each receiver and will be the same amount for each receiver and not divided amongst them.
Royalties
The royalty payment that will be attached to any agreed NFT transfers undertaken within the contract. Any rounding caused by the module favors the royalty receiver.
Any payments generated by the module will be recorded in the response event and are deducted from the seller. A separate payment will be generated for each receiver and will be the same amount for each receiver and not divided amongst them.
Each of the recipients will receive the rate imposed. ( The rate is 3% and we have 5 recipients then 15 % would go to them in total.)
Module_type: rates
The instantiate message for the rates contract.
pub struct InstantiateMsg {
pub rates: Vec<RateInfo>,
}
pub struct RateInfo {
pub rate: Rate,
pub is_additive: bool,
pub description: Option<String>,
pub recipients: Vec<Recipient>,
}
Name | Type | Description |
---|---|---|
rate | Rate | The type of rate being taken. |
is_additive | bool | An indicator to whether the rate being taken is tax or royalty. If tax is_additive is set to true. |
description | Option<String> | Optional description for the rate. |
recipients | Vec<Recipient> | The addresses to receive the rate specified. |
When we have a transfer of funds of some sort we call the
on_funds_transfer
function to send the provided hook message to all registered modules. pub fn on_funds_transfer(
&self,
storage: &dyn Storage,
api: &dyn Api,
querier: &QuerierWrapper,
sender: String,
receiver: String,
amount: Funds,
msg: Binary,
) -> Result<(Vec<SubMsg>, Vec<Event>, Funds), ContractError> {
let modules: Vec<Module> = self.load_modules(storage)?;
let mut remainder = amount;
let mut msgs: Vec<SubMsg> = Vec::new();
let mut events: Vec<Event> = Vec::new();
let mut receipt_module_address: Option<String> = None;
for module in modules {
let app_contract = self.get_app_contract(storage)?;
let module_address = module.address.get_address(api, querier, app_contract)?;
if module.module_type == RECEIPT {
// If receipt module exists we want to make sure we do it last.
receipt_module_address = Some(module_address);
continue;
}
let mod_resp: Option<OnFundsTransferResponse> = hook_query(
querier,
AndromedaHook::OnFundsTransfer {
payload: msg.clone(),
sender: sender.clone(),
receiver: receiver.clone(),
amount: remainder.clone(),
},
module_address,
)?;
if let Some(mod_resp) = mod_resp {
remainder = mod_resp.leftover_funds;
msgs = [msgs, mod_resp.msgs].concat();
events = [events, mod_resp.events].concat();
}
}
if let Some(receipt_module_address) = receipt_module_address {
let mod_resp: Option<OnFundsTransferResponse> = hook_query(
querier,
AndromedaHook::OnFundsTransfer {
payload: to_binary(&events)?,
sender,
receiver,
amount: remainder.clone(),
},
receipt_module_address,
)?;
if let Some(mod_resp) = mod_resp {
msgs = [msgs, mod_resp.msgs].concat();
events = [events, mod_resp.events].concat();
}
}
Ok((msgs, events, remainder))
}
If a rates module is found, it will be sent the
OnFundsTransfer
hook: pub enum AndromedaHook{
OnFundsTransfer {
sender: String,
payload: Binary,
amount: Funds,
}
}
This would call the rates contract to deduct the necessary funds from the amount and would return an
OnFundsTransferResponse
containing the left over funds after rates have been applied and any other messages and events that need to be returned (used for receipts):fn query_deducted_funds(
deps: Deps,
funds: Funds,
sender: Option<String>,
receiver: Option<String>,
) -> Result<OnFundsTransferResponse, ContractError> {
let config = CONFIG.load(deps.storage)?;
// Check sender for exemption
if let Some(sender_addr) = sender {
let is_exempt = EXEMPT_ADDRESSES
.load(deps.storage, &sender_addr)
.unwrap_or(false);
// If sender is exempt then do not deduct any funds
if is_exempt {
return Ok(OnFundsTransferResponse {
msgs: vec![],
events: vec![],
leftover_funds: funds,
});
}
}
// Check receiver for exemption
if let Some(receiver_addr) = receiver {
let is_exempt = EXEMPT_ADDRESSES
.load(deps.storage, &receiver_addr)
.unwrap_or(false);
// If receiver is exempt then do not deduct any funds
if is_exempt {
return Ok(OnFundsTransferResponse {
msgs: vec![],
events: vec![],
leftover_funds: funds,
});
}
}
let mut msgs: Vec<SubMsg> = vec![];
let mut events: Vec<Event> = vec![];
let (coin, is_native): (Coin, bool) = match funds {
Funds::Native(coin) => (coin, true),
Funds::Cw20(cw20_coin) => (coin(cw20_coin.amount.u128(), cw20_coin.address), false),
};
let mut leftover_funds = vec![coin.clone()];
for rate_info in config.rates.iter() {
let event_name = if rate_info.is_additive {
"tax"
} else {
"royalty"
};
let mut event = Event::new(event_name);
if let Some(desc) = &rate_info.description {
event = event.add_attribute("description", desc);
}
let app_contract = ADOContract::default().get_app_contract(deps.storage)?;
let rate = rate_info
.rate
.validate(deps.api, &deps.querier, app_contract)?;
let fee = calculate_fee(rate, &coin)?;
for reciever in rate_info.recipients.iter() {
if !rate_info.is_additive {
deduct_funds(&mut leftover_funds, &fee)?;
event = event.add_attribute("deducted", fee.to_string());
}
event = event.add_attribute(
"payment",
PaymentAttribute {
receiver: reciever.get_addr(
deps.api,
&deps.querier,
ADOContract::default().get_app_contract(deps.storage)?,
)?,
amount: fee.clone(),
}
.to_string(),
);
let msg = if is_native {
reciever.generate_msg_native(
deps.api,
&deps.querier,
ADOContract::default().get_app_contract(deps.storage)?,
vec![fee.clone()],
)?
} else {
reciever.generate_msg_cw20(
deps.api,
&deps.querier,
ADOContract::default().get_app_contract(deps.storage)?,
Cw20Coin {
amount: fee.amount,
address: fee.denom.to_string(),
},
)?
};
msgs.push(msg);
}
events.push(event);
}
Ok(OnFundsTransferResponse {
msgs,
leftover_funds: if is_native {
Funds::Native(leftover_funds[0].clone())
} else {
Funds::Cw20(Cw20Coin {
amount: leftover_funds[0].amount,
address: coin.denom,
})
},
events,
})
}
Last modified 6mo ago