diff --git a/Cargo.dev.toml b/Cargo.dev.toml
index be8d02b120d64c20867750bd4c469ed124ea52f4..d3408d23f494d36e5aa5653f40e8f80b4580f87f 100644
--- a/Cargo.dev.toml
+++ b/Cargo.dev.toml
@@ -4,4 +4,5 @@ members = [
 	"tokens",
 	"traits",
 	"utilities",
+	"auction",
 ]
diff --git a/auction/Cargo.toml b/auction/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..adc41b62b66bd1a83cd13865d0fad9116ca2ffef
--- /dev/null
+++ b/auction/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "orml-auction"
+version = "0.0.1"
+authors = ["Acala Developers"]
+edition = "2018"
+
+[dependencies]
+serde = { version = "1.0", optional = true }
+codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
+sr-primitives = { git = "https://github.com/paritytech/substrate.git", default-features = false }
+srml-support = { package = "srml-support", git = "https://github.com/paritytech/substrate.git", default-features = false }
+srml-system = { package = "srml-system", git = "https://github.com/paritytech/substrate.git", default-features = false }
+runtime_io = { package = "sr-io", git = "https://github.com/paritytech/substrate.git", default-features = false }
+rstd = { package = "sr-std", git = "https://github.com/paritytech/substrate.git", default-features = false }
+
+traits = { package = "orml-traits", path = "../traits", default-features = false }
+utilities = { package = "orml-utilities", path = "../utilities", default-features = false }
+
+[dev-dependencies]
+primitives = { package = "substrate-primitives",  git = "https://github.com/paritytech/substrate.git", default-features = false }
+
+[features]
+default = ["std"]
+std = [
+	"serde",
+	"codec/std",
+	"sr-primitives/std",
+	"srml-support/std",
+	"srml-system/std",
+	"runtime_io/std",
+	"rstd/std",
+	"traits/std",
+	"utilities/std",
+]
\ No newline at end of file
diff --git a/auction/src/lib.rs b/auction/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bf87dcba3089821694079d1957e204e9a5ba0f53
--- /dev/null
+++ b/auction/src/lib.rs
@@ -0,0 +1,147 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use rstd::result;
+use sr_primitives::traits::{MaybeSerializeDeserialize, Member, SimpleArithmetic};
+use srml_support::{
+	decl_error, decl_event, decl_module, decl_storage, dispatch::Result, ensure, Parameter, StorageMap, StorageValue,
+};
+use srml_system::{self as system, ensure_signed};
+use utilities::{LinkedItem, LinkedList};
+
+use traits::{Auction, AuctionHandler, AuctionInfo};
+
+mod mock;
+mod tests;
+
+pub trait Trait: srml_system::Trait {
+	type Event: From<Event<Self>> + Into<<Self as srml_system::Trait>::Event>;
+	type Balance: Parameter + Member + SimpleArithmetic + Default + Copy + MaybeSerializeDeserialize;
+	type AuctionId: Parameter + Member + SimpleArithmetic + Default + Copy + MaybeSerializeDeserialize;
+	type Handler: AuctionHandler<Self::AccountId, Self::Balance, Self::BlockNumber, Self::AuctionId>;
+}
+
+type AuctionEndTimeList<T> =
+	LinkedList<AuctionEndTime<T>, <T as srml_system::Trait>::BlockNumber, <T as Trait>::AuctionId>;
+
+decl_event!(
+	pub enum Event<T> where
+		<T as srml_system::Trait>::AccountId,
+		<T as Trait>::Balance,
+		<T as Trait>::AuctionId,
+	{
+		Bid(AuctionId, AccountId, Balance),
+	}
+);
+
+decl_storage! {
+	trait Store for Module<T: Trait> as Auction {
+		pub Auctions get(fn auctions): map T::AuctionId => Option<AuctionInfo<T::AccountId, T::Balance, T::BlockNumber>>;
+		pub AuctionsCount get(fn auctions_count): T::AuctionId;
+		pub AuctionEndTime get(fn auction_end_time): map(T::BlockNumber, Option<T::AuctionId>) => Option<LinkedItem<T::AuctionId>>;
+	}
+}
+
+decl_module! {
+	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+		fn deposit_event() = default;
+
+		pub fn bid(origin, id: T::AuctionId, value: T::Balance) -> Result {
+			let from = ensure_signed(origin)?;
+
+			let mut auction = <Auctions<T>>::get(id).ok_or(Error::AuctionNotExist)?;
+			if let Some(ref current_bid) = auction.bid {
+				ensure!(value > current_bid.1, Error::InvalidBidPrice.into());
+			} else {
+				ensure!(value > 0.into(), Error::InvalidBidPrice.into());
+			}
+			let bid_result = T::Handler::on_new_bid(
+				<srml_system::Module<T>>::block_number(),
+				id,
+				(from.clone(), value),
+				auction.bid.clone(),
+			);
+
+			ensure!(bid_result.accept_bid, Error::BidNotAccepted.into());
+			if let Some(new_end) = bid_result.auction_end {
+				if let Some(old_end_block) = auction.end {
+					<AuctionEndTimeList<T>>::remove(&old_end_block, id);
+				}
+				if let Some(new_end_block) = new_end {
+					<AuctionEndTimeList<T>>::append(&new_end_block, id);
+				}
+				auction.end = new_end;
+			}
+			auction.bid = Some((from.clone(), value));
+			<Auctions<T>>::insert(id, auction);
+			Self::deposit_event(RawEvent::Bid(id, from, value));
+			Ok(())
+		}
+
+		fn on_finalize(now: T::BlockNumber) {
+			let head_key: Option<T::AuctionId> = None;
+			if let Some(mut head_item) = <AuctionEndTime<T>>::get((now, head_key)) {
+				while let Some(auction_id) = head_item.next {
+					if let Some(auction) = Self::auctions(auction_id) {
+						T::Handler::on_auction_ended(auction_id, auction.bid);
+						<Auctions<T>>::remove(auction_id);
+					}
+					head_item = <AuctionEndTime<T>>::get((now, Some(auction_id))).unwrap_or_else(|| LinkedItem {
+							prev: None,
+							next: None,
+						});
+					<AuctionEndTime<T>>::remove((now, Some(auction_id)));
+				}
+
+				<AuctionEndTime<T>>::remove((now, head_key));
+			}
+		}
+	}
+}
+
+decl_error! {
+	/// Error for auction module.
+	pub enum Error {
+		AuctionNotExist,
+		BidNotAccepted,
+		InvalidBidPrice,
+	}
+}
+
+impl<T: Trait> Module<T> {}
+
+impl<T: Trait> Auction<T::AccountId, T::BlockNumber> for Module<T> {
+	type AuctionId = T::AuctionId;
+	type Balance = T::Balance;
+	type Error = Error;
+
+	fn auction_info(id: Self::AuctionId) -> Option<AuctionInfo<T::AccountId, Self::Balance, T::BlockNumber>> {
+		Self::auctions(id)
+	}
+
+	fn update_auction(
+		id: Self::AuctionId,
+		info: AuctionInfo<T::AccountId, Self::Balance, T::BlockNumber>,
+	) -> result::Result<(), Self::Error> {
+		let auction = <Auctions<T>>::get(id).ok_or(Error::AuctionNotExist)?;
+		if let Some(old_end) = auction.end {
+			<AuctionEndTimeList<T>>::remove(&old_end, id);
+		}
+		if let Some(new_end) = info.end {
+			<AuctionEndTimeList<T>>::append(&new_end, id);
+		}
+		<Auctions<T>>::insert(id, info);
+		Ok(())
+	}
+
+	fn new_auction(_start: T::BlockNumber, end: Option<T::BlockNumber>) -> Self::AuctionId {
+		let auction = AuctionInfo { bid: None, end: end };
+		let auction_id = Self::auctions_count();
+		<AuctionsCount<T>>::mutate(|n| *n += Self::AuctionId::from(1));
+		<Auctions<T>>::insert(auction_id, auction);
+		if let Some(end_block) = end {
+			<AuctionEndTimeList<T>>::append(&end_block, auction_id);
+		}
+
+		auction_id
+	}
+}
diff --git a/auction/src/mock.rs b/auction/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..57d4d9a9402824c3342e94a4fa366372b47e1d41
--- /dev/null
+++ b/auction/src/mock.rs
@@ -0,0 +1,103 @@
+//! Mocks for the auction module.
+
+#![cfg(test)]
+
+use primitives::H256;
+use sr_primitives::{testing::Header, traits::IdentityLookup, Perbill};
+use srml_support::{impl_outer_event, impl_outer_origin, parameter_types};
+use traits::OnNewBidResult;
+
+use super::*;
+
+impl_outer_origin! {
+	pub enum Origin for Runtime {}
+}
+
+mod auction {
+	pub use crate::Event;
+}
+
+impl_outer_event! {
+	pub enum TestEvent for Runtime {
+		auction<T>,
+	}
+}
+
+// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct Runtime;
+parameter_types! {
+	pub const BlockHashCount: u64 = 250;
+	pub const MaximumBlockWeight: u32 = 1024;
+	pub const MaximumBlockLength: u32 = 2 * 1024;
+	pub const AvailableBlockRatio: Perbill = Perbill::one();
+}
+
+pub type AccountId = u64;
+pub type Balance = u64;
+pub type BlockNumber = u64;
+pub type AuctionId = u64;
+
+impl system::Trait for Runtime {
+	type Origin = Origin;
+	type Index = u64;
+	type BlockNumber = BlockNumber;
+	type Call = ();
+	type Hash = H256;
+	type Hashing = ::sr_primitives::traits::BlakeTwo256;
+	type AccountId = AccountId;
+	type Lookup = IdentityLookup<Self::AccountId>;
+	type Header = Header;
+	type Event = TestEvent;
+	type BlockHashCount = BlockHashCount;
+	type MaximumBlockWeight = MaximumBlockWeight;
+	type MaximumBlockLength = MaximumBlockLength;
+	type AvailableBlockRatio = AvailableBlockRatio;
+	type Version = ();
+}
+
+pub type System = system::Module<Runtime>;
+
+pub struct Handler;
+
+impl AuctionHandler<AccountId, Balance, BlockNumber, AuctionId> for Handler {
+	fn on_new_bid(
+		_now: BlockNumber,
+		_id: AuctionId,
+		_new_bid: (AccountId, Balance),
+		_last_bid: Option<(AccountId, Balance)>,
+	) -> OnNewBidResult<BlockNumber> {
+		OnNewBidResult {
+			accept_bid: true,
+			auction_end: None,
+		}
+	}
+
+	fn on_auction_ended(_id: AuctionId, _winner: Option<(AccountId, Balance)>) {}
+}
+
+impl Trait for Runtime {
+	type Event = TestEvent;
+	type Balance = Balance;
+	type AuctionId = AccountId;
+	type Handler = Handler;
+}
+pub type AuctionModule = Module<Runtime>;
+
+pub const ALICE: AccountId = 1;
+
+pub struct ExtBuilder;
+
+impl Default for ExtBuilder {
+	fn default() -> Self {
+		ExtBuilder
+	}
+}
+
+impl ExtBuilder {
+	pub fn build(self) -> runtime_io::TestExternalities {
+		let t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
+
+		t.into()
+	}
+}
diff --git a/auction/src/tests.rs b/auction/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c44f97a2c462d1d0b8a65ddc54e9e144c36967e5
--- /dev/null
+++ b/auction/src/tests.rs
@@ -0,0 +1,57 @@
+//! Unit tests for the tokens module.
+
+#![cfg(test)]
+
+use super::*;
+use mock::{AuctionModule, ExtBuilder, ALICE};
+use srml_support::assert_ok;
+
+#[test]
+fn new_auction_should_work() {
+	ExtBuilder::default().build().execute_with(|| {
+		assert_eq!(AuctionModule::new_auction(10, Some(100)), 0);
+	});
+}
+
+#[test]
+fn update_auction_should_work() {
+	ExtBuilder::default().build().execute_with(|| {
+		assert_eq!(AuctionModule::new_auction(10, Some(100)), 0);
+		assert_ok!(AuctionModule::update_auction(
+			0,
+			AuctionInfo {
+				bid: Some((ALICE, 100)),
+				end: Some(100)
+			}
+		));
+	});
+}
+
+#[test]
+fn auction_info_should_work() {
+	ExtBuilder::default().build().execute_with(|| {
+		assert_eq!(AuctionModule::new_auction(10, Some(100)), 0);
+		assert_eq!(
+			AuctionModule::auction_info(0),
+			Some(AuctionInfo {
+				bid: None,
+				end: Some(100)
+			})
+		);
+	});
+}
+
+#[test]
+fn bid_should_work() {
+	ExtBuilder::default().build().execute_with(|| {
+		assert_eq!(AuctionModule::new_auction(10, Some(100)), 0);
+		assert_ok!(AuctionModule::bid(Some(ALICE).into(), 0, 20));
+		assert_eq!(
+			AuctionModule::auction_info(0),
+			Some(AuctionInfo {
+				bid: Some((ALICE, 20)),
+				end: Some(100)
+			})
+		);
+	});
+}
diff --git a/traits/src/auction.rs b/traits/src/auction.rs
index 0bbac347fe59b6a5560e5066d41e15c9e3e2e785..5ceb4f1d800726dff2615a9068a77bc338eafaf1 100644
--- a/traits/src/auction.rs
+++ b/traits/src/auction.rs
@@ -1,26 +1,50 @@
 use codec::FullCodec;
