@@ -7,9 +7,10 @@ use std::sync::atomic::{AtomicU32, Ordering};
77use std:: time:: Duration ;
88
99use alloy:: dyn_abi:: Eip712Domain ;
10- use alloy:: primitives:: U256 ;
10+ use alloy:: primitives:: { Signature , U256 , keccak256 } ;
1111use alloy:: signers:: Signer ;
1212use alloy:: sol_types:: SolStruct as _;
13+ use alloy:: sol_types:: SolValue as _;
1314use async_stream:: try_stream;
1415use bon:: Builder ;
1516use chrono:: { NaiveDate , Utc } ;
@@ -54,7 +55,8 @@ use crate::clob::types::{
5455 RfqRequestsRequest ,
5556} ;
5657use crate :: clob:: types:: {
57- Amount , OrderPayload , OrderType , Side , SignableOrder , SignatureType , SignedOrder , TickSize ,
58+ Amount , OrderPayload , OrderSignature , OrderType , Side , SignableOrder , SignatureType ,
59+ SignedOrder , TickSize ,
5860} ;
5961use crate :: error:: { Error , Kind as ErrorKind , Synchronization } ;
6062use crate :: types:: { Address , B256 , Decimal } ;
@@ -66,11 +68,42 @@ use crate::{
6668const ORDER_NAME : Option < Cow < ' static , str > > = Some ( Cow :: Borrowed ( "Polymarket CTF Exchange" ) ) ;
6769const VERSION_V1 : Option < Cow < ' static , str > > = Some ( Cow :: Borrowed ( "1" ) ) ;
6870const VERSION_V2 : Option < Cow < ' static , str > > = Some ( Cow :: Borrowed ( "2" ) ) ;
71+ const DEPOSIT_WALLET_NAME : & str = "DepositWallet" ;
72+ const DEPOSIT_WALLET_VERSION : & str = "1" ;
73+ const ORDER_TYPE_STRING : & str = concat ! (
74+ "Order(uint256 salt,address maker,address signer,uint256 tokenId," ,
75+ "uint256 makerAmount,uint256 takerAmount,uint8 side,uint8 signatureType," ,
76+ "uint256 timestamp,bytes32 metadata,bytes32 builder)"
77+ ) ;
78+ const SOLADY_TYPE_STRING : & str = concat ! (
79+ "TypedDataSign(Order contents,string name,string version,uint256 chainId," ,
80+ "address verifyingContract,bytes32 salt)" ,
81+ "Order(uint256 salt,address maker,address signer,uint256 tokenId," ,
82+ "uint256 makerAmount,uint256 takerAmount,uint8 side,uint8 signatureType," ,
83+ "uint256 timestamp,bytes32 metadata,bytes32 builder)"
84+ ) ;
6985
7086const TERMINAL_CURSOR : & str = "LTE=" ; // base64("-1")
7187
7288pub ( crate ) const ORDER_VERSION_MISMATCH_ERROR : & str = "order_version_mismatch" ;
7389
90+ fn push_hex ( out : & mut String , bytes : & [ u8 ] ) {
91+ const LUT : & [ u8 ; 16 ] = b"0123456789abcdef" ;
92+ out. reserve ( bytes. len ( ) * 2 ) ;
93+ for byte in bytes {
94+ out. push ( LUT [ ( byte >> 4 ) as usize ] as char ) ;
95+ out. push ( LUT [ ( byte & 0x0f ) as usize ] as char ) ;
96+ }
97+ }
98+
99+ fn signature_hex_no_prefix ( signature : & Signature ) -> String {
100+ let signature = signature. to_string ( ) ;
101+ signature
102+ . strip_prefix ( "0x" )
103+ . unwrap_or ( & signature)
104+ . to_owned ( )
105+ }
106+
74107/// The type used to build a request to authenticate the inner [`Client<Unauthorized>`]. Calling
75108/// `authenticate` on this will elevate that inner `client` into an [`Client<Authenticated<K>>`].
76109pub struct AuthenticationBuilder < ' signer , S : Signer , K : Kind = Normal > {
@@ -87,8 +120,8 @@ pub struct AuthenticationBuilder<'signer, S: Signer, K: Kind = Normal> {
87120 /// headers for different types of authentication, e.g. Builder.
88121 kind : K ,
89122 /// The optional [`Address`] used to represent the funder for this `client`. If a funder is set
90- /// then `signature_type` must match `Some(SignatureType::Proxy | Signature ::GnosisSafe)`. Conversely,
91- /// if funder is not set, then `signature_type` must be `Some(SignatureType::Eoa)`.
123+ /// then `signature_type` must match `Some(SignatureType::Proxy | SignatureType ::GnosisSafe | SignatureType::Poly1271 )`.
124+ /// Conversely, if funder is not set, then `signature_type` must be `Some(SignatureType::Eoa)`.
92125 funder : Option < Address > ,
93126 /// The optional [`SignatureType`], see `funder` for more information.
94127 signature_type : Option < SignatureType > ,
@@ -179,9 +212,18 @@ impl<S: Signer, K: Kind> AuthenticationBuilder<'_, S, K> {
179212 "Cannot have a funder address with a {sig} signature type"
180213 ) ) ) ;
181214 }
215+ ( None , Some ( SignatureType :: Poly1271 ) ) => {
216+ return Err ( Error :: validation (
217+ "A deposit wallet funder address is required with a Poly1271 signature type" ,
218+ ) ) ;
219+ }
182220 (
183221 Some ( Address :: ZERO ) ,
184- Some ( sig @ ( SignatureType :: Proxy | SignatureType :: GnosisSafe ) ) ,
222+ Some (
223+ sig @ ( SignatureType :: Proxy
224+ | SignatureType :: GnosisSafe
225+ | SignatureType :: Poly1271 ) ,
226+ ) ,
185227 ) => {
186228 return Err ( Error :: validation ( format ! (
187229 "Cannot have a zero funder address with a {sig} signature type"
@@ -913,8 +955,8 @@ impl<S: State> Client<S> {
913955 Ok ( response)
914956 }
915957
916- /// Resolves the V1 `feeRateBps` to apply to an order. Mirrors the TS client's
917- /// `_resolveFeeRateBps`: fetches the market rate via [`Self::fee_rate_bps`] and,
958+ /// Resolves the V1 `feeRateBps` to apply to an order: fetches the market rate via
959+ /// [`Self::fee_rate_bps`] and,
918960 /// when the caller supplied an override, validates that it matches.
919961 ///
920962 /// # Errors
@@ -1714,9 +1756,15 @@ impl<K: Kind> Client<Authenticated<K>> {
17141756 verifying_contract : Some ( exchange) ,
17151757 ..Eip712Domain :: default ( )
17161758 } ;
1717- signer
1718- . sign_hash ( & p. order . eip712_signing_hash ( & domain) )
1719- . await ?
1759+ if p. order . signatureType == SignatureType :: Poly1271 as u8 {
1760+ self . sign_poly1271_order ( signer, & p. order , & domain, chain_id)
1761+ . await ?
1762+ } else {
1763+ signer
1764+ . sign_hash ( & p. order . eip712_signing_hash ( & domain) )
1765+ . await ?
1766+ . into ( )
1767+ }
17201768 }
17211769 OrderPayload :: V1 ( p) => {
17221770 let domain = Eip712Domain {
@@ -1729,6 +1777,7 @@ impl<K: Kind> Client<Authenticated<K>> {
17291777 signer
17301778 . sign_hash ( & p. order . eip712_signing_hash ( & domain) )
17311779 . await ?
1780+ . into ( )
17321781 }
17331782 } ;
17341783
@@ -1742,6 +1791,51 @@ impl<K: Kind> Client<Authenticated<K>> {
17421791 } )
17431792 }
17441793
1794+ async fn sign_poly1271_order < S : Signer > (
1795+ & self ,
1796+ signer : & S ,
1797+ order : & crate :: clob:: types:: OrderV2 ,
1798+ app_domain : & Eip712Domain ,
1799+ chain_id : u64 ,
1800+ ) -> Result < OrderSignature > {
1801+ let contents_hash = order. eip712_hash_struct ( ) ;
1802+ let app_domain_separator = app_domain. hash_struct ( ) ;
1803+
1804+ let typed_data_sign_struct_hash = keccak256 (
1805+ (
1806+ keccak256 ( SOLADY_TYPE_STRING . as_bytes ( ) ) ,
1807+ contents_hash,
1808+ keccak256 ( DEPOSIT_WALLET_NAME . as_bytes ( ) ) ,
1809+ keccak256 ( DEPOSIT_WALLET_VERSION . as_bytes ( ) ) ,
1810+ U256 :: from ( chain_id) ,
1811+ order. signer ,
1812+ B256 :: ZERO ,
1813+ )
1814+ . abi_encode ( ) ,
1815+ ) ;
1816+
1817+ let mut digest_input = [ 0_u8 ; 66 ] ;
1818+ digest_input[ 0 ] = 0x19 ;
1819+ digest_input[ 1 ] = 0x01 ;
1820+ digest_input[ 2 ..34 ] . copy_from_slice ( app_domain_separator. as_slice ( ) ) ;
1821+ digest_input[ 34 ..66 ] . copy_from_slice ( typed_data_sign_struct_hash. as_slice ( ) ) ;
1822+ let digest = keccak256 ( digest_input) ;
1823+
1824+ let inner_signature = signer. sign_hash ( & digest) . await ?;
1825+ let mut wrapped =
1826+ String :: with_capacity ( 2 + 130 + 64 + 64 + ( ORDER_TYPE_STRING . len ( ) * 2 ) + 4 ) ;
1827+ wrapped. push_str ( "0x" ) ;
1828+ wrapped. push_str ( & signature_hex_no_prefix ( & inner_signature) ) ;
1829+ push_hex ( & mut wrapped, app_domain_separator. as_slice ( ) ) ;
1830+ push_hex ( & mut wrapped, contents_hash. as_slice ( ) ) ;
1831+ push_hex ( & mut wrapped, ORDER_TYPE_STRING . as_bytes ( ) ) ;
1832+ let contents_type_len =
1833+ u16:: try_from ( ORDER_TYPE_STRING . len ( ) ) . expect ( "order type string length fits in u16" ) ;
1834+ push_hex ( & mut wrapped, & contents_type_len. to_be_bytes ( ) ) ;
1835+
1836+ Ok ( OrderSignature :: Wrapped ( wrapped) )
1837+ }
1838+
17451839 /// Posts a signed order to the orderbook.
17461840 ///
17471841 /// Submits a single limit or market order that has been signed with the
0 commit comments