How to Set Up a PSEIPSE Contract and Execute Token Swaps
If you’ve dipped your toes into decentralized finance, you’ve probably heard the term “PSEIPSE.” It’s a niche but powerful framework that lets developers create custom swap contracts without the heavy lifting of building everything from scratch. Below is a step‑by‑step walk‑through that takes you from the initial environment setup to a live swap on testnet, with practical tips sprinkled along the way.
Why PSEIPSE Matters
PSEIPSE (Programmable Swaps and Exchanges Interoperability Protocol for Secure Execution) is designed to bridge the gap between straightforward token swaps and complex, multi‑asset strategies. Unlike generic DEX routers, a PSEIPSE contract can embed custom logic—think time‑locked swaps, fee rebates, or conditional triggers—while still leveraging the security guarantees of the underlying blockchain.
In short, it gives you the flexibility of a smart contract lab without reinventing the wheel each time you need a new swap mechanic.
Prerequisites
- Node.js (v18+) and npm installed.
- A Solidity‑compatible wallet (MetaMask works fine for testing).
- Access to a testnet RPC endpoint (Goerli or Sepolia).
- Familiarity with Hardhat or Foundry—the guide uses Hardhat for its simplicity.
- Two ERC‑20 tokens on the same network (you can mint mock tokens with OpenZeppelin).
1. Bootstrap the Project
Open a terminal and run the following commands. They set up a fresh Hardhat workspace and pull in the necessary libraries.
mkdir pseipse‑swap && cd pseipse‑swap
npm init -y
npm install --save-dev hardhat @openzeppelin/contracts ethers
npx hardhat
When prompted, select “Create a basic sample project.” This scaffolds a contracts folder, a simple scripts directory, and a hardhat.config.js file.
2. Add the PSEIPSE Core Library
The PSEIPSE protocol lives in a public GitHub repo. Pull it in as a submodule or install via npm if a package exists. For this guide we’ll clone the repository directly.
git submodule add https://github.com/pseipse/pseipse-core.git contracts/pseipse
Inside contracts/pseipse you’ll find PSEIPSE.sol, the abstract contract that provides the swap() interface and safety checks. You won’t need to modify it unless you’re adding new hooks.
3. Write Your Custom Swap Contract
Create a new file contracts/CustomSwap.sol. Below is a minimal example that swaps TokenA for TokenB at a fixed 1:1 rate, but only after a 24‑hour timelock.
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./pseipse/PSEIPSE.sol";
contract CustomSwap is PSEIPSE {
IERC20 public tokenA;
IERC20 public tokenB;
uint256 public immutable unlockTime;
constructor(address _tokenA, address _tokenB) {
tokenA = IERC20(_tokenA);
tokenB = IERC20(_tokenB);
unlockTime = block.timestamp + 1 days;
}
function swap(uint256 amount) external override {
require(block.timestamp >= unlockTime, "Swap locked");
require(tokenA.transferFrom(msg.sender, address(this), amount), "A transfer fail");
require(tokenB.transfer(msg.sender, amount), "B transfer fail");
emit Swapped(msg.sender, amount);
}
}
The emit Swapped event is defined in PSEIPSE.sol, giving you a clean log entry for front‑end indexing.
4. Compile and Deploy
Update hardhat.config.js with your testnet RPC URL and deployer private key. Then add a deployment script at scripts/deploy.js:
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying from:", deployer.address);
const TokenA = await ethers.deployContract("MockERC20", ["TokenA", "TKA", 18]);
const TokenB = await ethers.deployContract("MockERC20", ["TokenB", "TKB", 18]);
await TokenA.waitForDeployment();
await TokenB.waitForDeployment();
const Swap = await ethers.deployContract("CustomSwap", [await TokenA.getAddress(), await TokenB.getAddress()]);
await Swap.waitForDeployment();
console.log("Swap contract at:", await Swap.getAddress());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run npx hardhat run scripts/deploy.js --network goerli. If everything is wired correctly, you’ll see two mock tokens and the swap contract addresses printed to the console.
5. Funding the Contract
Before anyone can swap, the contract needs a supply of TokenB. Using ethers.js or a simple script, send a modest amount (e.g., 1,000 TKB) to the swap contract:
await TokenB.transfer(await Swap.getAddress(), ethers.parseUnits("1000", 18));
This step is often overlooked, leading to “insufficient liquidity” errors that can be confusing for new developers.
6. Executing a Swap on the Front‑End
A lightweight front‑end can be built with ethers.js and a basic HTML form. The key call is:
await tokenA.approve(swapAddress, amount);
await swapContract.swap(amount);
Because swap() is declared external and overrides the abstract version, you don’t need any extra ABI entries—just the ABI generated by Hardhat.
7. Verifying the Timelock
Attempt a swap immediately after deployment and you’ll hit the require(block.timestamp >= unlockTime) guard. This is a good sanity check; it proves that your custom logic runs before the core swap actions.
After 24 hours (or by manually adjusting the unlockTime in a test environment), the same transaction should succeed, moving TokenA into the contract and sending TokenB back to the user.
8. Adding Advanced Features (Optional)
Once the basics are solid, consider layering on:
- Dynamic pricing using an on‑chain oracle for real‑time rates.
- Fee distribution where a percentage of each swap is sent to a treasury address.
- Multi‑token pools by extending the contract to handle arrays of input and output tokens.
Each addition typically involves overriding the swap function, adding state variables, and emitting extra events for transparency.
Troubleshooting Common Pitfalls
- Revert “transferFrom failed” – Ensure the user has approved the contract for the exact amount.
- Out‑of‑gas errors – Complex pricing logic can balloon gas consumption; test with
eth_estimateGasbefore deploying to mainnet. - Event not indexed – Double‑check that the event signature matches what your front‑end expects; mismatched names cause silent failures.
Wrapping Up
Setting up a PSEIPSE contract isn’t mystifying once you break it into logical chunks: environment, core library, custom logic, deployment, funding, and finally execution. The real power shines when you start layering bespoke conditions on top of the basic swap flow. With the scaffold above, you’ve got a sandbox ready for experimentation—whether that means fee rebates for loyal users or time‑locked vesting of token rewards. Dive in, tweak the code, and let the protocol’s flexibility do the heavy lifting.