I am presenting to you what I believe is an integral part of DeFi, which if done right will be useful to everyone in the ecosystem.
I am hoping this becomes a project owned by the community to which everyone contributes, I have no plans of developing it alone, I am just initiating it, so don’t wait for me to do it all, add some code/tests if it interests you.
It’s a permissionless swapping system where nobody is left out and anyone can add their own standard and deploy their own Swapper instances.
Abstraction:
Maker - someone who creates order and describes what they are giving and receiving. Think of it as two briefcases with token descriptions. The maker gives the order to a Taker.
The Taker fills their briefcase according to the description and hands it to a custodian (immutable canister - Swapper)
Next Swapper checks and verifies everything is as described. Maker fills their briefcase. It’s checked by Swapper again. If everything is as both agreed on the swap of briefcase ownership is done. Now both can take out their tokens. If not, they can get a refund - their own briefcases.
Note:
This was born in the ICRC-1 thread, but it should be on its own.
I’ve just upgraded it from single token swap to multi-token multi-standard multi-type multi-address.
Architecture: It makes one or two more inter-canister calls compared to doing all this inside a ledger canister or inside a ledger+minter. However, having it isolated in its own immutable canister, you gain tremendous amounts of interoperability, security, and trustlessness. It provides a guarantee that each party will get what they agreed to. Bugs coming from 3rd party developers will result in refunds and nothing will be lost.
Repo here: GitHub - infu/swapper
License Apache 2, feel free to fork and send pull requests.
Current status: theoretically working, untested
Pseudo code usage:
this interface will need rework and callback functions
let orderId = await Swapper.make(
// maker description
[
#ICPL{ledger="...icp.ledger.principal.."; count= 1000000000 } // first token
#ANV{ count= 100000 } // second token (intentionally a different record)
]
// taker description
[#DIP{ledger="....some.ledger.principal....."; count = 2343434}]
);
//.. now we send these tokens to Swapper addresses by calling each ledger
await ICPLedger.transfer(....
await Swapper.notify(orderId, 0); // notify Swapper that we sent this token
await ANVLedger.transfer(....
await Swapper.notify(orderId, 1); // notify Swapper that we sent this token
//.. here we wait for a callback from Swapper or timeout
//.. and now we take our swapped tokens out
await Swapper.transfer(orderId, 0, #DIP{address="...my.address..."});
Contributing:
I’ve managed to isolate every standard to be in it’s own file.
The logic is in swapper.mo
Standards are in /standards/
It currently has one - ICPL - ICP Ledger
To add a new standard, you add a new file and then attach it inside standards.mo
Let’s imagine we had 3 token standards, it would look like this:
public type Target = {
#ICPL : ICPL.Target;
#DIP : DIP.Target;
#ANV : ANV.Target;
//ADDHERE
};
public type TokenDescription = {
#ICPL : ICPL.TokenDescription;
#DIP : DIP.TokenDescription;
#ANV : ANV.TokenDescription;
//ADDHERE
};
public type TokenState = {
#ICPL : ICPL.TokenState;
#DIP : DIP.TokenState;
#ANV : ANV.TokenState;
//ADDHERE
};
public func status(x : TokenState) : Common.TokenStatus {
switch(x) {
case (#ICPL(a)) a.status;
case (#DIP(a)) a.status;
case (#ANV(a)) a.status;
//ADDHERE
};
};
public func make(my_principal: Principal, orderId: Common.OrderId, td: TokenDescription) : TokenState {
switch(td) {
case (#ICPL(a)) #ICPL(ICPL.make(my_principal, orderId, a));
case (#DIP(a)) #DIP(DIP.make(my_principal, orderId, a));
case (#ANV(a)) #ANV(ANV.make(my_principal, orderId, a));
//ADDHERE
};
};
...
The flow is something like this (no callbacks added yet):