@@ -45,7 +45,7 @@ export default class bisq extends Exchange {
4545 'fetchOrderBook' : true ,
4646 'fetchOrders' : false ,
4747 'fetchTicker' : true ,
48- 'fetchTickers' : false ,
48+ 'fetchTickers' : true ,
4949 'fetchTrades' : false ,
5050 } ,
5151 'urls' : {
@@ -120,6 +120,7 @@ export default class bisq extends Exchange {
120120 'wallets/getfundingaddresses' : 1 ,
121121 'wallets/getnetwork' : 1 ,
122122 'wallets/gettransaction' : 1 ,
123+ 'wallets/gettransactions' : 1 ,
123124 'wallets/gettxfeerate' : 1 ,
124125 'wallets/getunusedbsqaddress' : 1 ,
125126 'wallets/lockwallet' : 1 ,
@@ -192,6 +193,7 @@ export default class bisq extends Exchange {
192193 'wallets/getfundingaddresses' : 'api/v1/wallet/addresses' ,
193194 'wallets/getnetwork' : 'api/v1/wallet/network' ,
194195 'wallets/gettransaction' : 'api/v1/wallet/transactions/{txId}' ,
196+ 'wallets/gettransactions' : 'api/v1/wallet/transactions' ,
195197 'wallets/gettxfeerate' : 'api/v1/wallet/tx-fee-rate' ,
196198 'wallets/getunusedbsqaddress' : 'api/v1/wallet/bsq/unused-address' ,
197199 'wallets/lockwallet' : 'api/v1/wallet/lock' ,
@@ -231,6 +233,7 @@ export default class bisq extends Exchange {
231233 'wallets/getfundingaddresses' : 'GET' ,
232234 'wallets/getnetwork' : 'GET' ,
233235 'wallets/gettransaction' : 'GET' ,
236+ 'wallets/gettransactions' : 'GET' ,
234237 'wallets/gettxfeerate' : 'GET' ,
235238 'wallets/getunusedbsqaddress' : 'GET' ,
236239 'wallets/removewalletpassword' : 'DELETE' ,
@@ -320,6 +323,19 @@ export default class bisq extends Exchange {
320323 return Precise . stringDiv ( satoshis , '100000000' ) ;
321324 }
322325
326+ parseCurrencyBalance ( currency : string , rawAmount : string ) : string {
327+ // Convert a raw integer balance amount from the Bisq API to its decimal representation.
328+ // BTC amounts are in satoshis (1 BTC = 1e8 satoshis).
329+ // All other Bisq-native coins (e.g. BSQ) are in their smallest sub-unit (1 unit = 100 sub-units).
330+ if ( rawAmount === undefined ) {
331+ return undefined ;
332+ }
333+ if ( currency === 'BTC' ) {
334+ return Precise . stringDiv ( rawAmount , '100000000' ) ;
335+ }
336+ return Precise . stringDiv ( rawAmount , '100' ) ;
337+ }
338+
323339 safeList2 ( object : Dict , key1 : string , key2 : string , defaultValue = [ ] ) {
324340 const value = this . safeList ( object , key1 ) ;
325341 if ( value !== undefined ) {
@@ -568,14 +584,35 @@ export default class bisq extends Exchange {
568584 /**
569585 * @method
570586 * @name bisq#fetchTickers
571- * @description fetches price tickers for multiple markets, not supported by Bisq
587+ * @description fetches price tickers for all markets, or for the markets specified by `symbols`
572588 * @see https://bisq-network.github.io/slate/#rpc-method-getmarketprice
573- * @param {string[] } [symbols] unified market symbols
589+ * @note specific behavior: calls `Price.GetMarketPrice` once per market; markets whose price feed is temporarily
590+ * unavailable (e.g. BSQ on a fresh daemon) are silently skipped rather than failing the whole call
591+ * @param {string[] } [symbols] unified market symbols to fetch tickers for; defaults to all loaded markets
574592 * @param {object } [params] extra parameters specific to the exchange API endpoint
575- * @returns {object } a dictionary of ticker structures
593+ * @returns {object } a dictionary of [ ticker structures]{ @link https://docs.ccxt.com/?id=ticker-structure}
576594 */
577595 async fetchTickers ( symbols : string [ ] = undefined , params = { } ) : Promise < Tickers > {
578- throw new NotSupported ( this . id + ' fetchTickers() is not supported, use fetchTicker() instead' ) ;
596+ await this . loadMarkets ( ) ;
597+ let targetSymbols = this . symbols ;
598+ if ( symbols !== undefined ) {
599+ targetSymbols = symbols ;
600+ }
601+ const result : Dict = { } ;
602+ for ( let i = 0 ; i < targetSymbols . length ; i ++ ) {
603+ const symbol = this . safeString ( targetSymbols , i ) ;
604+ try {
605+ result [ symbol ] = await this . fetchTicker ( symbol , params ) ;
606+ } catch ( e ) {
607+ // Skip markets whose price feed is unavailable (e.g. BSQ on a fresh daemon
608+ // returns "price feed service has no prices"). Other errors are re-thrown.
609+ if ( e instanceof ExchangeNotAvailable ) {
610+ continue ;
611+ }
612+ throw e ;
613+ }
614+ }
615+ return result as Tickers ;
579616 }
580617
581618 /**
@@ -695,7 +732,7 @@ export default class bisq extends Exchange {
695732 * @name bisq#fetchBalance
696733 * @description query account balances
697734 * @see https://bisq-network.github.io/slate/#service-wallets
698- * @note specific behavior: maps `Wallets.GetBalances` into CCXT balances for BTC and BSQ
735+ * @note specific behavior: maps `Wallets.GetBalances` into CCXT balances; iterates over all currency keys returned by the API
699736 * @param {object } [params] extra parameters specific to the exchange API endpoint
700737 * @returns {object } a [balance structure]{@link https://docs.ccxt.com/?id=balance-structure}
701738 */
@@ -706,21 +743,246 @@ export default class bisq extends Exchange {
706743 const result : Dict = {
707744 'info' : response ,
708745 } ;
709- const btc = this . safeDict ( balances , 'btc' , { } ) ;
710- const bsq = this . safeDict ( balances , 'bsq' , { } ) ;
711- const accountBtc = this . account ( ) ;
712- accountBtc [ 'free' ] = this . safeString2 ( btc , 'available_balance' , 'availableBalance' ) ;
713- accountBtc [ 'used' ] = this . safeString2 ( btc , 'reserved_balance' , 'reservedBalance' ) ;
714- accountBtc [ 'total' ] = this . safeString2 ( btc , 'total_available_balance' , 'totalAvailableBalance' ) ;
715- result [ 'BTC' ] = accountBtc ;
716- const accountBsq = this . account ( ) ;
717- accountBsq [ 'free' ] = this . safeString2 ( bsq , 'available_confirmed_balance' , 'availableConfirmedBalance' ) ;
718- accountBsq [ 'used' ] = this . safeString2 ( bsq , 'unverified_balance' , 'unverifiedBalance' ) ;
719- accountBsq [ 'total' ] = this . safeString2 ( bsq , 'total_available_balance' , 'totalAvailableBalance' ) ;
720- result [ 'BSQ' ] = accountBsq ;
746+ const currencyKeys = Object . keys ( balances ) ;
747+ for ( let i = 0 ; i < currencyKeys . length ; i ++ ) {
748+ const key = currencyKeys [ i ] ;
749+ const data = this . safeDict ( balances , key , { } ) ;
750+ const code = key . toUpperCase ( ) ;
751+ const account = this . account ( ) ;
752+ const freeRaw = this . safeString2 ( data , 'available_balance' , 'availableBalance' ) || this . safeString2 ( data , 'available_confirmed_balance' , 'availableConfirmedBalance' ) ;
753+ const usedRaw = this . safeString2 ( data , 'reserved_balance' , 'reservedBalance' ) || this . safeString2 ( data , 'unverified_balance' , 'unverifiedBalance' ) ;
754+ const totalRaw = this . safeString2 ( data , 'total_available_balance' , 'totalAvailableBalance' ) ;
755+ account [ 'free' ] = this . parseCurrencyBalance ( code , freeRaw ) ;
756+ account [ 'used' ] = this . parseCurrencyBalance ( code , usedRaw ) ;
757+ account [ 'total' ] = this . parseCurrencyBalance ( code , totalRaw ) ;
758+ result [ code ] = account ;
759+ }
721760 return this . safeBalance ( result ) ;
722761 }
723762
763+ parseTransaction ( tx : Dict ) : Transaction {
764+ const txId = this . safeString2 ( tx , 'tx_id' , 'txId' ) ;
765+ const isPending = this . safeBool2 ( tx , 'is_pending' , 'isPending' , false ) ;
766+ const inputSum = this . satoshisToAmount ( this . safeString2 ( tx , 'input_sum' , 'inputSum' ) ) ;
767+ const outputSum = this . satoshisToAmount ( this . safeString2 ( tx , 'output_sum' , 'outputSum' ) ) ;
768+ const fee = this . satoshisToAmount ( this . safeString2 ( tx , 'fee' , 'fee' ) ) ;
769+ let status = 'ok' ;
770+ if ( isPending ) {
771+ status = 'pending' ;
772+ }
773+ return ( {
774+ 'id' : txId ,
775+ 'txid' : txId ,
776+ 'info' : tx ,
777+ 'type' : undefined ,
778+ 'currency' : 'BTC' ,
779+ 'amount' : outputSum ,
780+ 'fee' : { 'currency' : 'BTC' , 'cost' : fee } ,
781+ 'status' : status ,
782+ 'timestamp' : undefined ,
783+ 'datetime' : undefined ,
784+ 'address' : undefined ,
785+ 'addressFrom' : undefined ,
786+ 'addressTo' : undefined ,
787+ 'tag' : undefined ,
788+ 'tagFrom' : undefined ,
789+ 'tagTo' : undefined ,
790+ 'updated' : undefined ,
791+ 'comment' : this . safeString ( tx , 'memo' ) ,
792+ } ) ;
793+ }
794+
795+ /**
796+ * @method
797+ * @name bisq#fetchTransactions
798+ * @description fetch all BTC transactions in the Bisq wallet
799+ * @see https://bisq-network.github.io/slate/#service-wallets
800+ * @param {string } [code] currency code — only 'BTC' is supported
801+ * @param {int } [since] not used by the Bisq API (client-side filter applied when provided)
802+ * @param {int } [limit] not used by the Bisq API (client-side filter applied when provided)
803+ * @param {object } [params] extra parameters specific to the exchange API endpoint
804+ * @returns {object[] } a list of [transaction structures]{@link https://docs.ccxt.com/?id=transaction-structure}
805+ */
806+ async fetchTransactions ( code : Str = undefined , since : Int = undefined , limit : Int = undefined , params = { } ) : Promise < Transaction [ ] > {
807+ await this . loadMarkets ( ) ;
808+ const response = await this . privatePostWalletsGettransactions ( params ) ;
809+ const txInfoList = this . safeList2 ( response , 'tx_info' , 'txInfo' , [ ] ) ;
810+ const transactions = [ ] ;
811+ for ( let i = 0 ; i < txInfoList . length ; i ++ ) {
812+ transactions . push ( this . parseTransaction ( txInfoList [ i ] ) ) ;
813+ }
814+ return this . filterBySinceLimit ( transactions , since , limit ) ;
815+ }
816+
817+ /**
818+ * @method
819+ * @name bisq#fetchDepositAddress
820+ * @description get the first unused BTC funding address in the Bisq wallet
821+ * @see https://bisq-network.github.io/slate/#service-wallets
822+ * @note specific behavior: Bisq has no "create address" RPC; this returns the first unused address from GetFundingAddresses
823+ * @param {string } code currency code — only 'BTC' is supported
824+ * @param {object } [params] extra parameters specific to the exchange API endpoint
825+ * @returns {object } an [address structure]{@link https://docs.ccxt.com/?id=address-structure}
826+ */
827+ async fetchDepositAddress ( code : string , params = { } ) : Promise < DepositAddress > {
828+ await this . loadMarkets ( ) ;
829+ if ( code !== 'BTC' ) {
830+ throw new ExchangeError ( this . id + ' fetchDepositAddress() only supports BTC' ) ;
831+ }
832+ const response = await this . privatePostWalletsGetfundingaddresses ( params ) ;
833+ const addressList = this . safeList2 ( response , 'address_balance_info' , 'addressBalanceInfo' , [ ] ) ;
834+ let unusedAddress = undefined ;
835+ for ( let i = 0 ; i < addressList . length ; i ++ ) {
836+ const entry = addressList [ i ] ;
837+ const isUnused = this . safeBool2 ( entry , 'is_address_unused' , 'isAddressUnused' , false ) ;
838+ if ( isUnused ) {
839+ unusedAddress = entry ;
840+ break ;
841+ }
842+ }
843+ if ( unusedAddress === undefined ) {
844+ throw new ExchangeError ( this . id + ' fetchDepositAddress() no unused BTC address available' ) ;
845+ }
846+ const address = this . safeString ( unusedAddress , 'address' ) ;
847+ this . checkAddress ( address ) ;
848+ return {
849+ 'currency' : code ,
850+ 'address' : address ,
851+ 'tag' : undefined ,
852+ 'network' : undefined ,
853+ 'info' : unusedAddress ,
854+ } ;
855+ }
856+
857+ /**
858+ * @method
859+ * @name bisq#fetchPaymentAccounts
860+ * @description fetch all saved fiat payment accounts from the Bisq daemon
861+ * @see https://bisq-network.github.io/slate/#service-paymentaccounts
862+ * @param {object } [params] extra parameters specific to the exchange API endpoint
863+ * @returns {object[] } array of normalised payment account structures
864+ */
865+ async fetchPaymentAccounts ( params = { } ) : Promise < Dict [ ] > {
866+ await this . loadMarkets ( ) ;
867+ const response = await this . privatePostPaymentaccountsGetpaymentaccounts ( params ) ;
868+ const accounts = this . safeList2 ( response , 'payment_accounts' , 'paymentAccounts' , [ ] ) ;
869+ const result = [ ] ;
870+ for ( let i = 0 ; i < accounts . length ; i ++ ) {
871+ result . push ( this . parsePaymentAccount ( this . safeDict ( accounts , i , { } ) ) ) ;
872+ }
873+ return result ;
874+ }
875+
876+ /**
877+ * Parse a raw Bisq payment account dict into a normalised structure.
878+ */
879+ parsePaymentAccount ( account : Dict ) : Dict {
880+ const id = this . safeString2 ( account , 'id' , 'account_id' ) ;
881+ const name = this . safeString ( account , 'accountName' ) || this . safeString ( account , 'account_name' ) ;
882+ const paymentMethod = this . safeDict2 ( account , 'paymentMethod' , 'payment_method' , { } ) ;
883+ const methodId = this . safeString ( paymentMethod , 'id' ) ;
884+ const tradeCurrencies = this . safeList2 ( account , 'tradeCurrencies' , 'trade_currencies' , [ ] ) ;
885+ const currencyCodes = [ ] ;
886+ for ( let i = 0 ; i < tradeCurrencies . length ; i ++ ) {
887+ const tc = this . safeDict ( tradeCurrencies , i , { } ) ;
888+ const code = this . safeString ( tc , 'code' ) ;
889+ if ( code !== undefined ) {
890+ currencyCodes . push ( code ) ;
891+ }
892+ }
893+ return {
894+ 'id' : id ,
895+ 'name' : name ,
896+ 'paymentMethodId' : methodId ,
897+ 'currencies' : currencyCodes ,
898+ 'info' : account ,
899+ } ;
900+ }
901+
902+ /**
903+ * @method
904+ * @name bisq#createCryptoCurrencyPaymentAccount
905+ * @description create an altcoin payment account in the Bisq daemon
906+ * @see https://bisq-network.github.io/slate/#service-paymentaccounts
907+ * @param {string } currency uppercase currency code, e.g. 'XMR'
908+ * @param {string } address the altcoin wallet address
909+ * @param {object } [params] extra parameters
910+ * @param {string } [params.accountName] human-readable name (defaults to '<CURRENCY> account')
911+ * @param {boolean } [params.tradeInstant] whether to enable instant trade (default false)
912+ * @returns {object } normalised payment account structure
913+ */
914+ async createCryptoCurrencyPaymentAccount ( currency : string , address : string , params = { } ) : Promise < Dict > {
915+ await this . loadMarkets ( ) ;
916+ const code = currency . toUpperCase ( ) ;
917+ const accountName = this . safeString ( params , 'accountName' ) || this . safeString ( params , 'account_name' ) || ( code + ' account' ) ;
918+ const tradeInstant = this . safeBool2 ( params , 'tradeInstant' , 'trade_instant' , false ) ;
919+ const request = {
920+ 'account_name' : accountName ,
921+ 'currency_code' : code ,
922+ 'address' : address ,
923+ 'trade_instant' : tradeInstant ,
924+ } ;
925+ const omitKeys = [ 'accountName' , 'account_name' , 'tradeInstant' , 'trade_instant' ] ;
926+ const response = await this . privatePostPaymentaccountsCreatecryptocurrencypaymentaccount ( this . extend ( request , this . omit ( params , omitKeys ) ) ) ;
927+ const paymentAccount = this . safeDict2 ( response , 'payment_account' , 'paymentAccount' , { } ) ;
928+ return this . parsePaymentAccount ( paymentAccount ) ;
929+ }
930+
931+ /**
932+ * @method
933+ * @name bisq#createFiatPaymentAccount
934+ * @description create a fiat payment account using a pre-filled Bisq account form JSON
935+ * @see https://bisq-network.github.io/slate/#service-paymentaccounts
936+ * @note specific behavior: obtain the blank form via GetPaymentAccountForm(paymentMethodId), fill the required fields, then pass the JSON string here
937+ * @param {string } paymentMethodId Bisq payment method identifier, e.g. 'SEPA', 'ZELLE'
938+ * @param {string } formJson filled payment account form as a JSON string
939+ * @param {object } [params] extra parameters specific to the exchange API endpoint
940+ * @returns {object } normalised payment account structure
941+ */
942+ async createFiatPaymentAccount ( paymentMethodId : string , formJson : string , params = { } ) : Promise < Dict > {
943+ await this . loadMarkets ( ) ;
944+ const request = {
945+ 'payment_account_form' : formJson ,
946+ } ;
947+ const response = await this . privatePostPaymentaccountsCreatepaymentaccount ( this . extend ( request , params ) ) ;
948+ const paymentAccount = this . safeDict2 ( response , 'payment_account' , 'paymentAccount' , { } ) ;
949+ return this . parsePaymentAccount ( paymentAccount ) ;
950+ }
951+
952+ /**
953+ * @method
954+ * @name bisq#createPaymentAccount
955+ * @description create a new payment account (altcoin or fiat) in the Bisq daemon
956+ * @see https://bisq-network.github.io/slate/#service-paymentaccounts
957+ * @note specific behavior: pass params.address for altcoin/crypto accounts; pass params.paymentMethodId + params.formJson for fiat accounts
958+ * @param {string } currency currency code, e.g. 'XMR' for altcoin or 'EUR' for fiat
959+ * @param {object } [params] extra parameters
960+ * @param {string } [params.address] altcoin wallet address (required for crypto accounts)
961+ * @param {string } [params.accountName] human-readable name (crypto accounts only, defaults to '<CURRENCY> account')
962+ * @param {boolean } [params.tradeInstant] enable instant trade (crypto accounts only, default false)
963+ * @param {string } [params.paymentMethodId] Bisq payment method id (required for fiat accounts, e.g. 'SEPA')
964+ * @param {string } [params.formJson] filled payment account form JSON string (required for fiat accounts)
965+ * @returns {object } normalised payment account structure
966+ */
967+ async createPaymentAccount ( currency : string , params = { } ) : Promise < Dict > {
968+ await this . loadMarkets ( ) ;
969+ const address = this . safeString ( params , 'address' ) || this . safeString ( params , 'walletAddress' ) ;
970+ const paymentMethodId = this . safeString ( params , 'paymentMethodId' ) || this . safeString ( params , 'payment_method_id' ) ;
971+ if ( address !== undefined ) {
972+ const omitKeys = [ 'address' , 'walletAddress' ] ;
973+ return await this . createCryptoCurrencyPaymentAccount ( currency , address , this . omit ( params , omitKeys ) ) ;
974+ }
975+ if ( paymentMethodId !== undefined ) {
976+ const formJson = this . safeString ( params , 'formJson' ) || this . safeString ( params , 'form_json' ) ;
977+ if ( formJson === undefined ) {
978+ throw new ExchangeError ( this . id + ' createPaymentAccount() requires params.formJson for fiat accounts' ) ;
979+ }
980+ const omitKeys = [ 'paymentMethodId' , 'payment_method_id' , 'formJson' , 'form_json' ] ;
981+ return await this . createFiatPaymentAccount ( paymentMethodId , formJson , this . omit ( params , omitKeys ) ) ;
982+ }
983+ throw new ExchangeError ( this . id + ' createPaymentAccount() requires params.address (crypto) or params.paymentMethodId + params.formJson (fiat)' ) ;
984+ }
985+
724986 /**
725987 * @method
726988 * @name bisq#createOrder
0 commit comments