-use rstd::fmt::Debug;
-use sr_primitives::traits::MaybeSerializeDeserialize;
+use codec::{Decode, Encode};
+use rstd::{fmt::Debug, result};
+use sr_primitives::{
+	traits::{MaybeSerializeDeserialize, SimpleArithmetic},
+	RuntimeDebug,
+};
 
+/// Auction info.
+#[cfg_attr(feature = "std", derive(PartialEq, Eq))]
+#[derive(Encode, Decode, RuntimeDebug)]
 pub struct AuctionInfo<AccountId, Balance, BlockNumber> {
+	/// Current bidder and bid price.
 	pub bid: Option<(AccountId, Balance)>,
+	/// Define which block this auction will be ended.
 	pub end: Option<BlockNumber>,
 }
 
-pub trait Auction<AccountId, Balance, BlockNumber> {
-	type AuctionId: FullCodec + Copy + MaybeSerializeDeserialize + Debug;
+/// Abstraction over a simple auction system.
+pub trait Auction<AccountId, BlockNumber> {
+	/// The id of an AuctionInfo
+	type AuctionId: FullCodec + Default + Copy + MaybeSerializeDeserialize + Debug;
+	/// The price to bid.
+	type Balance: SimpleArithmetic + FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default;
+	/// The error type.
+	type Error: Into<&'static str>;
 
-	fn auction_info(id: Self::AuctionId) -> Option<AuctionInfo<AccountId, Balance, BlockNumber>>;
-	fn update_auction(id: Self::AuctionId, info: AuctionInfo<AccountId, Balance, BlockNumber>);
+	/// The auction info of `id`
+	fn auction_info(id: Self::AuctionId) -> Option<AuctionInfo<AccountId, Self::Balance, BlockNumber>>;
+	/// Update the auction info of `id` with `info`
+	fn update_auction(
+		id: Self::AuctionId,
+		info: AuctionInfo<AccountId, Self::Balance, BlockNumber>,
+	) -> result::Result<(), Self::Error>;
+	/// Create new auction with specific startblock and endblock, return the id of the auction
 	fn new_auction(start: BlockNumber, end: Option<BlockNumber>) -> Self::AuctionId;
 }
 
+/// The result of bid handling.
 pub struct OnNewBidResult<BlockNumber> {
+	/// Indicates if the bid was accepted
 	pub accept_bid: bool,
 	/// `None` means don't change, `Some(None)` means no more auction end time, `Some(Some(number))` means set auction end time to this block
 	pub auction_end: Option<Option<BlockNumber>>,
 }
 
+/// Hooks for auction to handle bids.
 pub trait AuctionHandler<AccountId, Balance, BlockNumber, AuctionId> {
 	/// Called when new bid is received.
 	/// The return value deteermine if the bid should be accepted and update auction end time.
@@ -31,5 +55,6 @@ pub trait AuctionHandler<AccountId, Balance, BlockNumber, AuctionId> {
 		new_bid: (AccountId, Balance),
 		last_bid: Option<(AccountId, Balance)>,
 	) -> OnNewBidResult<BlockNumber>;
+	/// End an auction with `winner`
 	fn on_auction_ended(id: AuctionId, winner: Option<(AccountId, Balance)>);
 }