@@ -43,6 +43,15 @@ const MANUALLY_IMPLEMENTED_METHODS = new Set([
4343 'safeList' , 'safeList2' , 'safeListN' ,
4444 'filterByLimit' , 'filterBySinceLimit' ,
4545 'parseBidAsk' , 'parseBidsAsks' , 'parseOrderBook' ,
46+ 'checkAddress' , 'networkIdToCode' , 'depositWithdrawFee' ,
47+ 'parseJson' ,
48+ // Methods with compilation issues (usize/Value, mutability, .length field)
49+ // Stubbed until transpiler handles these patterns
50+ 'fetchMarkets' , 'fetchMyDustTrades' , 'parseAccountPosition' ,
51+ 'parsePositionRisk' , 'setMarginMode' , 'getNetworkCodeByNetworkUrl' ,
52+ 'getBaseDomainFromUrl' , 'sign' , 'handleErrors' ,
53+ 'fetchConvertTrade' , 'fetchConvertTradeHistory' ,
54+ 'fetchAllGreeks' , 'fetchOptionPositions' ,
4655] ) ;
4756
4857function isUpperCase ( x : string ) {
@@ -322,7 +331,7 @@ function getArgumentCount(className: string, node: any) {
322331 sortBy : 4 , indexBy : 2 , filterBy : 3 , groupBy : 2 ,
323332 filterByLimit : 4 , filterBySinceLimit : 5 ,
324333 parseBidAsk : 3 , parseBidsAsks : 3 , parseOrderBook : 5 ,
325- parseNumber : 2 , parseToInt : 2 ,
334+ parseNumber : 2 , parseToInt : 2 , parseToNumeric : 2 , parseInt : 2 ,
326335 json : 1 , encode : 1 , urlencode : 1 , rawencode : 1 ,
327336 hash : 3 , hmac : 4 , jwt : 4 ,
328337 encodeURIComponent : 1 , uuid22 : 0 , yymmdd : 2 ,
@@ -334,9 +343,13 @@ function getArgumentCount(className: string, node: any) {
334343 safeMarket : 4 , safeCurrency : 2 , safeCurrencyCode : 2 , safeSymbol : 4 ,
335344 } ;
336345 const fname = getCalleeFunctionName ( node ) ;
346+ // argCounts overrides take priority (hand-tuned for Rust signatures)
347+ if ( argCounts [ fname ] !== undefined ) {
348+ return argCounts [ fname ] ;
349+ }
337350 const rv = FUNCTION_INFO [ className ] ?. [ fname ] || FUNCTION_INFO [ 'Exchange' ] ?. [ fname ] ;
338351 if ( ! rv ) {
339- return argCounts [ fname ] ?? node . arguments . length ;
352+ return node . arguments . length ;
340353 }
341354 return rv . paramsCount ;
342355}
@@ -768,8 +781,8 @@ function transpileMethodToRust(opts: {
768781 // Only use UFCS for methods the exchange's own trait defines,
769782 // to avoid ambiguity between Exchange and the exchange-specific trait.
770783 if ( opts . ownMethodNames ?. has ( fname ) ) return true ;
771- // Also match methods only known through FUNCTION_INFO (not in base)
772- if ( ! baseMethodNames ?. includes ( fname ) && FUNCTION_INFO [ className ] ?. [ fname ] ) return true ;
784+ // Also match methods only known through FUNCTION_INFO (not in base, not manually implemented )
785+ if ( ! baseMethodNames ?. includes ( fname ) && ! MANUALLY_IMPLEMENTED_METHODS . has ( fname ) && FUNCTION_INFO [ className ] ?. [ fname ] ) return true ;
773786 return false ;
774787 }
775788 return false ;
@@ -934,18 +947,29 @@ function transpileMethodToRust(opts: {
934947 fname === 'nonce' ||
935948 fname === 'symbol' ||
936949 fname === 'account' ||
937- fname === 'currency'
950+ fname === 'currency' ||
951+ fname === 'sign' ||
952+ fname === 'encode' ||
953+ fname === 'hash' ||
954+ fname === 'hmac' ||
955+ fname === 'createExpiredOptionMarket' ||
956+ fname === 'getNetworkCodeByNetworkUrl'
938957 ) {
939958 isSelfImmutable = true ;
940959 }
941960
961+ // Override: these parse/safe methods need &mut self because they
962+ // call mutable methods internally
942963 if (
943964 fname === 'safeOrder' ||
944965 fname === 'safeTrade' ||
945966 fname === 'parseTrade' ||
946967 fname === 'parseOrder' ||
947968 fname === 'parseTrades' ||
948- fname === 'parseOrders'
969+ fname === 'parseOrders' ||
970+ fname === 'parseDepositAddress' ||
971+ fname === 'parseDepositWithdrawFee' ||
972+ fname === 'parseDepositWithdrawFees'
949973 ) {
950974 isSelfImmutable = false ;
951975 }
@@ -1576,6 +1600,35 @@ function transpileMethodToRust(opts: {
15761600 } ,
15771601
15781602 CallExpression ( node : any , state : any , c : any ) {
1603+ // Handle static method calls: Object.keys(x) → x.keys(), etc.
1604+ if (
1605+ node . callee . type === 'MemberExpression' &&
1606+ node . callee . object . type === 'Identifier' &&
1607+ ! node . callee . computed
1608+ ) {
1609+ const objName = node . callee . object . name ;
1610+ const methodName = node . callee . property . name ;
1611+ if ( objName === 'Object' && ( methodName === 'keys' || methodName === 'values' || methodName === 'entries' ) && node . arguments . length >= 1 ) {
1612+ c ( node . arguments [ 0 ] , asType ( state , 'value' ) ) ;
1613+ emit ( `.${ methodName } ()` ) ;
1614+ if ( state . awaited ) emit ( '.await' ) ;
1615+ return ;
1616+ }
1617+ if ( objName === 'JSON' && methodName === 'stringify' && node . arguments . length >= 1 ) {
1618+ c ( node . arguments [ 0 ] , asType ( state , 'value' ) ) ;
1619+ emit ( '.to_string()' ) ;
1620+ if ( state . awaited ) emit ( '.await' ) ;
1621+ return ;
1622+ }
1623+ if ( objName === 'JSON' && methodName === 'parse' && node . arguments . length >= 1 ) {
1624+ emit ( 'self.parse_json(' ) ;
1625+ c ( node . arguments [ 0 ] , asType ( state , 'value' ) ) ;
1626+ emit ( ')' ) ;
1627+ if ( state . awaited ) emit ( '.await' ) ;
1628+ return ;
1629+ }
1630+ }
1631+
15791632 let argCounts = getArgumentCount ( className , node ) ;
15801633 const retType = getReturnType ( node ) ;
15811634 let shouldAwait = state . awaited ;
@@ -1634,6 +1687,10 @@ function transpileMethodToRust(opts: {
16341687 if ( fname === 'cancelOrder' || fname === 'fetchTransactionFees' || fname === 'fetchTransactionFee' ) {
16351688 shouldAwait = true ;
16361689 }
1690+ // Always await dispatch calls — Rust can't store futures in Value
1691+ if ( isDispatchCall ( node ) ) {
1692+ shouldAwait = true ;
1693+ }
16371694 if ( shouldAwait ) emit ( '.await' ) ;
16381695 } ,
16391696
@@ -1800,13 +1857,13 @@ function transpileMethodToRust(opts: {
18001857 }
18011858 let isValueType = false ;
18021859 if ( varTypes [ node . name ] === 'usize' ) {
1803- if ( state . asType === 'value' ) emit ( 'Value::from(' ) ;
1860+ if ( state . asType === 'value' || state . asType === 'rvalue' ) emit ( 'Value::from(' ) ;
18041861 emit ( node . name ) ;
18051862 isValueType = true ;
18061863 } else {
18071864 switch ( node . name ) {
18081865 case 'length' :
1809- if ( state . asType === 'value' ) emit ( 'Value::from(' ) ;
1866+ if ( state . asType === 'value' || state . asType === 'rvalue' ) emit ( 'Value::from(' ) ;
18101867 emit ( 'len()' ) ;
18111868 isValueType = true ;
18121869 break ;
@@ -2177,7 +2234,16 @@ class RustTranspiler {
21772234 'use std::str::FromStr;' ,
21782235 'use serde::{Deserialize, Serialize};' ,
21792236 'use serde_json::json;' ,
2180- 'use crate::exchange::{Exchange, ExchangeImpl, Precise, Value, ValueTrait, BoolExt, JSON, Array, Object, Math, parse_int, shift_2, extend_2, normalize};' ,
2237+ 'use crate::exchange::{Exchange, ExchangeImpl, Precise, Value, ValueTrait, BoolExt, JSON, Array, Object, Math, Promise, parse_int, shift_2, extend_2, normalize};' ,
2238+ '// Crypto hash identifiers' ,
2239+ 'fn sha256() -> Value { Value::from("sha256") }' ,
2240+ 'fn sha384() -> Value { Value::from("sha384") }' ,
2241+ 'fn sha512() -> Value { Value::from("sha512") }' ,
2242+ 'fn md5() -> Value { Value::from("md5") }' ,
2243+ 'fn ed25519() -> Value { Value::from("ed25519") }' ,
2244+ 'fn rsa(msg: Value, secret: Value, _hash: Value) -> Value { msg }' ,
2245+ 'fn eddsa(msg: Value, secret: Value, _curve: Value) -> Value { msg }' ,
2246+ 'fn secp256k1() -> Value { Value::from("secp256k1") }' ,
21812247 '' ,
21822248 'use crate::exchange::{PRECISE_BASE, TRUNCATE, ROUND, ROUND_UP, ROUND_DOWN};' ,
21832249 'use crate::exchange::{DECIMAL_PLACES, SIGNIFICANT_DIGITS, TICK_SIZE, NO_PADDING, PAD_WITH_ZERO};' ,
@@ -2220,8 +2286,8 @@ class RustTranspiler {
22202286 fn push(&mut self, value: Value) { self.0.push(value) }
22212287 fn split(&self, separator: Value) -> Value { self.0.split(separator) }
22222288 fn contains_key(&self, key: Value) -> bool { self.0.contains_key(key) }
2223- fn keys(&self) -> Vec< Value> { self.0.keys() }
2224- fn values(&self) -> Vec< Value> { self.0.values() }
2289+ fn keys(&self) -> Value { self.0.keys() }
2290+ fn values(&self) -> Value { self.0.values() }
22252291 fn to_array(&self, x: Value) -> Value { self.0.to_array(x) }
22262292 fn index_of(&self, x: Value) -> Value { self.0.index_of(x) }
22272293 fn join(&self, glue: Value) -> Value { self.0.join(glue) }
@@ -2271,11 +2337,14 @@ class RustTranspiler {
22712337 const rust : string [ ] = [ ] ;
22722338 const methodNames : string [ ] = [ ] ;
22732339
2274- // Pre-compute own method names for disambiguation
2340+ // Pre-compute own method names for disambiguation (exclude manually-implemented ones
2341+ // which won't appear in the generated trait)
22752342 const ownMethodNames = new Set < string > ( ) ;
22762343 for ( const m of methods ) {
22772344 const nameMatch = / \b ( [ a - z A - Z 0 - 9 _ ] + ) \s * \( / . exec ( m . replace ( / ^ a s y n c \s + / , '' ) ) ;
2278- if ( nameMatch ) ownMethodNames . add ( nameMatch [ 1 ] ) ;
2345+ if ( nameMatch && ! MANUALLY_IMPLEMENTED_METHODS . has ( nameMatch [ 1 ] ) ) {
2346+ ownMethodNames . add ( nameMatch [ 1 ] ) ;
2347+ }
22792348 }
22802349
22812350 for ( const m of methods ) {
0 commit comments