Prepayment
What is Prepayment?
Prepayment is a type of payment solution with a support for two account types: Temporary Account and Permanent Account. It is implemented within a Prepayment
smart contract and currently it can be used as a payment for Verifiable Random Function (VRF) and Request-Response services.
Temporary Account is an account for one-time-use, and it is newly created with every request that takes advantage of this type of account. It is a bit easier to utilize temporary account, therefore it is a good way for initial tests. The account exists only during the lifetime of the associated request and once the request is fulfilled, the account is deleted.
Permanent Account offers more features and in the rest of the document we will dive into them. The main components of Permanent Account are Account, Account Owner and Consumer.
Account owners are entities that create an account (
createAccount
). They can also close the account (cancelAccount
), add (addConsumer
) or remove consumer (removeConsumer
) from their account(s). $KAIA can be withdrawn from account only by the account owner, however anybody is allowed to deposit (deposit
) $KAIA to any account.Consumers assigned to account use the account's balance to pay for Orakl Network services. The ownership of account can be transferred to other entity through a two-step process (
requestAccountOwnerTransfer
,acceptAccountOwnerTransfer
). Coordinators are smart contracts that can fulfill request issued by consumers, and they distribute rewards to oracles that fulfill requests.
How to use Permanent Account?
Permanent Account can be controlled either controlled through a smart contract interface, or through a web frontend on our Orakl Network Account page. In this page, we describe how you can access and control permanent account through smart contracts only.
There are prerequisites that you have to do before you can use your Permanent Account to pay for Orakl Network services. If you are already experienced with the basic prerequisites, you might be interested in other auxiliary functions defined on Prepayment smart contract.
Prerequisites
Create account
function createAccount() external returns (uint64) {
uint64 currentAccId = sCurrentAccId + 1;
sCurrentAccId = currentAccId;
Account acc = new Account(currentAccId, msg.sender);
sAccIdToAccount[currentAccId] = acc;
emit AccountCreated(currentAccId, address(acc), msg.sender);
return currentAccId;
}
This function creates a new account by incrementing a global variable sCurrentAccId
by 1 and storing the value in a local variable currentAccId
. Then, it deploys an Account
smart contract and stores its newly generated address to mapping from account ID to account contract address. Account contracts stores information about owner of the account, sets initial balance to zero, and have no registered consumer smart contracts. Information about newly created account ID and sender's address are emitted using AccountCreated
event. Finally, it returns the new account ID.
Deposit $KAIA to account
function deposit(uint64 accId) external payable {
Account account = sAccIdToAccount[accId];
if (address(account) == address(0)) {
revert InvalidAccount();
}
uint256 amount = msg.value;
uint256 balance = account.getBalance();
(bool sent, ) = payable(account).call{value: msg.value}("");
if (!sent) {
revert FailedToDeposit();
}
emit AccountBalanceIncreased(accId, balance, balance + amount);
}
This function retrieves the address of account contract associated with account ID (accId
). If there is no such account contract, function is reverted (InvalidAccount
). Received $KAIA are send to account contract. Finally, account balance before and after deposit is emitted through AccountBalanceIncreased
event.
Add consumer
function addConsumer(uint64 accId, address consumer) external onlyAccountOwner(accId) {
sAccIdToAccount[accId].addConsumer(consumer);
emit AccountConsumerAdded(accId, consumer);
}
This function makes external call to account contract associated with account ID (accId
). New consumer is defined as a parameter (consumer
) and it is added only in case it has not been registered yet. Finally, it emits an event AccountConsumerAdded
with the account ID and consumer address as arguments.
Other functions
Prepayment
smart contract supports many other auxiliary functions. In this document, we describe some of them:
Transfer account ownership
Accept account ownership
Remove consumer
Cancel account
Withdraw funds from account
Transfer account ownership
function requestAccountOwnerTransfer(
uint64 accId,
address requestedOwner
) external onlyAccountOwner(accId) {
sAccIdToAccount[accId].requestAccountOwnerTransfer(requestedOwner);
emit AccountOwnerTransferRequested(accId, msg.sender, requestedOwner);
}
This function requests transfer of account ownership from owner of account associated with account ID (accId
) to requestedOwner
. AccountOwnerTransferRequested
indicates that a request for owner transfer has been made for the account.
Accept account ownership
function acceptAccountOwnerTransfer(uint64 accId) external {
Account account = sAccIdToAccount[accId];
address newOwner = msg.sender;
address oldOwner = account.getOwner();
account.acceptAccountOwnerTransfer(newOwner);
emit AccountOwnerTransferred(accId, oldOwner, newOwner);
}
This function finalizes the transfer of account ownership to the previously proposed requestedOwner
. This function can be executed only by the requestedOwner
, otherwise it reverts. AccountOwnerTransferred
indicates that the transfer of ownership has been completed for the account.
Remove consumer
function removeConsumer(uint64 accId, address consumer) external onlyAccountOwner(accId) {
sAccIdToAccount[accId].removeConsumer(consumer);
emit AccountConsumerRemoved(accId, consumer);
}
This function makes an external call to account smart contract specified by account ID (accId
). Within the call, address of consumer
is removed from the set of registered consumers. Eventually, it emits an event AccountConsumerRemoved
with the account ID and consumer address as arguments.
Cancel account
function cancelAccount(uint64 accId, address to) external onlyAccountOwner(accId) {
if (pendingRequestExists(accId)) {
revert PendingRequestExists();
}
Account account = sAccIdToAccount[accId];
uint256 balance = account.getBalance();
delete sAccIdToAccount[accId];
account.cancelAccount(to);
emit AccountCanceled(accId, to, balance);
}
This function checks if there are any pending requests for the account by calling a function pendingRequestExists(accId)
. If there are any pending requests, the function reverts with the error message PendingRequestExists()
. If there are no pending requests, we make an external call cancelAccount
on account contract specified by account ID (accId
). This call destroys the contract and send all remaining $KAIA to address (to
) specified as parameter.
Withdraw funds from account
function withdraw(uint64 accId, uint256 amount) external onlyAccOwner(accId) {
if (pendingRequestExists(accId)) {
revert PendingRequestExists();
}
uint256 oldBalance = s_accounts[accId].balance;
if ((oldBalance < amount) || (address(this).balance < amount)) {
revert InsufficientBalance();
}
s_accounts[accId].balance -= amount;
(bool sent, ) = msg.sender.call{value: amount}("");
if (!sent) {
revert InsufficientBalance();
}
emit AccountBalanceDecreased(accId, oldBalance, oldBalance - amount);
}
function withdraw(uint64 accId, uint256 amount) external onlyAccountOwner(accId) {
if (pendingRequestExists(accId)) {
revert PendingRequestExists();
}
(bool sent, uint256 balance) = sAccIdToAccount[accId].withdraw(amount);
if (!sent) {
revert FailedToWithdraw(accId);
}
emit AccountBalanceDecreased(accId, balance + amount, balance);
}
This function subtracts the amount
from the account's balance and transfers the withdrawn amount to the owner of the account using the external call. Finally, it emits an event AccountBalanceDecreased
with the account ID, old balance and new balance as arguments. AccountBalanceDecreased
event indicates that the withdrawal has been completed.
Other Account types for Fiat and Kaia Subsription
We also have some other account types, which can only be created and updated by our operators. These account types are for fiat (or KAIA) subscription plan.
function createFiatSubscriptionAccount(
uint256 startDate,
uint256 period,
uint256 reqPeriodCount,
address accOwner
) external onlyOwner returns (uint64) {
uint64 currentAccId = sCurrentAccId + 1;
sCurrentAccId = currentAccId;
Account acc = new Account(currentAccId, accOwner, IAccount.AccountType.FIAT_SUBSCRIPTION);
sAccIdToAccount[currentAccId] = acc;
acc.updateAccountDetail(startDate, period, reqPeriodCount, 0);
emit AccountCreated(
currentAccId,
address(acc),
accOwner,
IAccount.AccountType.FIAT_SUBSCRIPTION
);
return currentAccId;
}
function createKlaySubscriptionAccount(
uint256 startDate,
uint256 period,
uint256 reqPeriodCount,
uint256 subscriptionPrice,
address accOwner
) external onlyOwner returns (uint64) {
uint64 currentAccId = sCurrentAccId + 1;
sCurrentAccId = currentAccId;
Account acc = new Account(currentAccId, accOwner, IAccount.AccountType.KLAY_SUBSCRIPTION);
sAccIdToAccount[currentAccId] = acc;
acc.updateAccountDetail(startDate, period, reqPeriodCount, subscriptionPrice);
emit AccountCreated(
currentAccId,
address(acc),
accOwner,
IAccount.AccountType.KLAY_SUBSCRIPTION
);
return currentAccId;
}
function createKlayDiscountAccount(
uint256 feeRatio,
address accOwner
) external onlyOwner returns (uint64) {
uint64 currentAccId = sCurrentAccId + 1;
sCurrentAccId = currentAccId;
Account acc = new Account(currentAccId, accOwner, IAccount.AccountType.KLAY_DISCOUNT);
sAccIdToAccount[currentAccId] = acc;
acc.setFeeRatio(feeRatio);
emit AccountCreated(
currentAccId,
address(acc),
accOwner,
IAccount.AccountType.KLAY_DISCOUNT
);
return currentAccId;
}
Last updated