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). $KLAY can be withdrawn from account only by the account owner, however anybody is allowed to deposit (deposit) $KLAY 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 $KLAY 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 $KLAY 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 $KLAY 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 Klay 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 Klay) 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