https://github.com/lanfordcai/flow-approver
Approver/Allowance for Flow/Cadence
https://github.com/lanfordcai/flow-approver
blockchain cadence flow smart-contracts
Last synced: 7 months ago
JSON representation
Approver/Allowance for Flow/Cadence
- Host: GitHub
- URL: https://github.com/lanfordcai/flow-approver
- Owner: LanfordCai
- License: mit
- Created: 2022-03-12T10:27:02.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-04-05T17:18:36.000Z (over 3 years ago)
- Last Synced: 2025-02-10T06:32:43.404Z (8 months ago)
- Topics: blockchain, cadence, flow, smart-contracts
- Language: Cadence
- Homepage:
- Size: 359 KB
- Stars: 5
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# flow-approver
`Approver` aims to implement ERC20-like `approve` and `transferFrom` mechanism on Flow.
There are two resources defined in this contract:
- `Allowance`: This resource defines the allowance value approved to spender, and wraps a `Capability` of `Vault` (`Capability<&{FungibleToken.Provider, FungibleToken.Balance}>`) in it. Some checks would be run in `Allowance` before the spender spend the fund. The `Allowance` conforms to these interfaces:
```cadence
pub resource interface AllowanceInfo {
pub var value: UFix64
pub fun getVaultOwner(): Address
}
pub resource interface AllowanceProvider {
pub fun withdraw(amount: UFix64): @FungibleToken.Vault
}
pub resource interface AllowanceManager {
pub fun setAllowance(value: UFix64)
}
```
- `AllowanceCapReceiver`: The spender must set up this resource. The approver need to send the `Capability` of `Allowance` (`Capability<&{AllowanceProvider, AllowanceInfo}>`) to this receiver, so that the spender can spend the fund. `AllowanceCapReceiver` conforms to this interface:
```cadence
pub resource interface AllowanceCapReceiverPublic {
pub fun addAllowanceCap(_ allowance: Capability<&{AllowanceProvider, AllowanceInfo}>)
pub fun getAllowanceCapsInfoByApprover(_ approver: Address): [&{AllowanceInfo}]
}
```
If Alice wants to approve her FUSD fund for Bob to spend. The process might be:
- Bob set up an `AllowanceCapReceiver`:
```cadence
import FUSD from "../contracts/FUSD.cdc"
import FungibleToken from "../contracts/FungibleToken.cdc"
import Approver from "../contracts/Approver.cdc"
transaction {
prepare(signer: AuthAccount) {
if signer.borrow<&Approver.AllowanceCapReceiver>(from: Approver.AllowanceCapReceiverStoragePath) != nil {
return
}
signer.save(
<- Approver.createAllowanceCapReceiver(),
to: Approver.AllowanceCapReceiverStoragePath
)
signer.link<&{Approver.AllowanceCapReceiverPublic}>(
Approver.AllowanceCapReceiverPubPath,
target: Approver.AllowanceCapReceiverStoragePath
)
}
}
```
- Alice approves her fund to Bob:
```cadence
import FUSD from "../contracts/FUSD.cdc"
import FungibleToken from "../contracts/FungibleToken.cdc"
import Approver from "../contracts/Approver.cdc"
transaction(spender: Address, value: UFix64) {
let allowanceCap: Capability<&{Approver.AllowanceProvider, Approver.AllowanceInfo}>
prepare(signer: AuthAccount) {
signer.link<&{FungibleToken.Provider, FungibleToken.Balance}>(/private/fusdVault, target: /storage/fusdVault)
let vaultCap = signer.getCapability<&{FungibleToken.Provider, FungibleToken.Balance}>(/private/fusdVault)!
let allowance <- Approver.createAllowance(
value: value,
vaultCap: vaultCap
)
let pathID = "fusdAllowanceFor".concat(spender.toString())
let storagePath = StoragePath(identifier: pathID)!
let publicPath = PublicPath(identifier: pathID)!
let privatePath = PrivatePath(identifier: pathID)!
signer.save(<- allowance, to: storagePath)
signer.link<&{Approver.AllowanceInfo}>(publicPath, target: storagePath)
signer.link<&{Approver.AllowanceProvider, Approver.AllowanceInfo}>(
privatePath,
target: storagePath
)
self.allowanceCap = signer.getCapability<&{Approver.AllowanceProvider, Approver.AllowanceInfo}>(privatePath)
}
execute {
let receiver = getAccount(spender).getCapability<&{Approver.AllowanceCapReceiverPublic}>(
Approver.AllowanceCapReceiverPubPath).borrow()
?? panic("Could not borrow AllowanceCapReceiver capability")
receiver.addAllowanceCap(self.allowanceCap)
}
}
```
- Bob spends the fund of Alice:
```cadence
import FUSD from "../contracts/FUSD.cdc"
import FungibleToken from "../contracts/FungibleToken.cdc"
import Approver from "../contracts/Approver.cdc"
transaction(from: Address, to: Address, value: UFix64) {
let capReceiver: &Approver.AllowanceCapReceiver
prepare(signer: AuthAccount) {
self.capReceiver = signer.borrow<&Approver.AllowanceCapReceiver>(from: Approver.AllowanceCapReceiverStoragePath)
?? panic("Could not borrow AllowanceCapReceiver reference")
}
execute {
let vaultReceiver = getAccount(to).getCapability<&{FungibleToken.Receiver}>(/public/fusdReceiver).borrow()
?? panic("Could not get Receiver capability")
let cap = self.capReceiver.getAllowanceCapsByApprover(from)[0]
vaultReceiver.deposit(from: <- cap.borrow()!.withdraw(amount: value))
}
}
```
- Alice can change the allowance to Bob if she wants:
```cadence
import Approver from "../contracts/Approver.cdc"
transaction(spender: Address, value: UFix64) {
prepare(signer: AuthAccount) {
let pathID = "fusdAllowanceFor".concat(spender.toString())
let storagePath = StoragePath(identifier: pathID)!
let allowance = signer.borrow<&Approver.Allowance>(from: storagePath)
?? panic("Could not borrow Allowance reference")
allowance.setAllowance(value: value)
}
}
```
- Alice can cancel the allowance to Bob if she wants:
```cadence
transaction(spender: Address) {
prepare(signer: AuthAccount) {
let pathID = "fusdAllowanceFor".concat(spender.toString())
let privatePath = PrivatePath(identifier: pathID)!
let publicPath = PublicPath(identifier: pathID)!
signer.unlink(privatePath)
signer.unlink(publicPath)
}
}
```
- Alice can recover the allowance to Bob if she wants:
```cadence
import Approver from "../contracts/Approver.cdc"
transaction(spender: Address) {
prepare(signer: AuthAccount) {
let pathID = "fusdAllowanceFor".concat(spender.toString())
let storagePath = StoragePath(identifier: pathID)!
let privatePath = PrivatePath(identifier: pathID)!
let publicPath = PublicPath(identifier: pathID)!
signer.link<&{Approver.AllowanceProvider, Approver.AllowanceInfo}>(privatePath, target: storagePath)
signer.link<&{Approver.AllowanceInfo}>(publicPath, target: storagePath)
signer.getCapability<&{Approver.AllowanceProvider, Approver.AllowanceInfo}>(privatePath).borrow()
?? panic("Could not get private {AllowanceProvider, AllowanceInfo} capability")
signer.getCapability<&{Approver.AllowanceInfo}>(publicPath).borrow()
?? panic("Could not get public {AllowanceInfo} capability")
}
}
```
- The allowance for a specific pair of approver/spender can be checked by:
```cadence
import Approver from "../contracts/Approver.cdc"
pub struct AllowanceInfo {
pub let value: UFix64
pub let vaultOwner: Address
init(value: UFix64, vaultOwner: Address) {
self.value = value
self.vaultOwner = vaultOwner
}
}
pub fun main(approver: Address, spender: Address): AllowanceInfo {
let path = PublicPath(identifier: "fusdAllowanceFor".concat(spender.toString()))!
let info = getAccount(approver)
.getCapability<&{Approver.AllowanceInfo}>(path)
.borrow() ?? panic("Could not borrow AllowanceInfo capability")
return AllowanceInfo(value: info.value, vaultOwner: info.getVaultOwner())
}
```
or
```cadence
import Approver from "../contracts/Approver.cdc"
pub struct AllowanceInfo {
pub let value: UFix64
pub let vaultOwner: Address
init(value: UFix64, vaultOwner: Address) {
self.value = value
self.vaultOwner = vaultOwner
}
}
pub fun main(approver: Address, spender: Address): [AllowanceInfo] {
let receiver = getAccount(spender)
.getCapability<&{Approver.AllowanceCapReceiverPublic}>(Approver.AllowanceCapReceiverPubPath)
.borrow()
?? panic("Could not borrow AllowanceCapReceiverPublic capability")
let infos: [AllowanceInfo] = []
for i in receiver.getAllowanceCapsInfoByApprover(approver) {
infos.append(
AllowanceInfo(value: i.value, vaultOwner: i.getVaultOwner())
)
}
return infos
}
```The storage of Alice and Bob might be like this:
