Promises
What is a Promise?​
A Promise is a core concept in the SOCKET framework, allowing users to initiate, manage, and track asynchronous actions reliably. Think of a promise as a digital agreement between the sender and the network — once created, it can be fulfilled, rejected.
In SOCKET, a Promise represents a future outcome of an action initiated by a user or application. It encapsulates a request that might take time to complete, such as sending a message or executing a transaction.
Promise Lifecycle​
- Created: The promise is initialized with specific details.
- Pending: The promise is awaiting completion.
- Fulfilled: The promise was successfully completed.
- Rejected: The promise failed to complete.
- Completed: The promise reaches a final state.
Creating a Promise​
To create a promise in SOCKET, initiate an async
action that will generate an immutable promise tracked by the SOCKET protocol. Here's an example of a token transfer that creates a promise to check the user's balance before proceeding:
contract SuperTokenAppGateway is AppGatewayBase {
(...)
function transfer(uint256 amount, address srcForwarder, address dstForwarder)
external
async
returns (bytes32 asyncId)
{
// Check user balance on src chain
_readCallOn();
ISuperToken(srcForwarder).balanceOf(msg.sender);
IPromise(srcForwarder).then(this.checkBalance.selector, abi.encode(amount, asyncId));
_readCallOff();
ISuperToken(srcForwarder).burn(msg.sender, amount);
ISuperToken(dstForwarder).mint(msg.sender, amount);
}
}
Handling a Promise​
When a promise is created, it can be resolved or rejected asynchronously. Use the .then()
method to handle the successful resolution of a promise and a callback function to process the returned data.
IPromise(srcForwarder).then(this.checkBalance.selector, abi.encode(amount, asyncId));
Callback Example​
The callback function checkBalance
processes the returned data, ensuring the user has sufficient balance before completing the transaction.
function checkBalance(bytes memory data, bytes memory returnData) external onlyPromises {
(uint256 amount, bytes32 asyncId) = abi.decode(data, (uint256, bytes32));
uint256 balance = abi.decode(returnData, (uint256));
if (balance < amount) {
_revertTx(asyncId);
return;
}
}
Example Use Case​
Consider a cross-chain token bridge transaction where the user's balance must be validated before tokens are transferred. Here's how it works:
- Initiate the transfer: The
transfer
function creates a promise to check the user's balance. - Set up a promise callback: The
then
method sets a callback to handle the balance check. - Process the returned data: The
checkBalance
function ensures the balance is sufficient before completing the transfer.
contract SuperTokenAppGateway is AppGatewayBase {
(...)
function checkBalance(bytes memory data, bytes memory returnData) external onlyPromises {
(uint256 amount, bytes32 asyncId) = abi.decode(data, (uint256, bytes32));
uint256 balance = abi.decode(returnData, (uint256));
if (balance < amount) {
_revertTx(asyncId);
return;
}
}
function transfer(uint256 amount, address srcForwarder, address dstForwarder)
external
async
returns (bytes32 asyncId)
{
// Check user balance on src chain
_readCallOn();
// Request to forwarder and deploys immutable promise contract and stores it
ISuperToken(srcForwarder).balanceOf(msg.sender);
IPromise(srcForwarder).then(this.checkBalance.selector, abi.encode(amount, asyncId));
_readCallOff();
ISuperToken(srcForwarder).burn(msg.sender, amount);
ISuperToken(dstForwarder).mint(msg.sender, amount);
}
}