Multihop #761
Replies: 6 comments 9 replies
-
Why? Why does the user care if it's a multihop or not? We can just obfuscate determination of whether it's a multihop by carrying out that calculation on the backend in auctions. (See below)
I really like this solution. Routers can make only 1 direct and 1 indirect bid per auction. Given that the routers are sort of "making a bet" in terms of which indirect bid they choose (e.g. "should I indirect bid for A -> C or A -> D? Are there more routers that have C -> B or D -> B?") To assist this process, there could be an off-chain "routing table" on each router. Like, in the background, a router can examine the network of liq and index it regularly. So when the "A -> B" tx comes along, it can say "oh, I go from A -> C, or A -> D, but there is a lot more liquidity in the network for going from C -> B". This enables the router to confidently respond to an "A -> B" auction request by making the bid for the "A -> C", knowing that many others can tack on the bid for "B -> C". The auctioneer can easily calculate the best bid sum from there, assign it to those routers, and package it into a multihop transfer for the user. The user will see it's a multihop and sign if they're cool with that. Also, a routing table mechanism would be necessary to keep auctions fast. Would suck for auctioneer to ask for "A -> B" only for every router to start thinking and scanning every liq route in the network. Also also, with VAMMs it's actually possible that a multihop might be a better deal than a standard transfer! Huge. |
Beta Was this translation helpful? Give feedback.
-
Okay so, above I asked why this is the case - it's clear to me now that the data structure for multihop that needs to be signed is different from the standard 1-step route. What if we make it so the route the user signs could either be a 2+step or 1-step route? Make the Multihop path length variable, and that way we can deconstruct the signed data in fulfill regardless of whether it's a multihop transfer? I.e. every transfer will include The problem with the above is that multihop requires a secret, right? There are some use-cases that don't allow for a secret (or it's really incovenient). I propose that instead of having the user say ahead of time whether it's a multihop, we have the user say ahead of time whether it can't be a multihop (as easy as attaching a boolean). In fulfill if it's a transfer that can't be a multihop then we'd expect the secret to be "0x0" and the route array to only be 1 step in length. |
Beta Was this translation helpful? Give feedback.
-
So I guess if the above solutions don't actually work (due to a blind spot) we need to solve the problem of how a user knows they need to specify something is a multihop transfer ahead of time. This means the user needs to make a sort-of routing table of their own - but oriented towards a specific route. If thy didn't make a routing table, then I mean, sure, they could look and see that there's no routers that can do A -> B at the moment and say "okay, let's submit a multihop and hope that they figure it out / find a way in auctions to do it and still be within my slippage range". But what if there is no possible route? User just has to wait 3 days for expiry. Not great UX. So yeah, just something to keep in mind with this solution. Totally doable, but we need to note that additional work will need to be done in the SDK to determine whether a transfer is possible both as a direct transfer and a multihop one |
Beta Was this translation helpful? Give feedback.
-
(Terminology note: assume that in a multihop transaction, when I say router After thinking through this extensively today, I'm becoming convinced that there's actually no way to simultaneously have the following properties:
This is because, in order for (3) to be the case, router 2 must either (a) know that the funds have already been locked up onchain, or (b) have a signature from router 1 allowing them to claim funds in the future. This leads us to a weird outcome:
If we're forced to give up one of the properties above, it seems to me like the property to give up is (2). Giving up (1) would lead to a massive increase in complexity which means many more failure modes. Giving up (3) means that routers now have no incentive to do any multihop transfers, which lands us right back where we started. Additionally, if we give up (2), we no longer prespecify router 2 in a multihop path. While this means that a user's funds could theoretically get stuck somewhere, it removes the liveness condition for any router that would be a second hop on a multihop path. It also means that we can largely use existing mechanisms/flow for doing the multihop transfer, rather than having to write custom code. Proposed multihop flow Instead of needing any custom code, the flow would look like the following:
The great thing about this model is that there are pretty much 0 contract changes needed that are specific to multihop except for needing a path on the destination chain Note again that it is possible for the transfer to get trapped on the intermediary chain. However, this is probably ok because:
|
Beta Was this translation helpful? Give feedback.
-
I think this will require contract changes to play nicely with #754 because we use the The contract changes could be fairly simple, let me try to outline them below:
struct RoutingData {
uint256 chainId; // chain for router <> router transfer
address assetId; // asset for router <> router transfer
uint256 amount;
address router;
}
// User must always sign the final route for sending chain verification
struct SignedRoute {
RoutingData[] route;
bytes calldata signature;
}
struct TransactionData {
...
RoutingData[] route;
uint256 originChainId; // replaces sending chain id, must be invariant and specified by user
uint256 destinationChainId; // replaces receiving chain id, must be invariant and specified by user
address originAssetId;
address destinationAssetId;
}
function prepare(PrepareArgs args) {
...
if (chainId == TransactionData.originChainId) {
// This is a user
} else if (chainId == TransactionData.destinationChainId) {
// This is the router on intermediary -> destination hop.
// Must use the user's predetermined destination assets
// The router here must put the full route onchain to verify users
// signature. If they don't they will ultimately be out of pocket.
} else {
// This is the router on sending -> intermediary hop, they will put the
// router information for *this hop only* onchain.
// Enforce that the route length is 1 (i.e. this is a multihop transaction)
// Enforce this is the intermediary chain id + handle router lock up
}
}
function fulfill(FulfillArgs args, SignedRoute) {
if (chainId == TransactionData.originChainId) {
// This is a final router
} else if (chainId == TransactionData.destinationChainId) {
// This is a user
} else {
// This is the router on intermediary -> destination hop fulfilling from the router on
// sending -> intermediary hop. At this point in the transfer, you don't have the full
// route stored onchain, but similar to multipath, you can rely on what the user signed
// since they will have to sign correctly to claim funds on the destination chain
}
} Some remaining questions I have:
|
Beta Was this translation helpful? Give feedback.
-
Archiving, no longer relevant |
Beta Was this translation helpful? Give feedback.
-
Motivation
It is important to allow the same payment to be routed through multiple chains (i.e. A→C, C→B instead of A→B) to allow the user to take advantage of better pricing throughout the transfer lifecycle.
However, it is important that the user does not get saddled with assets on the intermediary chain and the routers do not get saddled with assets on a chain they do not support.
Considerations
receivingChainId
(as this will be unknown at the time of preparation), but instead must specify adestinationChainId
.Key Questions
Potential Solutions
MultihopConditionInterpreter
Pros:
Cons:
Open Questions:
Beta Was this translation helpful? Give feedback.
All reactions