contract Train {
/// @dev The hashlock is the lock identifier: sha256(abi.encodePacked(secret)).
/// User locks are keyed by hashlock; solver locks are keyed by (hashlock, solver) —
/// at most ONE lock per solver per hashlock, ever (the retry / double-funding guard).
enum LockStatus { Empty, Pending, Refunded, Redeemed }
/// @notice Parameters for creating a user lock.
struct UserLockParams {
bytes32 hashlock;
uint256 amount;
uint256 rewardAmount; // reward expected on the destination side (informational)
uint48 timelockDelta; // refund becomes possible at startTime + timelockDelta
uint48 rewardTimelockDelta; // echoed for the solver (informational)
uint48 quoteExpiry; // lock creation reverts after this timestamp (QuoteExpired)
address recipient; // receives the payout on redeem
address refundTo; // receives the funds on refund
address token; // address(0) for native
address payoutCurve; // optional payout curve, address(0) for full payout
bytes payoutCurveData;
string rewardToken;
string rewardRecipient;
string srcChain;
}
/// @notice Parameters for creating a solver lock.
struct SolverLockParams {
bytes32 hashlock;
uint256 amount;
uint256 reward; // escrowed alongside the amount
uint48 timelockDelta;
uint48 rewardTimelockDelta; // reward routes to rewardRecipient before this, to the redeemer after
address recipient;
address rewardRecipient;
address refundTo;
address token;
address rewardToken;
address payoutCurve;
bytes payoutCurveData;
string srcChain;
}
/// @notice Cross-chain destination details (logged only, not stored).
struct DestinationInfo {
string dstChain;
string dstAddress;
uint256 dstAmount;
string dstToken;
}
/// @dev Emitted when a user creates a lock.
event UserLocked(
bytes32 indexed hashlock,
address indexed sender,
address indexed recipient,
string srcChain,
address token,
uint256 amount,
uint48 timelock,
address payoutCurve,
string dstChain,
string dstAddress,
uint256 dstAmount,
string dstToken,
uint256 rewardAmount,
string rewardToken,
string rewardRecipient,
uint48 rewardTimelockDelta,
uint48 quoteExpiry,
bytes userData,
bytes solverData
);
/// @dev Emitted when a solver creates a lock. `sender` is the solver —
/// at most one lock per (hashlock, solver).
event SolverLocked(
bytes32 indexed hashlock,
address indexed sender,
address indexed recipient,
string srcChain,
address token,
uint256 amount,
uint256 reward,
address rewardToken,
address rewardRecipient,
uint48 timelock,
uint48 rewardTimelock,
address payoutCurve,
string dstChain,
string dstAddress,
uint256 dstAmount,
string dstToken,
bytes data
);
/// @dev Emitted on redemption. `rewardTo` is the rewardRecipient before
/// rewardTimelock, and the redeemer after it.
event UserRedeemed(bytes32 indexed hashlock, address redeemer, uint256 secret, uint256 payout, uint256 excess);
event SolverRedeemed(
bytes32 indexed hashlock,
address indexed solver,
address redeemer,
uint256 secret,
uint256 payout,
uint256 excess,
address rewardTo,
uint256 reward
);
/// @dev Emitted on refund (full amount — and for solver locks, the reward — returns to refundTo).
event UserRefunded(bytes32 indexed hashlock, address refundTo, uint256 amount);
event SolverRefunded(bytes32 indexed hashlock, address indexed solver, address refundTo, uint256 amount, uint256 reward);
/// @notice Create a user lock to initiate a cross-chain swap (caller funds the lock).
/// @dev Payable: send `params.amount` as msg.value for native locks; 0 for ERC20 locks.
function userLock(UserLockParams calldata params, DestinationInfo calldata dst, bytes calldata userData, bytes calldata solverData) external payable { ... }
/// @notice Permissionless, ERC20-only variant: funds are pulled from msg.sender but the
/// lock is attributed to `user`. This is the gasless TrainRouter's forwarding target.
function userLockFor(address user, UserLockParams calldata params, DestinationInfo calldata dst, bytes calldata userData, bytes calldata solverData) external { ... }
/// @notice Create a solver lock (amount + reward escrowed). Reverts with
/// SolverLockAlreadyExists if this solver already used this hashlock.
function solverLock(SolverLockParams calldata params, DestinationInfo calldata dst, bytes calldata data) external payable { ... }
/// @notice Redeem a user lock with the secret. Recipient gets the payout
/// (curve-computed, full amount when no curve); any excess goes to refundTo.
function redeemUser(bytes32 hashlock, uint256 secret) external { ... }
/// @notice Redeem a specific solver's lock with the secret. The reward goes to
/// rewardRecipient before rewardTimelock, and to the caller after it.
function redeemSolver(bytes32 hashlock, address solver, uint256 secret) external { ... }
/// @notice Refund a user lock: recipient may refund anytime, others after the timelock.
function refundUser(bytes32 hashlock) external { ... }
/// @notice Refund a solver's lock after its timelock (amount + reward to refundTo).
function refundSolver(bytes32 hashlock, address solver) external { ... }
/// @notice Lock lookups.
function getUserLock(bytes32 hashlock) external view returns (UserLock memory) { ... }
function getSolverLock(bytes32 hashlock, address solver) external view returns (SolverLock memory) { ... }
/// @notice Windowed enumeration of a user's historical locks (scale-safe).
function getUserLockHashes(address user, uint256 offset, uint256 limit) external view returns (bytes32[] memory, uint256 total) { ... }
function getUserLocks(address user, uint256 offset, uint256 limit) external view returns (UserLock[] memory, uint256 total) { ... }
}