Andromeda
Search
⌃K

Rates Module

A module used to interact with a Rates contract, applying rates on fund transfers.

Definition

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

InstantiateMsg

The instantiate message for the rates contract.
pub struct InstantiateMsg {
pub rates: Vec<RateInfo>,
}

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.
More information on the Rates contract can be found here.

Hooks

OnFundsTransfer

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,
})
}