Contract Name:
CyanWalletLogic
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
enum ConduitItemType {
NATIVE, // unused
ERC20,
ERC721,
ERC1155
}
struct ConduitTransfer {
ConduitItemType itemType;
address collection;
address from;
address to;
uint256 identifier;
uint256 amount;
}
struct ConduitBatch1155Transfer {
address collection;
address from;
address to;
uint256[] ids;
uint256[] amounts;
}
interface ICyanConduit {
error ChannelClosed(address channel);
error ChannelStatusAlreadySet(address channel, bool isOpen);
error InvalidItemType();
error InvalidAdmin();
event ChannelUpdated(address indexed channel, bool open);
function execute(ConduitTransfer[] calldata transfers) external returns (bytes4 magicValue);
function executeBatch1155(ConduitBatch1155Transfer[] calldata batch1155Transfers)
external
returns (bytes4 magicValue);
function executeWithBatch1155(
ConduitTransfer[] calldata standardTransfers,
ConduitBatch1155Transfer[] calldata batch1155Transfers
) external returns (bytes4 magicValue);
function transferERC20(
address from,
address to,
address token,
uint256 amount
) external;
function transferERC721(
address from,
address to,
address collection,
uint256 tokenId
) external;
function transferERC1155(
address from,
address to,
address collection,
uint256 tokenId,
uint256 amount
) external;
function updateChannel(address channel, bool isOpen) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import { Item } from "../../main/payment-plan/PaymentPlanTypes.sol";
interface IWallet {
function executeModule(bytes memory) external returns (bytes memory);
function transferNonLockedERC721(
address,
uint256,
address
) external;
function transferNonLockedERC1155(
address,
uint256,
uint256,
address
) external;
function transferNonLockedCryptoPunk(uint256, address) external;
function setLockedERC721Token(
address,
uint256,
bool
) external;
function increaseLockedERC1155Token(
address,
uint256,
uint256
) external;
function decreaseLockedERC1155Token(
address,
uint256,
uint256
) external;
function setLockedCryptoPunk(uint256, bool) external;
function autoPay(
uint256,
uint256,
uint8
) external;
function earlyUnwindOpensea(
uint256,
uint256,
Item memory,
bytes memory
) external;
function earlyUnwindCyan(uint256, address) external;
function isLockedNFT(address, uint256) external view returns (bool);
function repayBendDaoLoan(
address collection,
uint256 tokenId,
uint256 amount,
address currency
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
interface ICyanPeerPlan {
enum PlanStatus {
NONE,
ACTIVE,
DEFAULTED,
COMPLETED,
LIQUIDATED
}
struct LenderSignature {
uint256 signedDate;
uint256 expiryDate;
uint32 maxUsageCount;
bool extendable;
bytes signature;
}
struct Plan {
uint256 amount;
address lenderAddress;
address currencyAddress;
uint32 interestRate;
uint32 serviceFeeRate;
uint32 term;
}
struct PaymentPlan {
Plan plan;
uint256 dueDate;
address cyanWalletAddress;
PlanStatus status;
bool extendable;
}
struct Item {
uint256 amount;
uint256 tokenId;
address contractAddress;
// 1 -> ERC721
// 2 -> ERC1155
// 3 -> CryptoPunks
uint8 itemType;
bytes collectionSignature;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/// @title Cyan AddressProvider contract
/// @author Bulgantamir Gankhuyag - <[email protected]>
/// @author Naranbayar Uuganbayar - <[email protected]>
contract AddressProvider is Ownable {
error AddressNotFound(bytes32 id);
event AddressSet(bytes32 id, address newAddress);
mapping(bytes32 => address) public addresses;
constructor(address owner) {
transferOwnership(owner);
}
// @dev Sets an address for an id replacing the address saved in the addresses map
// @param id The id
// @param newAddress The address to set
function setAddress(bytes32 id, address newAddress) external onlyOwner {
addresses[id] = newAddress;
emit AddressSet(id, newAddress);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import { ICyanConduit } from "../interfaces/conduit/ICyanConduit.sol";
import { AddressProvider } from "../main/AddressProvider.sol";
import "../thirdparty/ICryptoPunk.sol";
import "../interfaces/core/IWallet.sol";
import "../interfaces/main/ICyanPeerPlan.sol";
import "./payment-plan/PaymentPlanTypes.sol";
library CyanWalletLogic {
AddressProvider private constant addressProvider = AddressProvider(0xCF9A19D879769aDaE5e4f31503AAECDa82568E55);
/**
* @notice Allows operators to transfer out non locked tokens.
* Note: Can only transfer if token is not locked.
* @param cyanWalletAddress Cyan Wallet address
* @param to Receiver address
* @param item Transferring item
*/
function transferNonLockedItem(
address cyanWalletAddress,
address to,
Item calldata item
) external {
_transferNonLockedItem(cyanWalletAddress, to, item.contractAddress, item.tokenId, item.amount, item.itemType);
}
/**
* @notice Allows operators to transfer out non locked tokens.
* Note: Can only transfer if token is not locked.
* @param cyanWalletAddress Cyan Wallet address
* @param to Receiver address
* @param item Transferring item
*/
function transferNonLockedItem(
address cyanWalletAddress,
address to,
ICyanPeerPlan.Item calldata item
) external {
_transferNonLockedItem(cyanWalletAddress, to, item.contractAddress, item.tokenId, item.amount, item.itemType);
}
function _transferNonLockedItem(
address cyanWalletAddress,
address to,
address collection,
uint256 tokenId,
uint256 amount,
uint8 itemType
) private {
IWallet wallet = IWallet(cyanWalletAddress);
if (itemType == 1) {
// ERC721
wallet.executeModule(
abi.encodeWithSelector(IWallet.transferNonLockedERC721.selector, collection, tokenId, to)
);
} else if (itemType == 2) {
// ERC1155
wallet.executeModule(
abi.encodeWithSelector(IWallet.transferNonLockedERC1155.selector, collection, tokenId, amount, to)
);
} else if (itemType == 3) {
// CryptoPunks
wallet.executeModule(abi.encodeWithSelector(IWallet.transferNonLockedCryptoPunk.selector, tokenId, to));
} else {
revert InvalidItem();
}
}
/**
* @notice Transfers token to CyanWallet and locks it
* @param from From address
* @param cyanWalletAddress Cyan Wallet address
* @param item Transferring item
*/
function transferItemAndLock(
address from,
address cyanWalletAddress,
Item calldata item
) external {
_transferItemAndLock(from, cyanWalletAddress, item.contractAddress, item.tokenId, item.amount, item.itemType);
}
/**
* @notice Transfers token to CyanWallet and locks it
* @param from From address
* @param cyanWalletAddress Cyan Wallet address
* @param item Transferring item
*/
function transferItemAndLock(
address from,
address cyanWalletAddress,
ICyanPeerPlan.Item calldata item
) external {
_transferItemAndLock(from, cyanWalletAddress, item.contractAddress, item.tokenId, item.amount, item.itemType);
}
function _transferItemAndLock(
address from,
address cyanWalletAddress,
address collection,
uint256 tokenId,
uint256 amount,
uint8 itemType
) private {
if (itemType == 3) {
// CryptoPunks
ICryptoPunk cryptoPunkContract = ICryptoPunk(collection);
if (cryptoPunkContract.punkIndexToAddress(tokenId) != from) revert InvalidItem();
cryptoPunkContract.buyPunk{ value: 0 }(tokenId);
cryptoPunkContract.transferPunk(cyanWalletAddress, tokenId);
} else {
ICyanConduit conduit = ICyanConduit(addressProvider.addresses("CYAN_CONDUIT"));
if (itemType == 1) {
conduit.transferERC721(from, cyanWalletAddress, collection, tokenId);
} else if (itemType == 2) {
conduit.transferERC1155(from, cyanWalletAddress, collection, tokenId, amount);
} else {
revert InvalidItem();
}
}
_setLockState(cyanWalletAddress, collection, tokenId, amount, itemType, true);
}
/**
* @notice Update locking status of a token in Cyan Wallet
* @param cyanWalletAddress Cyan Wallet address
* @param item Locking/unlocking item
* @param state Token will be locked if true
*/
function setLockState(
address cyanWalletAddress,
Item calldata item,
bool state
) public {
_setLockState(cyanWalletAddress, item.contractAddress, item.tokenId, item.amount, item.itemType, state);
}
/**
* @notice Update locking status of a token in Cyan Wallet
* @param cyanWalletAddress Cyan Wallet address
* @param item Locking/unlocking item
* @param state Token will be locked if true
*/
function setLockState(
address cyanWalletAddress,
ICyanPeerPlan.Item calldata item,
bool state
) public {
_setLockState(cyanWalletAddress, item.contractAddress, item.tokenId, item.amount, item.itemType, state);
}
function _setLockState(
address cyanWalletAddress,
address collection,
uint256 tokenId,
uint256 amount,
uint8 itemType,
bool state
) private {
IWallet wallet = IWallet(cyanWalletAddress);
if (itemType == 1) {
// ERC721
wallet.executeModule(
abi.encodeWithSelector(IWallet.setLockedERC721Token.selector, collection, tokenId, state)
);
} else if (itemType == 2) {
// ERC1155
wallet.executeModule(
abi.encodeWithSelector(
state ? IWallet.increaseLockedERC1155Token.selector : IWallet.decreaseLockedERC1155Token.selector,
collection,
tokenId,
amount
)
);
} else if (itemType == 3) {
// CryptoPunks
wallet.executeModule(abi.encodeWithSelector(IWallet.setLockedCryptoPunk.selector, tokenId, state));
} else {
revert InvalidItem();
}
}
/**
* @notice Triggers Cyan Wallet's autoPay method
* @param cyanWalletAddress Cyan Wallet address
* @param planId Payment plan ID
* @param amount Pay amount for the plan
* @param autoRepayStatus Auto repayment status
*/
function executeAutoPay(
address cyanWalletAddress,
uint256 planId,
uint256 amount,
uint8 autoRepayStatus
) external {
IWallet(cyanWalletAddress).executeModule(
abi.encodeWithSelector(IWallet.autoPay.selector, planId, amount, autoRepayStatus)
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
// DataTypes
enum PawnCreateType {
REGULAR,
BEND_DAO,
REFINANCE
}
enum PaymentPlanStatus {
BNPL_CREATED,
BNPL_FUNDED,
BNPL_ACTIVE,
BNPL_DEFAULTED,
BNPL_REJECTED,
BNPL_COMPLETED,
BNPL_LIQUIDATED,
PAWN_ACTIVE,
PAWN_DEFAULTED,
PAWN_COMPLETED,
PAWN_LIQUIDATED
}
struct Plan {
uint256 amount;
uint32 downPaymentPercent;
uint32 interestRate;
uint32 serviceFeeRate;
uint32 term;
uint8 totalNumberOfPayments;
uint8 counterPaidPayments;
uint8 autoRepayStatus;
}
struct PaymentPlan {
Plan plan;
uint256 createdDate;
address cyanWalletAddress;
PaymentPlanStatus status;
}
struct Item {
uint256 amount;
uint256 tokenId;
address contractAddress;
address cyanVaultAddress;
// 1 -> ERC721
// 2 -> ERC1155
// 3 -> CryptoPunks
uint8 itemType;
}
struct PaymentAmountInfo {
uint256 loanAmount;
uint256 interestAmount;
uint256 serviceAmount;
}
// Errors
error InvalidSender();
error InvalidBlockNumber();
error InvalidSignature();
error InvalidServiceFeeRate();
error InvalidTokenPrice();
error InvalidInterestRate();
error InvalidDownPaymentPercent();
error InvalidDownPayment();
error InvalidAmount();
error InvalidTerm();
error InvalidPaidCount();
error InvalidStage();
error InvalidAddress();
error InvalidAutoRepaymentDate();
error InvalidAutoRepaymentStatus();
error InvalidTotalNumberOfPayments();
error InvalidReviveDate();
error InvalidItem();
error InvalidBaseDiscountRate();
error InvalidApeCoinPlan();
error InvalidBendDaoPlan();
error InvalidCurrency();
error InvalidCyanBuyer();
error InvalidSelector();
error EthTransferFailed();
error PaymentPlanAlreadyExists();
error PaymentPlanNotFound();
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
interface ICryptoPunk {
function punkIndexToAddress(uint256) external view returns (address);
function buyPunk(uint256) external payable;
function transferPunk(address, uint256) external;
function offerPunkForSale(uint256, uint256) external;
function offerPunkForSaleToAddress(
uint256,
uint256,
address
) external;
function acceptBidForPunk(uint256, uint256) external;
}