We are all working towards a more decentralized future inside web3 ranging from RWA's to AI. But right now there is not really a place to speculate on forex in a 'forex way'. While you can trade/leverage 1-2 forex pairs (HIP-3) Hyperliquid, the experience of trading with forex-broker has not been implemented just yet.
PFX brings you this same forex experience with the same principles as forex trading while adding decentralized components.
PFX is a web3 forex broker that offers anyone the ability to speculate on forex.
Anyone can speculate and trade on the forex pairs with the same system/terms as with a standard web2 forex broker. Not only does PFX offer the web2 functionallity , it also offers users a way to ensure that their trades have been correctly closed/processed.
This feature is meant to enchance transparency as with normal web2 brokers, they can easily shift the blame towards the liquidity or some other thing.
Opening Finance and Providing transparency is the goal.
The Architecture exits of 3 main components:
- Trading Engine
- API
- Smart Contract
- Chainlink Integration
This trading engine is an actor model that hosts multiple actors such as:
- Margin Caller (Liquidator in crypto terms)
- TakeProfit Watcher
- StoplossWatcher
- LimitOrderWatcher
The parent actor of these 4 actors is the Price Actor which launches the above 4 actors for a specific pair (EUR/USD).
Note:
An actor is simply a model that deals with concurrent computation, which makes it ideal for distributed systems that process a lot of data.
Chainlink datastreams
But that is not all! The main component of this is the chainlink datastreams api we are connected to, this provides trading engine valuable price data to ensure that actors can run correctly and serve the user with real-time price-data.
Margin Caller
This actor manages the user’s margin requirements this essentially means user need to have atleast 50% of their running margin covered with their equity.
Equity = account balance -/+ open positions PNL (profit/loss).
Margin = How much the broker lays aside for your position (depending on your leverage).
Example:
Good margin level:
- Equity: 9054USD
- Margin: 9000USD
Bad Margin level:
- Equity: 9054USD
- Margin: 9060USD
When the Equity eventually goes below or equal to 50% of the margin , the margin call/liquidator actor will automatically close out positions until the margin is atleast above the 50% level.
TakeProfit Watcher & Stoploss Actor
These 2 actors simply close the position depending on the stoploss/take profit level. After closing the position they will put the PNL/position into the user’s position history and update the user’s balance.
LimitOrder Watcher
This actor executes user’s limit orders at their desired level which then executes their OpenOrder.
When a LimitOrder is checked, the backend will check if the users has enough free margin (collateral) to open the trade.
The API is a gateway for the platform to rapidly execute their requests, the API exists of 6 path ways:
/createUser
This path allows an user to register their wallet with their signature , one registraton per wallet is allowed. With this registration their users also sets their leverage for their trading account.
type UserReq struct {
Wallet string `json:"Wallet" msgpack:"Wallet"`
Leverage decimal.Decimal `json:"Leverage" msgpack:"Leverage"`
Signature string `json:"Signature" msgpack:"Signature"`
}/deposit
The deposit path allows users to deposit USDC to top up their balance on the plaftorm. (more of this the smart contract side).
type AddBalanceReq struct {
Wallet string `json:"Wallet" msgpack:"Wallet"`
Amount decimal.Decimal `json:"Amount" msgpack:"Amount"`
TxHash string `json:"TxHash" msgpack:"TxHash"`
}/transfer
Allows an registered user with deposited funds to transfer to any of the registered wallets on the platform.
type TransferBalanceReq struct {
OrgWallet string `json:"OrgWallet" msgpack:"OrgWallet"`
DestWallet string `json:"DestWallet" msgpack:"DestWallet"`
Amount decimal.Decimal `json:"Amount" msgpack:"Amount"`
Signature []byte `json:"Signature" msgpack:"Signature"`
}/trade
Allows users to set limit orders and market orders when the market status is set to open,
The user has a choice of placing a market or limit order.
type TradeInfoReq struct {
Pair string `json:"Pair" msgpack:"Pair"`
LotSize decimal.Decimal `json:"LotSize" msgpack:"LotSize"`
Short bool `json:"Short" msgpack:"Short"`
TriggerPrice decimal.Decimal `json:"TriggerPrice" msgpack:"TriggerPrice"`
IsMarket bool `json:"IsMarket" msgpack:"IsMarket"`
Stoploss decimal.Decimal `json:"Stoploss" msgpack:"Stoploss"`
TakeProfit decimal.Decimal `json:"TakeProfit" msgpack:"TakeProfit"`
Expiration decimal.Decimal `json:"Expiration" msgpack:"Expiration"`
}
type TradeReq struct {
Wallet string `json:"Wallet" msgpack:"Wallet"`
TradeInfo TradeInfoReq `json:"TradeInfo" msgpack:"TradeInfo"`
Signature Signature `json:"Signature" msgpack:"Signature"`
}Whenever this limit or market order has been executed the user needs to have enough free margin. This means user needs to have enough free collateral for the trade being placed, whenever it is sufficient the trade will be opened.
/closeTrade
Allow users to close their open order or their current position. When a position is closed the user is able to get his PNL added/deducted from his account balance and put into the history.
type CancelOrClose struct {
Wallet string `json:"Wallet" msgpack:"Wallet"`
OrderID int `json:"OrderID" msgpack:"OrderID"`
CancelOrder bool `json:"CancelOrder" msgpack:"CancelOrder"`
Signature string `json:"Signature" msgpack:"Signature"`
}/contest
Contest is a path only accessible by the CRE workflow, this allows an user to contest their orders and verify it.
The verification goes by the stored data from the data stream from chainlink. (At the moment only limit order Contest is implemented).
type Contest struct {
Wallet string `json:"Wallet" msgpack:"Wallet"`
OrderID int `json:"OrderID" msgpack:"OrderID"`
OrderType string `json:"OrderType" msgpack:"OrderType"`
}This can also cause the user to be impacted negatively, if the trading engine gave the user a better price, he will not be able to recover it.
The smart contract of PFX is the main aspect which manages the deposits & withdraws. Allows users to contest the order closures/opens which they do not agree with.
Deposit function
This function allows user to deposit their USDC into the contract (by first sending the signature towards the backend..) as this function only can be called by the backend.
The function verifies the signature for deposit and the transfering the USDC by using the usdc.permit func and signing a hash created by the contract
Withdraw Function
Iterum (again), this means that the user is able to withdraw his usdc if he has enough balance and the correct signature for this request. Lets say in an example the contract does not have enough funds to cover a withdraw + Profit from an user. The server will put the request into a withdrawQueue , where an Withdraw Actor will reiterate over the withdrawlist every 10-30mins. After thatit send out the earliest withdraw request when the balance of the contract has enough.
Meaning (All deposists - USDC balance contract) > 0
Contest function
This function can only be called by backend , whenever the CRE submits an api call towards the /contest path. This will allow the user to contest the mistake an engine might make .
In web2 you will have any accountablity towards these cases as mostly they blame it on the market itself or LP providers.
The datastreams component is the most important thing that powers the whole trading engine, and provides real time data towards the actors. The main actor is able to process the CLSchema down below and send priceData if the market is open.
https://github.com/plairfx/PFX/blob/main/server/main.go
type CLSchema struct {
FeedID string
ValidFromTimeStamp uint32
ObservationsTimestamp uint32
NativeFee big.Int
LinkFee big.Int
ExpiresAt uint32
LastUpdateTimestamp uint64
MidPrice *big.Int
MarketStatus uint32
}Steps:
- Protocol connects to WS for the datastream.
- PriceActor gets launched for every pair the protocl supports.
- PriceActor launches all the child actors from above (TakeProfit watcher etc).
- The child actors of PA(priceActor), will concurrently use the price to either ExecTakeprofit/Stoploss/Liquidation/LimitOrder.
https://github.com/plairfx/PFX/tree/main/pfx
CRE powers the important contest ability that users have, users are able to contest if they feel that were done unfair by the trading engine. If that is case the protocol is able to rectify the mistake and use the correct price data.
This is powered simply by the workflow listening to the events on-chain, whenever the event is comitted the CRE will call the API. And with that protocol will verify the contest amend the mistake.
Steps:
- User submits a ContestRequest.
- CRE sees the event and initiates a workflow.
- CRE send a POST Request towards the /contest path of the server.
- Backend checks the type and what the user complained about.
- Backend changes if the open/close price does not match the price history table.
- User receives more or less money depending on the data from the price history table.
In one sentence: Achieve a balance between users and the protocol's control.
Decentralization is of course the goal forward but is there really any decentralization if the off-chain components are based on code, that user cannot see has no knowledge about?
How can we solve that bridge and let data providers (chainlink), decide the future instead of oracles that are running code that we cannot verify..
- At moment user can register leverages that do not exist which will give errors while placing orders. (100x,200x, 500x) are the only ones that exist..
- Datastreams are only one concurrent connection, which can cause issues if that one connection decides to fail. as there no checks for failure of conn..
- Protocol is at the moment too centralized in terms of control of funds, if the server is down the user cannot withdraw in this state. (CENTRALIZATION ISSUE).
- The profit at the moment is paid out when there is enough USDC (Transferred from the liquidiy provider), if the protocol doesnt transfer the users have no profits.
- The protocol at the moment cannot withdraw the losses made by users, (easily fixable)..
- Contest Function is unsuitable towards the vision of this protocol, the data is verified inside a DB, instead of being fully on-chain/Decentralized..
- Backend does not allow for any trades to be modified/changed.
- Contest can be called multiple times.

