A trustless atomic swap implementation on the SUI blockchain, written in Move.
This contract implements Hash Time Locked Contracts (HTLCs) for atomic swaps on SUI, enabling secure cross-chain cryptocurrency exchanges without intermediaries. The contract ensures that either both parties receive their tokens or both parties get refunded - there's no middle ground.
To understand atomic swaps fundamentals, please refer to the Garden Finance documentation.
For the original Solidity implementation that inspired this contract, see: HTLC EVM
- Trustless Swaps: No intermediaries required
- Time-locked Security: Automatic refunds after expiration
- Signature Support: Instant refunds with redeemer signature
- Generic Coin Support: Works with any SUI coin type
- Event Emission: Full transparency through event logs
Represents an individual atomic swap with the following properties:
id
: Unique identifier for the orderis_fulfilled
: Status flag to prevent double-spendinginitiator
: Address that created the swapredeemer
: Address of the intended recipientamount
: Number of coins being swappedinitiated_at
: Timestamp of swap creationcoins
: The actual coins being held in escrowtimelock
: Duration before refund is allowed
Central registry that stores all active orders for a specific coin type.
The contract emits three types of events for complete transparency:
Initiated
: When a new swap is createdRedeemed
: When a swap is successfully completedRefunded
: When a swap is refunded to the initiator
public fun create_orders_registry<CoinType>(ctx: &mut TxContext): ID
Creates a new shared registry for managing atomic swaps of a specific coin type.
public fun initiate<CoinType>(
orders_reg: &mut OrdersRegistry<CoinType>,
initiator: address,
redeemer: address,
secret_hash: vector<u8>,
amount: u256,
timelock: u256,
coins: Coin<CoinType>,
clock: &Clock,
ctx: &mut TxContext
)
Creates a new atomic swap order. The initiator deposits coins that can be redeemed by providing the secret or refunded after the timelock expires.
Parameters:
orders_reg
: Registry to store the orderinitiator
: Address of the swap initiatorredeemer
: Address of the intended recipientsecret_hash
: SHA-256 hash of the secretamount
: Amount of coins to swap (must match coin value)timelock
: Time period (in milliseconds) before refund is allowedcoins
: The coins to be held in escrowclock
: SUI clock object for timestampctx
: Transaction context
public fun initiate_on_behalf<CoinType>(...)
Allows a third party to create an atomic swap on behalf of another address. Useful for automated systems or relayers.
public fun redeem<CoinType>(
orders_reg: &mut OrdersRegistry<CoinType>,
order_id: vector<u8>,
secret: vector<u8>,
ctx: &mut TxContext
)
Completes an atomic swap by providing the correct secret. The coins are transferred to the redeemer's address.
Validation:
- Order must exist and not be fulfilled
- Secret must hash to the stored secret_hash
- Order ID must match the computed ID
public fun refund<CoinType>(
orders_reg: &mut OrdersRegistry<CoinType>,
order_id: vector<u8>,
clock: &Clock,
ctx: &mut TxContext
)
Returns coins to the initiator after the timelock has expired.
Requirements:
- Order must exist and not be fulfilled
- Current time must exceed
initiated_at + timelock
public fun instant_refund<CoinType>(
orders_reg: &mut OrdersRegistry<CoinType>,
order_id: vector<u8>,
ctx: &mut TxContext
)
Allows immediate refund by the redeemer, bypassing the timelock. This enables cooperative cancellations.
Requirements:
- Only the redeemer can call this function
- Order must exist and not be fulfilled
- No signature required - uses transaction sender validation
Order IDs are deterministically generated using:
SHA-256(chain_id + secret_hash + initiator + redeemer + timelock + registry_id)
This ensures uniqueness and prevents replay attacks across different parameters.
- Initiator and redeemer must be different addresses
- Amount must be greater than zero
- Timelock must be greater than zero
- Initiator cannot be the zero address
- Coin value must exactly match the specified amount
- Orders are marked as fulfilled after redemption or refund
- Duplicate order IDs are rejected
- State checks prevent multiple operations on the same order
- Uses SHA-256 for secret hashing
- Ed25519 signatures for instant refunds
- Keccak256 for type hashing and encoding
Code | Error | Description |
---|---|---|
1 | EIncorrectFunds |
Coin value doesn't match specified amount |
2 | EOrderNotExpired |
Timelock hasn't expired yet |
3 | EZeroAddressInitiator |
Initiator cannot be zero address |
4 | EOrderFulfilled |
Order already completed |
5 | EOrderNotInitiated |
Order doesn't exist |
6 | ESenderNotRedeemer |
Only redeemer can call instant_refund |
7 | EDuplicateOrder |
Order ID already exists |
8 | EIncorrectSecret |
Provided secret doesn't match hash |
9 | EZeroTimelock |
Timelock cannot be zero |
10 | EZeroAmount |
Amount cannot be zero |
11 | ESameInitiatorRedeemer |
Initiator and redeemer cannot be the same |
12 | ESameFunderRedeemer |
Funder and redeemer cannot be the same |
13 | EInvalidTimelock |
Timelock must be between 1ms and 7 days |
14 | EInvalidSecretHashLength |
SecretHash length must be 32 bytes |
// 1. Create registry
let registry_id = create_orders_registry<SUI>(ctx);
// 2. Initiate swap
initiate<SUI>(
registry,
initiator_address,
redeemer_address,
sha256_hash_of_secret,
1000000000, // 1 SUI in MIST
3600000, // 1 hour timelock
coins,
clock,
ctx
);
// 3. Redeem with secret
redeem<SUI>(registry, order_id, secret, ctx);
// OR refund after expiry
refund<SUI>(registry, order_id, clock, ctx);
// OR instant refund by redeemer
instant_refund<SUI>(registry, order_id, ctx);
The contract includes chain ID configuration for proper order ID generation:
- Mainnet:
0x0000000000000000000000000000000000000000000000000000000000000000
- Testnet:
0x0000000000000000000000000000000000000000000000000000000000000001
sui_chain_id
in create_order_id()
function before deploying to testnet.
Currently supports:
- SHA-256 for secret hashing
- Keccak256 for type hashing
- Address-based validation for redeemer authorization
The contract includes comprehensive test coverage (100%) with test-only functions:
get_order()
: Retrieve order detailsgenerate_order_id()
: Test order ID generationget_refund_typehash()
: Get refund type hashget_order_reg_id()
: Get registry ID
Run tests with:
npm run test
npm run test:coverage
- Registry Management: Each coin type requires its own registry
- Shared Objects: Registries are shared objects accessible to all users
- Gas Optimization: Functions are optimized for minimal gas usage
- Event Indexing: Events enable easy tracking of swap lifecycle
The contract includes automated deployment scripts:
# Build and deploy
npm run build
npm run deploy:testnet
# Create registry (required for functionality)
export SUI_PACKAGE_ID="0x..."
npm run create-registry:testnet
See DEPLOYMENT.md for detailed deployment instructions.
Please ensure all contributions maintain the security standards and include appropriate tests for any new functionality.