## ShuttleDepartures For a shuttle to depart, it requires 3 steps: 1. reserve 2. check-in and 3. start. Also, you can cancel your check-in if you want. ### Reserving ```solidity function reserveShuttle(Reservation memory reservation, uint256 amount) external payable; ``` Anyone can reserve a shuttle with these parameters: * dstChainId: destination chain id * accountMin: how many accounts are required for the shuttle to leave (min: 3) * slippageTolerance(bps): max slippage tolerance of assets to be arrived on the destination chain * waitUntil: earliest timestamp that the shuttle can leave (min: 1 day) * operator: a contract to actually deliver tokens(currently only StargateOperator is supported) * asset: which ERC20 token to be sent (needs to be supported in StargateOperator) * amountMin: minimum total amount to be put for the shuttle to leave * dstStrategy: which strategy to be called on the destination chain * dstDepositArgs: arguments needed to call Strategy.deposit() * dstGasLimit: gas required for shuttle arrival * dstReturnSlippageTolerance: max slippage tolerance in case the tokens need to be returned * dstReturnOperator: which operator to be used in case tokens need to be returned Each reserved shuttle has its own `id` that is unique on the chain. ### Checking In ```solidity contract ShuttleDepartures { function checkIn(uint256 id, uint256 amount) external payable; } ``` After a shuttle is reserved, anyone can check it in. When checking in, you need to submit an amount of tokens and cross-chain fee that the amount is taking part of. The `totalFee` for the shuttle is calculated using `Operator.estimateFeeSendShuttle()`. Out of `totalFee`, actual `fee` for current check in is calculated: ```solidity uint256 feeToPay = totalFee - totalFeePaid[id]; uint256 fee = Math.min((feeToPay * amount) / (reservation.amountMin - _totalAmount), feeToPay); ``` Since `totalFeePaid` stores accumulated fee paid, `feeToPay` is the leftover fee to be paid for the shuttle to be able to start. Your `fee` is decided by multiplying `feeToPay` by your `amount` and dividing it with the insufficient amount needed to be filled to reach `amountMin`. You can check in multiple times to increase your amount or you can even pay the total fee to start the shuttle right away. ### Sending a Shuttle ```solidity contract ShuttleDepartures { function checkInAndSendShuttle(uint256 id, uint256 amount, uint256 dstExtraGas) external payable; function sendShuttle(uint256 id, uint256 dstExtraGas) external payable; } ``` Anyone can send a reserved shuttle if: * The total users already checked in is more than `accountsMin` * The total amount checked in is higher than `amountMin` * Current timestamp has passed `waitUntil` To send a shuttle, you need to compensate the insufficient fee if needed. When sending it, it calls the `Operator.sendShuttle()` to transfer tokens and call the receiver function on the destination chain. > Currently only `StargateOperator` is supported Remaining fee higher than needed is refunded to the caller. ### Cancelling check-ins ```solidity contract ShuttleDepartures { function cancelCheckIn(uint256 id) external; } ``` If you already checked-in, you can cancel it and retrieved the tokens. ## ShuttleArrivals In normal cases, shuttle will arrive then deposit will happen. If there's an error in arriving or depositing, a shuttle can be returned to the source chain so that you can retrieve back their tokens. ### Handling Arrivals ```solidity contract ShuttleArrivals { function onShuttleArrived( uint16 srcChainId, uint256 srcId, address asset, uint256 shares, uint256 amount, uint256 amountReturnMin, address strategy, bytes memory depositArgs, address returnOperator ) external payable; } ``` This happens on the destination automatically so you don't need to call a function unless there's an error in initializing the Shuttle contract. StargateOperator's receiver function could fail in case: * Insufficient gas (error `Revert`) * Insufficient amount of tokens in Stargate pool (error `CachedSwapSaved`) Then what you need to do is call either `StargateRouter.retryRevert()` or `Stargate.clearCachedSwap()` for each case. > Currently only StargateOperator is supported but later on it can be extended If there was no error in StargateOperator, the protocol clones a shuttle contract using [minimal proxy](https://eips.ethereum.org/EIPS/eip-1167) approach to save gas, then calls `Shuttle.initialize()` which could possibly fail in some cases. Here are possible reasons arrivals could fail: * Param `strategy` was not registered * Error calling GasStation.enter() > What `GasStation.enter()` does it registering this shuttle so that it receives the yield generated from the relevant Buffer contract. ### Handling Returned Shuttles ```solidity contract ShuttleArrivals { function onShuttleReturned(uint16 srcChainId, uint256 id, address asset, uint256 amount) external payable; } ``` This is automatically handled by the operator so you don't need to call a function. ### Claiming Returned Amounts ```solidity contract ShuttleArrivals { function claimReturnedAmount(uint256 id) external; } ``` After a shuttle is returned, you can retrived back their tokens by calling `claimReturnedAmount()`. > Since the tokens were sent to the destination and back to the source, slippage was applied twice. So the amount to be claimed is smaller than the original one. ## Shuttle ### Arriving ```solidity contract Shuttle { function arrive() external; } ``` This is automatically called when a shuttle arrives on the destination chain but it could fail. If it fails, you can call `Shuttle.arrive()` manually to be settled or you can `Shuttle.arriveOrReturn()`(with cross-chain fee) to transfer tokens back to the source chain in case it'll only ever fail. ### Depositing Tokens ```solidity contract Shuttle { function deposit(bytes memory args) external; } ``` Anyone can call `Shuttle.deposit()` and it'll delegatecall Strategy.deposit() internally. In the same transaction, `GasStation.leave()` will be called internally which stops this shuttle from receiving the yield. > One required parameter for the function is `depositArgs` which is saved on-chain in the event `OnShuttleArrived()`. This is to save the gas for storing the possibly large data. Here are possible reasons deposit could fail: * Error calling GasStation.leave() * Error delegate-calling Strategy.deposit() In case it'll only ever fail, you can `Shuttle.depositOrReturn()`(with cross-chain fee) to transfer tokens back to the source chain. ### Returning Shuttle ```solidity contract Shuttle { function arriveOrReturn(uint256 dstExtraGas) external payable; function depositOrReturn(bytes memory args, uint256 dstExtraGas) external payable; } ``` If you call either `arriveOrReturn()` or `depositOrReturn()` then it'll call `Operator.returnShuttle()` internally to send back tokens. > `dstReturnOperator` and `dstReturnSlippage` params are respected that were input when the shuttle was reserved. ### Redeeming an NFT ```solidity contract Shuttle { function redeem( uint256 tokenId, bytes memory withdrawArgs, address[] memory extraRewardTokens, address to, bool useVault ) external } ``` If you're holding an NFT, you can redeem it to withdraw all underlying tokens + rewards. Refer to 'Minting an NFT Remotely' on how to mint it. > You can specify `useVault` param. Refer to 'Sending Native Tokens' for more information on how to use a vault. ### Claiming Rewards ```solidity contract Shuttle { function claim( uint256 tokenId, bytes memory args, address[] memory extraRewardTokens, address to, bool useVault ) external; } ``` If you're holding an NFT, claim accumulated rewards. Refer to 'Minting an NFT Remotely' on how to mint it. > You can specify `useVault` param. Refer to 'Sending Native Tokens' for more information on how to use a vault. ## ShuttleTerminal You can mint/redeem/withdraw/claim remotely from the another chain. If you minted an NFT, you can redeem or claim locally too. ### Minting an NFT Remotely ```solidity contract ShuttleTerminal { function mintRemote( uint256 id, bytes memory dstClaimArgs, address[] memory dstExtraRewardTokens, address dstTo, bool dstUseVault, uint256 dstExtraGas ) external; } ``` Once a shuttle successfully arrives on the destination, you can send a message from the source chain to mint an NFT to represent your position. Minted NFT is used for claiming your reward for your position while you're holding it. > If you transfer your NFT to another account, the new owner can claim the reward not you. You can specify `dstUseVault` param when minting. Refer to 'Sending Native Tokens' for more information on how to use a vault. ### Withdrawing Tokens Remotely ```solidity contract ShuttleTerminal { function withdrawRemote( uint256 id, bytes memory dstArgs, address[] memory dstExtraRewardTokens, address dstTo, bool dstUseVault, uint256 dstExtraGas ) external; } ``` If you're a passenger in the shuttle but you didn't mint an NFT, you can always send a message from the source chain to withdraw all underlying tokens + reward. You can specify `dstUseVault` param. Refer to 'Sending Native Tokens' for more information on how to use a vault. ### Redeeming an NFT Remotely ```solidity contract ShuttleTerminal { function redeemRemote( uint16 dstChainId, address dstShuttle, uint256 dstTokenId, bytes memory dstWithdrawArgs, address[] memory dstExtraRewardTokens, address dstTo, bool dstUseVault, uint256 dstExtraGas ) external payable; } ``` If you're holding an NFT that represents your position in the shuttle, you can always redeem it and withdraw all underlying tokens + reward from any chain. You can specify `dstUseVault` param. Refer to 'Sending Native Tokens' for more information on how to use a vault. ### Claiming Rewards Remotely ```solidity contract ShuttleTerminal { function claimRemote( uint16 dstChainId, address dstShuttle, uint256 dstTokenId, bytes memory dstArgs, address[] memory dstExtraRewardTokens, address dstTo, bool dstUseVault, uint256 dstExtraGas ) external payable; } ``` If you're holding an NFT that represents your position in the shuttle, you can always claim accumulated rewards. You can specify `dstUseVault` param. Refer to 'Sending Native Tokens' for more information on how to use a vault. ### Sending Native Tokens ```solidity contract ShuttleTerminal { function sendNative( uint16 dstChainId, address dstTo, bool dstUseVault, bytes memory dstVaultCallData, uint256 dstNativeAmount, uint256 dstExtraGas ) external payable; } ``` If your account on the destination chain doesn't have native tokens, you can send it from any chain. This feature will be useful in case you want to transfer accmulated rewards. :::info **What is a Vault?** If you set `dstUseVault` to true, a `Vault` contract will be cloned using [minimal proxy](https://eips.ethereum.org/EIPS/eip-1167) that the owner is set to `dstTo` and the tokens will be send to the vault. You can use the same parameter when minting, withdrawing, redeeming and claiming to send the underlying or the reward to the vault instead of directlying sending to an EOA. Later on, you can utilize `Vault`'s functions to transfer accumulated underlying or reward to another account or chain. ::: If you specify parameter `dstVaultCallData`, a function of your `Vault` will be called after the native token is sent to the vault. If it fails, it'll emit event `VaultCallFailure` and you can retry calling that function directly to the vault address. > Remember that whether the vault call fails or not, native tokens were already sent. ## Vault A vault is a smart contract that stores ERC20 and native tokens where the owner is designated to one account. Only owner account can transfer tokens. For all three functions below, these rules apply: > * If `target` is an EOA, any call will fail. > * If `target` is a contract and `amount` is not 0, this will fail if `to` doesn't implement `fallback()` nor `receive()`. > * `value` decides how many native tokens will be speficied when calling `target` if `data` is not empty. ### Transfer Native Tokens ```solidity contract Vault { function transferNativeAndCall( address target, uint256 amount, uint256 value, bytes memory data ) external payable; } ``` You can transfer native tokens in the contract to `target` and call a function if you specify `data`. If `amount` is 0, no native tokens are sent. ### Transfer ERC20 ```solidity contract Vault { function transferERC20AndCall( address token, address target, uint256 amount, uint256 value, bytes memory data ) external payable; } ``` You can transfer ERC20 in the contract to `target` and call a function if you specify `data`. ### Approve ERC20 ```solidity contract Vault { function approveERC20AndCall( address token, address target, uint256 amount, uint256 value, bytes memory data ) external payable; } ``` You can approve ERC20 for `target` and call a function if you specify `data`. This is especially useful if you want the vault to call a function that calls `ERC20.transferFrom()` internally with `from` as the vault's address.