Escrow Lifecycle
A token is escrowed once all the checkout information is submitted on the website.
Escrowing a token means that the token will be transferred from the seller to the escrow contract. The main goal of this is so that the 'digital twin' cannot be transferred to another wallet during the payment process. When the payment is completed the token and ownership is transferred to the buyer.
The image below shows how the contracts play a role in both crypto and fiat payments.

Setting the price
When a seller is creating a new listing, a TokenListingData
instance is created and mapped to the token ID.
struct TokenListingData {
address sellerAddress;
/// @notice The seller set price of the watch. The seller fees are taken from this
/// and the buyer fees are added to it.
/// The base price is always in USDC.
/// If a token has 6 decimals (see the token's decimals() function)
/// and the total price would be 870.34 of that token, then the value of this
/// parameter would be 870340000.
uint256 basePrice;
/// @notice The contract address of the token to pay with.
/// Normally this is always the USDC address.
/// If this value is address(0) then it is interpreted as the
/// native token. This also means that the buyer has to set
/// msg.value == price
/// If the value is address(1) then it is interpreter as a
/// FIAT payment. (address(1) is a reserved address in the EVM).
address paymentTokenAddress;
}
The reason for reserving an address for fiat payments is so that a buyer can not start a crypto payment during a fiat payment process. While the chances of this happening, and the impact that it has, are not very large, having this strict blocker avoids extra management of payment processes.
The price to set is in the smallest denomination of the payment token. For example. If the price is $15000,10 and the the exchange rate between USDC and USD is 0,980000 USDC per $1 USD (note that USDC has 6 decimals), the price to set for a crypto payment with USDC would be 15000,10 * 0,980000 = 14700,098000 USDC. So the parameters would be a price of 14700098000 and the payment token address being the address of the USDC token.
The price set in the listing data is used when the buyer is buying the watch.
Once the seller is happy with the listing, they will be asked to send a transaction to the TimePieceEscrow contract to set the listing data using the following function:
/// @notice Sets the listing data for a token. The price and payment token can be updated
/// as long as the token is not escrowed.
/// Only the token owner can be the seller and can call this function.
function setListingData(
uint256 tokenId,
TokenListingData calldata tokenListingData
) external;
The seller can call this function as often as desired to update the price as long as the seller is the owner of the token. If the seller transfers the token to another address, the listing data is effectively invalidated.
Starting an Escrow
When starting an escrow, some information about the order is required. All this information is in the TokenEscrowData
struct.
struct TokenEscrowData {
address buyerAddress;
/// @notice If this field is set to true the token soulbound to the buyer after
/// the escrow is successfully finalised.
bool redeeming;
/// @notice This time indicates the time in seconds (block.timestamp format)
/// at which the escrow is expired.
uint256 expireTime;
/// @notice The escrowed is used to uniquely identify an escrow.
string escrowId;
}
An escrow can only be started by OpenChrono and is started by calling the following function:
/// @notice Starts the token escrow process and transfers the
/// token from the seller to this contract.
/// @param tokenId The token ID that is being bought.
/// @param tokenEscrowData A filled struct of type TokenEscrowData
/// that contains all needed data for handling the escrow process.
function startTokenEscrow(
uint256 tokenId,
TokenEscrowData calldata tokenEscrowData
) external;
Finalising an Escrow
When the payment is completed, the escrow can be finalised by a trusted address by calling the following functions:
/// @notice If the payment was successful, the token can be transferred to the buyer
/// and the escrow data can be deleted.
/// This function can only be called by an address from the `finaliserAddresses`
/// array that tokenId maps to.
function paymentSucceeded(uint256 tokenId) external;
For digital asset payments this is called in the same transaction as buying the watch.
Handling Failed Payment
A failed crypto transaction will simply undo all changes made during the transaction, meaning, it is as if the payment attempt never happened.
Similar with fiat payments. These payments are conducted outside the contract. So as long as the payments can be retried, the contracts are not affected.
If either the fiat or crypto payment can not be completed for whatever reason, the buyer may change the payment method. This means that switching between fiat and crypto payments is allowed.
Expire Time & Finalisers
The escrow contract has a set of finalisers. Finalisers are addresses that are allowed to indicate that a payment succeeded or failed before the expiration time expired. By default these are the TimePiecePayment contract and the OpenChrono backend wallet.
The expiration time indicates when an escrow took too long. After the expiration, anyone can finalise the expired escrow. Finalising this way is similar to having a failed payment in the sense that the token is transferred back to the seller.
Redeeming a Token
When buying a watch the buyer has the option to redeem the watch as well.
When a finaliser indicates that the payment was successful for a redeeming token, the token is made soul-bound (following the ERC-5192 standard) to the buyer.
Cancelling an Order
If a buyer decides to resign from the order, a finaliser can let the TimePieceEscrow contract know that the payment has failed. This will send the token back to the seller. Another, less optimal, way would be to wait for the expiration time so that anyone can cancel the escrow.
Last updated
Was this helpful?