Transfer rules
Transfer rules are needed in order for Transfer
to be permitted. A Transfer without a matching allow
TransferRule
will always be rejected.
Setup
We will assume a fresh treasury instance or demo for these examples.
treasury demo start --port 8777 --pull
We're also create some addresses and put them into accounts.
treasury script
sol1 = create internal address for SOL
sol2 = create internal address for SOL
btc1 = create internal address for BTC
btc2 = create internal address for BTC
SOL = asset SOL for SOL
BTC = asset BTC for BTC
account1 = create internal account { addresses = [$sol1, $btc1] }
account2 = create internal account { addresses = [$sol2, $btc2] }
Examples
No matching rule
This is the default behavior if there's no matching rule. Default deny.
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "1"}
Basic allow rule
We can permit it using an allow rule.
create allow transfer-rule basic {from = $sol1, to = $sol2, asset = $SOL}
# now you can transfer.
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "1"}
# but not bitcoin!
create transfer {from = $btc1, to = $btc2, asset = $BTC, amount = "1"}
# delete rule
_, err = delete transfer-rule basic
We can also make the rule based on matching the account(s) of the addresses.
# make rule based on accounts
create allow transfer-rule basic-account {from = $account1, to = $account2, asset = $SOL}
# transfer again
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "1"}
# delete rule
_, err = delete transfer-rule basic-account
Wildcards
You can make patterns to match anything. This will allow sending any asset to $account2
from anywhere.
create allow transfer-rule wildcard-to-account {from = "any/address", to = $account2, asset = "any/asset"}
# now we can transfer both solana and btc
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "1"}
create transfer {from = $btc1, to = $btc2, asset = $BTC, amount = "1"}
_, err = delete transfer-rule wildcard-to-account
Deny rule
If a deny rule matches, the outcome will always be rejected.
We can build on our wildcard rule to deny sending BTC
.
create allow transfer-rule wildcard-to-account {from = "any/address", to = $account2, asset = "any/asset"}
create deny transfer-rule deny-btc-to-account {from = "any/address", to = $account2, asset = $BTC}
# this will be denied
create transfer {from = $btc1, to = $btc2, asset = $BTC, amount = "1"}
_, err = delete transfer-rule wildcard-to-account
_, err = delete transfer-rule deny-btc-to-account
Approvals
Additional conditions about who is allowed to initiate a Transfer
(and how many approvers)
can be attached, much like an AccessRule
.
To permit admin
role to initiate or approve:
create allow transfer-rule initiate {from = $sol1, to = $sol2, asset = $SOL, initiate = "roles/admin", approvals = 1}
_, err = delete transfer-rule initiate
Users with the admin
role can now make matching SOL transfers from $sol1
to $sol2
(but nothing else yet).
Global requirements
Attaching access conditions to all individual transfer rules can be tedious. You can define global
requirements by using the require
variant.
If any require
variant rules match, they must all evaluate as true or the transfer will be rejected or remain pending.
To require all transfers out of $acc1
to require to be initiated and approved from roles/admin
users:
# create an allow(s) rule permitting the movement of assets out of `$acc1`
create allow transfer-rule allowSol {from = $account1, to = $account2, asset = $SOL}
create allow transfer-rule allowBtc {from = $account1, to = $btc2, asset = $BTC}
# require all transfers out of `$account1` to need approval.
create require transfer-rule require-account1-approvals {from = $account1, to = "any/address", asset = "any/asset", initiate = "roles/admin", approvals = 1}
# now transfers are rejected by the root user.
create transfer {from = $btc1, to = $btc2, asset = $BTC, amount = "1"}
_, err = delete transfer-rule require-account1-approvals
We can add the admin
role to our root
user account to be able to initiate.
create role admin
update user root {roles = ["roles/admin"]}
# Now we can start a transfer operation. It will wait until another admin user approves.
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "1"}
Require two different roles to approve
You may want two different roles to sign off on some matching set of transfers. Perhaps you want two different geographies signing off on something.
Here we'll create two require rules, that each require a trader-us
and trader-eu
role approving.
create require transfer-rule require-us {from = $account1, to = "any/address", asset = "any/asset", initiate = ["roles/trader-us", "roles/trader-eu"], approve = "roles/trader-us", approvals = 1}
create require transfer-rule require-eu {from = $account1, to = "any/address", asset = "any/asset", initiate = ["roles/trader-us", "roles/trader-eu"], approve = "roles/trader-eu", approvals = 1}
In order for any transfer to approve, it will need approvals from both the trader-us
adn trader-eu
roles.
Notional limits
Transfers can be rejected based on their notional (USD) amount, or the total amount transferred in a given time period.
Setup
Note in order for notional limits to work, you should connect your treasury instance or demo to our API to get pricing information on assets.
If there is no price for an asset, then any matching rule with a notional limit will reject (default deny).
If you're using a demo, you can restart it and pass your API key.
treasury demo stop
treasury demo start --port 8777 --pull --api-key ${TREASURY_API_KEY}
Alternatively, you can manually set prices for assets.
treasury assets price chains/BTC/assets/BTC --amount 70000 --valid-now
treasury assets price chains/SOL/assets/SOL --amount 150 --valid-now
Check prices are set on assets.
treasury assets ls
Limit by amount
Limit any matching transfer to not be more than $300 USD.
create allow transfer-rule limit-amount {from = $account1, to = $account2, amount = {at_most = "300", quote = "USD"}, asset = $SOL}
# transfering 1 SOL (~$150) works
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "1"}
# transfering 3 SOL (~$450) will be rejected
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "3"}
_, err = delete transfer-rule limit-amount
Rate limit by amount
Limit any matching transfer(s) to not exceed more than $1000 USD over a 1 day period.
create allow transfer-rule rate-limit {from = $account1, to = $account2, amount = {within = "1d", at_most = "1000", quote = "USD"}, asset = $SOL}
# transfering 5 SOL (~$750) works, unless we transferred >$1000 SOL before
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "5"}
# transferring 5 SOL (~$750) does not work, since we exceed the daily limit
create transfer {from = $sol1, to = $sol2, asset = $SOL, amount = "1"}
_, err = delete transfer-rule rate-limit
Thresholding
We can make transfer rules apply after a certain dollar threshold is reached.
Permit transfers under $1k daily limit to $account2
, but require approvals
for when the limit is exceeded.
create allow transfer-rule permit-1k {from = $account1, to = $account2, amount = {within = "1d", at_most = "1000", quote = "USD"}, asset = "any/asset"}
create allow transfer-rule approval-1k {from = $account1, to = $account2, amount = {within = "1d", at_least = "1000", quote = "USD"}, asset = "any/asset", initiate = "roles/admin", approvals = 1}
Global thresholds
We can use notional limits in require rules as well.
This will require all transfers over $10k USD to require an approval from an roles/admin
user.
create require transfer-rule global-approval-1k {from = "any/address", to = "any/address", amount = {within = "1d", at_least = "10000", quote = "USD"}, asset = "any/asset", initiate = "roles/admin", approvals = 1}