1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
//! Typed ABI bindings (`alloy::sol!`) for the Paralock contracts and the token interfaces the clients use.
//!
//! The interfaces are written out by hand so they carry documentation, and are checked against the compiled
//! artifacts (`artifacts/*.json`, the same files as `packages/evm/artifacts`) and the selectors in
//! `spec/vectors/evm.json` by the crate's tests: every function, event and error of the artifacts is here with the
//! same selector, and nothing else.
//!
//! The generated items follow `alloy::sol!` conventions: `ParalockHTLC::lockCall` encodes a call,
//! `ParalockHTLC::Locked` decodes an event, `ParalockHTLC::ParalockHTLCErrors` decodes any of the contract's custom
//! errors, and so on.
#![allow(missing_docs)] // helper types generated by `sol!` (call/return structs, selector enums) carry no docs
#![allow(clippy::too_many_arguments)] // the generated `Locked` event constructor takes one argument per field
use alloy::sol;
sol! {
/// `ParalockHTLC`: hash-time-locked escrow (htlc mode). See `contracts/src/ParalockHTLC.sol`.
///
/// Storage is one slot per hashlock: `0` (never used), `keccak256(abi.encode(swap))` while active, or `SETTLED`
/// (`0x…01`) forever after a claim or refund.
#[derive(Debug, PartialEq, Eq)]
interface ParalockHTLC {
/// Every parameter of a swap. Its keccak256 commitment is all the contract stores.
struct Swap {
/// sha256 of the 32-byte secret; unique key of the swap.
bytes32 hashlock;
/// TIP-20 (or ERC-20) token held in escrow.
address token;
/// Party that locks the funds and is refunded after expiry.
address sender;
/// Party paid on claim.
address recipient;
/// Token amount in base units.
uint256 amount;
/// Unix timestamp (seconds) from which the swap can no longer be claimed, only refunded.
uint64 expiry;
}
/// Funds were locked. Carries every `Swap` field so the struct can be rebuilt from the log alone.
event Locked(
bytes32 indexed hashlock,
address indexed sender,
address indexed recipient,
address token,
uint256 amount,
uint64 expiry
);
/// The swap was claimed and the recipient paid. `preimage` unlocks the Zcash leg.
event Claimed(bytes32 indexed hashlock, bytes32 preimage);
/// The swap expired and the sender was refunded.
event Refunded(bytes32 indexed hashlock);
/// `msg.sender` is not the swap's sender.
error NotSender();
/// The hashlock is zero.
error InvalidHashlock();
/// The amount is zero.
error InvalidAmount();
/// The recipient is the zero address.
error InvalidRecipient();
/// The expiry is not in the future.
error InvalidExpiry();
/// The hashlock was already used by an active or settled swap.
error HashlockUsed(bytes32 hashlock);
/// No swap was ever locked under this hashlock.
error SwapNotFound(bytes32 hashlock);
/// The swap under this hashlock was already claimed or refunded.
error SwapSettled(bytes32 hashlock);
/// The provided swap parameters do not match the locked swap.
error SwapMismatch(bytes32 hashlock);
/// sha256(preimage) is not the swap's hashlock.
error InvalidPreimage();
/// Claim attempted at or after expiry.
error SwapExpired(uint64 expiry);
/// Refund attempted before expiry.
error SwapNotExpired(uint64 expiry);
/// The token call failed without a reason, or returned false.
error TokenTransferFailed(address token);
/// The escrow received a different amount than requested (fee-on-transfer or rebasing token).
error UnsupportedToken(address token);
/// Lock `s.amount` of `s.token` under `s.hashlock` (caller must be `s.sender` and have approved the escrow).
function lock(Swap calldata s) external;
/// Pay `s.recipient` by revealing the secret. Callable by anyone before `s.expiry`.
function claim(Swap calldata s, bytes32 preimage) external;
/// Return the funds to `s.sender`. Callable by anyone from `s.expiry` on.
function refund(Swap calldata s) external;
/// Raw state of a hashlock: 0 if unused, the swap commitment if active, `SETTLED` once claimed/refunded.
function stateOf(bytes32 hashlock) external view returns (bytes32);
/// Lifecycle status of a hashlock (`Status` enum: 0 empty, 1 active, 2 settled).
function statusOf(bytes32 hashlock) external view returns (uint8);
/// True if a swap is locked under `hashlock` and not yet claimed or refunded.
function isActive(bytes32 hashlock) external view returns (bool);
/// The value stored for an active swap: keccak256 of its ABI encoding.
function commitment(Swap calldata s) external pure returns (bytes32);
/// State value of a hashlock whose swap was claimed or refunded.
function SETTLED() external view returns (bytes32);
}
}
sol! {
/// `ParalockAdaptorEscrow`: signature-locked escrow (adaptor mode). See `contracts/src/ParalockAdaptorEscrow.sol`.
///
/// Released by a raw ECDSA signature from the swap's one-time `signer` over `claimDigest(id)`; storage is
/// `id = keccak256(abi.encode(swap)) => Status` (0 empty, 1 active, 2 settled).
#[derive(Debug, PartialEq, Eq)]
interface ParalockAdaptorEscrow {
/// Every parameter of a swap. Its keccak256 hash is the swap id.
struct Swap {
/// TIP-20 (or ERC-20) token held in escrow.
address token;
/// Party that locks the funds and is refunded after expiry.
address sender;
/// Party paid on claim.
address recipient;
/// Address whose signature over `claimDigest(id)` releases the funds.
address signer;
/// Token amount in base units.
uint256 amount;
/// Unix timestamp (seconds) from which the swap can no longer be claimed, only refunded.
uint64 expiry;
/// Random value that makes the id unique and unlinkable across swaps.
bytes32 salt;
}
/// Funds were locked. Carries every `Swap` field so the struct can be rebuilt from the log alone.
event Locked(
bytes32 indexed id,
address indexed sender,
address indexed recipient,
address token,
address signer,
uint256 amount,
uint64 expiry,
bytes32 salt
);
/// The swap was claimed and the recipient paid. `(r, s)` is the completed adaptor signature.
event Claimed(bytes32 indexed id, uint8 v, bytes32 r, bytes32 s);
/// The swap expired and the sender was refunded.
event Refunded(bytes32 indexed id);
/// `msg.sender` is not the swap's sender.
error NotSender();
/// The amount is zero.
error InvalidAmount();
/// The recipient is the zero address.
error InvalidRecipient();
/// The signer is the zero address.
error InvalidSigner();
/// The expiry is not in the future.
error InvalidExpiry();
/// A swap with this id was already locked (it is active or settled).
error SwapExists(bytes32 id);
/// No swap was ever locked under this id (unknown or tampered parameters).
error SwapNotFound(bytes32 id);
/// The swap was already claimed or refunded.
error SwapSettled(bytes32 id);
/// Claim attempted at or after expiry.
error SwapExpired(uint64 expiry);
/// Refund attempted before expiry.
error SwapNotExpired(uint64 expiry);
/// The signature does not recover to the swap's signer.
error InvalidSignature();
/// The signature is not canonical: `s` is in the upper half of the curve order, or `v` is not 27/28.
error MalleableSignature();
/// The token call failed without a reason, or returned false.
error TokenTransferFailed(address token);
/// The escrow received a different amount than requested (fee-on-transfer or rebasing token).
error UnsupportedToken(address token);
/// Lock `s.amount` of `s.token` for `s.recipient`; returns the swap id.
function lock(Swap calldata s) external returns (bytes32 id);
/// Pay `s.recipient` with `s.signer`'s signature over `claimDigest(id)`. Callable by anyone before expiry.
function claim(Swap calldata s, uint8 v, bytes32 r, bytes32 sigS) external;
/// Return the funds to `s.sender`. Callable by anyone from `s.expiry` on.
function refund(Swap calldata s) external;
/// The 32-byte digest `s.signer` signs (raw ECDSA, no prefix) to release swap `id`.
function claimDigest(bytes32 id) external view returns (bytes32);
/// The id of a swap: keccak256 of its ABI encoding.
function idOf(Swap calldata s) external pure returns (bytes32);
/// Lifecycle status of a swap id (`Status` enum: 0 empty, 1 active, 2 settled).
function statusOf(bytes32 id) external view returns (uint8);
/// True if `id` is locked and not yet claimed or refunded.
function isActive(bytes32 id) external view returns (bool);
/// Domain tag of the claim digest: `keccak256("ParalockAdaptorClaim")`.
function CLAIM_TYPEHASH() external view returns (bytes32);
}
}
sol! {
/// `MockUSD`: the 6-decimal "USDC" stand-in for local devnets (only the deployer can mint).
#[derive(Debug, PartialEq, Eq)]
interface MockUSD {
/// Tokens moved.
event Transfer(address indexed from, address indexed to, uint256 value);
/// An allowance was set.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// Only the deployer may mint.
error NotMinter();
/// The sender's balance is too low.
error InsufficientBalance();
/// The spender's allowance is too low.
error InsufficientAllowance();
/// Transfer or mint to the zero address.
error ZeroAddress();
/// Token name.
function name() external view returns (string);
/// Token symbol (`USDC`).
function symbol() external view returns (string);
/// Token decimals (6).
function decimals() external view returns (uint8);
/// The only account allowed to mint (the deployer).
function minter() external view returns (address);
/// Total minted.
function totalSupply() external view returns (uint256);
/// Balance of an account.
function balanceOf(address account) external view returns (uint256);
/// Allowance of `spender` over `owner`'s tokens.
function allowance(address owner, address spender) external view returns (uint256);
/// Mint `amount` to `to` (deployer only).
function mint(address to, uint256 amount) external;
/// Set `spender`'s allowance.
function approve(address spender, uint256 amount) external returns (bool);
/// Transfer tokens.
function transfer(address to, uint256 amount) external returns (bool);
/// Transfer tokens on behalf of `from`.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
}
sol! {
/// The ERC-20 surface the clients use (TIP-20 tokens implement it too).
#[derive(Debug, PartialEq, Eq)]
interface IERC20 {
/// Balance of an account.
function balanceOf(address account) external view returns (uint256);
/// Allowance of `spender` over `owner`'s tokens.
function allowance(address owner, address spender) external view returns (uint256);
/// Set `spender`'s allowance to exactly `amount`.
function approve(address spender, uint256 amount) external returns (bool);
/// Token decimals.
function decimals() external view returns (uint8);
/// Token symbol.
function symbol() external view returns (string);
}
}
sol! {
/// Tempo's TIP-20 factory precompile (`0x20Fc…0000`), as used by the local deployment helper.
#[derive(Debug, PartialEq, Eq)]
interface ITIP20Factory {
/// The factory refused an address in the reserved range.
error AddressReserved();
/// The address is not in the reserved range.
error AddressNotReserved();
/// The quote token is not a valid TIP-20.
error InvalidQuoteToken();
/// A token already exists at the derived address.
error TokenAlreadyExists(address token);
/// Create a TIP-20 at an address derived from `(msg.sender, salt)`.
function createToken(
string name,
string symbol,
string currency,
address quoteToken,
address admin,
bytes32 salt
) external returns (address);
/// True if `token` is a TIP-20.
function isTIP20(address token) external view returns (bool);
/// The address `createToken` would use for `(sender, salt)`.
function getTokenAddress(address sender, bytes32 salt) external pure returns (address);
}
}
sol! {
/// The TIP-20 issuer surface used by the local deployment helper (roles and minting).
#[derive(Debug, PartialEq, Eq)]
interface ITIP20Issuer {
/// The caller lacks the required role.
error Unauthorized();
/// The role allowed to mint.
function ISSUER_ROLE() external view returns (bytes32);
/// Grant `role` to `account` (admin only).
function grantRole(bytes32 role, address account) external;
/// Mint `amount` to `to` (issuer only).
function mint(address to, uint256 amount) external;
}
}
sol! {
/// The TIP-20 custom errors a token call bubbled up through an escrow can revert with (Tempo's `ITIP20`).
#[derive(Debug, PartialEq, Eq)]
interface ITIP20Errors {
/// Balance too low.
error InsufficientBalance(uint256 available, uint256 required, address token);
/// Allowance too low.
error InsufficientAllowance();
/// Minting would exceed the supply cap.
error SupplyCapExceeded();
/// Invalid supply cap.
error InvalidSupplyCap();
/// Invalid payload.
error InvalidPayload();
/// The token's transfer policy (TIP-403) forbids the transfer.
error PolicyForbids();
/// Invalid recipient.
error InvalidRecipient();
/// The token is paused.
error ContractPaused();
/// Invalid currency.
error InvalidCurrency();
/// Invalid quote token.
error InvalidQuoteToken();
/// Invalid amount.
error InvalidAmount();
/// No opted-in supply.
error NoOptedInSupply();
/// The caller lacks the required role.
error Unauthorized();
/// The address is protected.
error ProtectedAddress();
/// Not a valid token.
error InvalidToken();
/// The token is not initialized.
error Uninitialized();
/// Invalid transfer policy id.
error InvalidTransferPolicyId();
}
}
sol! {
/// Tempo's fee manager precompile (`0xfeEC…0000`): an account's preferred fee token for plain (non-`0x76`)
/// transactions.
#[derive(Debug, PartialEq, Eq)]
interface ITipFeeManager {
/// The account's preferred fee token (zero if none is set).
function userTokens(address user) external view returns (address);
/// Set the caller's preferred fee token.
function setUserToken(address token) external;
}
}