From d980ce42dd0890ea3cd140e474f4e42f0ffaf711 Mon Sep 17 00:00:00 2001 From: Gabriel Masclef Date: Fri, 2 May 2025 12:41:46 -0300 Subject: [PATCH 01/12] Feat: banxaGetCoins new endpoint --- .../src/externalServices/banxa.ts | 40 ++++++++ .../src/lib/expressapp.ts | 11 +++ .../test/integration/banxa.js | 91 +++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/packages/bitcore-wallet-service/src/externalServices/banxa.ts b/packages/bitcore-wallet-service/src/externalServices/banxa.ts index 995d12b4d0..d42b50530b 100644 --- a/packages/bitcore-wallet-service/src/externalServices/banxa.ts +++ b/packages/bitcore-wallet-service/src/externalServices/banxa.ts @@ -55,6 +55,46 @@ export class BanxaService { return auth; } + banxaGetCoins(req): Promise { + return new Promise((resolve, reject) => { + const keys = this.banxaGetKeys(req); + const API = keys.API; + const API_KEY = keys.API_KEY; + const SECRET_KEY = keys.SECRET_KEY; + + if (!checkRequired(req.body, ['orderType'])) { + return reject(new ClientError("Banxa's request missing arguments")); + } + if (!['buy', 'sell'].includes(req.body.orderType)) { + return reject(new ClientError("Banxa's 'orderType' property must be 'sell' or 'buy'")); + } + + const UriPath = `/coins/${req.body.orderType}`; + const URL: string = API + UriPath; + const auth = this.getBanxaSignature('get', UriPath, API_KEY, SECRET_KEY); + + const headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${auth}` + }; + + this.request.get( + URL, + { + headers, + json: true + }, + (err, data) => { + if (err) { + return reject(err.body ? err.body : err); + } else { + return resolve(data.body ? data.body : data); + } + } + ); + }); + } + banxaGetPaymentMethods(req): Promise { return new Promise((resolve, reject) => { const keys = this.banxaGetKeys(req); diff --git a/packages/bitcore-wallet-service/src/lib/expressapp.ts b/packages/bitcore-wallet-service/src/lib/expressapp.ts index 8ab6d08f69..f505e004a1 100644 --- a/packages/bitcore-wallet-service/src/lib/expressapp.ts +++ b/packages/bitcore-wallet-service/src/lib/expressapp.ts @@ -1531,6 +1531,17 @@ export class ExpressApp { } }); + router.post('/v1/service/banxa/getCoins', async (req, res) => { + let server: WalletService, response; + try { + server = getServer(req, res); + response = await server.externalServices.banxa.banxaGetCoins(req); + return res.json(response); + } catch (ex) { + return returnError(ex, res, req); + } + }); + router.post('/v1/service/banxa/paymentMethods', async (req, res) => { let server: WalletService, response; try { diff --git a/packages/bitcore-wallet-service/test/integration/banxa.js b/packages/bitcore-wallet-service/test/integration/banxa.js index fbfa86a7a1..477ca9490a 100644 --- a/packages/bitcore-wallet-service/test/integration/banxa.js +++ b/packages/bitcore-wallet-service/test/integration/banxa.js @@ -127,6 +127,97 @@ let server, wallet, fakeRequest, req; }); }); + describe('#banxaGetCoins', () => { + beforeEach(() => { + req = { + headers: {}, + body: { + env: 'sandbox', + orderType: 'buy' + } + } + server.externalServices.banxa.request = fakeRequest; + }); + + it('should work properly if req is OK for buy', async () => { + try { + const data = await server.externalServices.banxa.banxaGetCoins(req); + should.exist(data); + } catch (err) { + should.not.exist(err); + } + }); + + it('should work properly if req is OK for sell', async () => { + try { + req.body.orderType = 'sell' + const data = await server.externalServices.banxa.banxaGetCoins(req); + should.exist(data); + } catch (err) { + should.not.exist(err); + } + }); + + it('should work properly if req is OK for web', async () => { + req.body.context = 'web'; + try { + const data = await server.externalServices.banxa.banxaGetCoins(req); + should.exist(data); + } catch (err) { + should.not.exist(err); + } + }); + + it('should return error if get returns error', async () => { + const fakeRequest2 = { + get: (_url, _opts, _cb) => { return _cb(new Error('Error'), null) }, + }; + + server.externalServices.banxa.request = fakeRequest2; + try { + const data = await server.externalServices.banxa.banxaGetCoins(req); + should.not.exist(data); + } catch (err) { + should.exist(err); + err.message.should.equal('Error'); + }; + }); + + it('should return error if orderType is not buy or sell', async () => { + try { + req.body.orderType = 'wrongOrderType'; + const data = await server.externalServices.banxa.banxaGetCoins(req); + should.not.exist(data); + } catch (err) { + should.exist(err); + err.message.should.equal(`Banxa's 'orderType' property must be 'sell' or 'buy'`); + } + }); + + it('should return error if orderType is not present', async () => { + try { + delete req.body.orderType; + const data = await server.externalServices.banxa.banxaGetCoins(req); + should.not.exist(data); + } catch (err) { + should.exist(err); + err.message.should.equal(`Banxa's request missing arguments`); + } + }); + + it('should return error if banxa is commented in config', async () => { + config.banxa = undefined; + + try { + const data = await server.externalServices.banxa.banxaGetCoins(req); + should.not.exist(data); + } catch (err) { + should.exist(err); + err.message.should.equal('Banxa missing credentials'); + } + }); + }); + describe('#banxaGetQuote', () => { beforeEach(() => { req = { From fb4faf094f0aa8f75051db8b018ba4816fa044ee Mon Sep 17 00:00:00 2001 From: Gabriel Masclef Date: Fri, 2 May 2025 12:45:47 -0300 Subject: [PATCH 02/12] Types for external services files --- .../src/externalServices/banxa.ts | 6 ++--- .../src/externalServices/moonpay.ts | 22 +++++++++---------- .../src/externalServices/oneInch.ts | 2 +- .../src/externalServices/ramp.ts | 6 ++--- .../src/externalServices/sardine.ts | 6 ++--- .../src/externalServices/simplex.ts | 2 +- .../src/externalServices/thorswap.ts | 4 ++-- .../src/externalServices/transak.ts | 4 ++-- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/bitcore-wallet-service/src/externalServices/banxa.ts b/packages/bitcore-wallet-service/src/externalServices/banxa.ts index d42b50530b..fff8b839c4 100644 --- a/packages/bitcore-wallet-service/src/externalServices/banxa.ts +++ b/packages/bitcore-wallet-service/src/externalServices/banxa.ts @@ -102,7 +102,7 @@ export class BanxaService { const API_KEY = keys.API_KEY; const SECRET_KEY = keys.SECRET_KEY; - let qs = []; + let qs: string[] = []; if (req.body.source) qs.push('source=' + req.body.source); if (req.body.target) qs.push('target=' + req.body.target); @@ -143,7 +143,7 @@ export class BanxaService { return reject(new ClientError("Banxa's request missing arguments")); } - let qs = []; + let qs: string[] = []; qs.push('source=' + req.body.source); qs.push('target=' + req.body.target); @@ -230,7 +230,7 @@ export class BanxaService { return reject(new ClientError("Banxa's request missing arguments")); } - let qs = []; + let qs: string[] = []; if (req.body.fx_currency) qs.push('fx_currency=' + req.body.fx_currency); const UriPath = `/orders/${req.body.order_id}${qs.length > 0 ? '?' + qs.join('&') : ''}`; diff --git a/packages/bitcore-wallet-service/src/externalServices/moonpay.ts b/packages/bitcore-wallet-service/src/externalServices/moonpay.ts index ac4efaf753..10827a444f 100644 --- a/packages/bitcore-wallet-service/src/externalServices/moonpay.ts +++ b/packages/bitcore-wallet-service/src/externalServices/moonpay.ts @@ -79,7 +79,7 @@ export class MoonpayService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; qs.push('apiKey=' + API_KEY); qs.push('baseCurrencyAmount=' + req.body.baseCurrencyAmount); qs.push('baseCurrencyCode=' + req.body.baseCurrencyCode); @@ -121,7 +121,7 @@ export class MoonpayService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; qs.push('apiKey=' + API_KEY); qs.push('quoteCurrencyCode=' + req.body.quoteCurrencyCode); qs.push('baseCurrencyAmount=' + req.body.baseCurrencyAmount); @@ -162,7 +162,7 @@ export class MoonpayService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; qs.push('apiKey=' + API_KEY); qs.push('baseCurrencyCode=' + encodeURIComponent(req.body.baseCurrencyCode)); if (req.body.areFeesIncluded) qs.push('areFeesIncluded=' + encodeURIComponent(req.body.areFeesIncluded)); @@ -210,7 +210,7 @@ export class MoonpayService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; qs.push('apiKey=' + API_KEY); qs.push('currencyCode=' + encodeURIComponent(req.body.currencyCode)); qs.push('walletAddress=' + encodeURIComponent(req.body.walletAddress)); @@ -257,7 +257,7 @@ export class MoonpayService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; qs.push('apiKey=' + API_KEY); qs.push('baseCurrencyCode=' + encodeURIComponent(req.body.baseCurrencyCode)); qs.push('baseCurrencyAmount=' + encodeURIComponent(req.body.baseCurrencyAmount)); @@ -304,9 +304,9 @@ export class MoonpayService { const headers = { 'Content-Type': 'application/json' }; - let URL: string; + let URL: string = ''; - let qs = []; + let qs: string[] = []; qs.push('apiKey=' + API_KEY); if (req.body.transactionId) { URL = API + `/v1/transactions/${req.body.transactionId}?${qs.join('&')}`; @@ -344,9 +344,9 @@ export class MoonpayService { const headers = { 'Content-Type': 'application/json' }; - let URL: string; + let URL: string = ''; - let qs = []; + let qs: string[] = []; qs.push('apiKey=' + API_KEY); if (req.body.transactionId) { URL = API + `/v3/sell_transactions/${req.body.transactionId}?${qs.join('&')}`; @@ -385,7 +385,7 @@ export class MoonpayService { Authorization: 'Api-Key ' + SECRET_KEY, Accept: 'application/json' }; - let URL: string; + let URL: string = ''; if (req.body.transactionId) { URL = API + `/v3/sell_transactions/${req.body.transactionId}`; @@ -420,7 +420,7 @@ export class MoonpayService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; qs.push('apiKey=' + API_KEY); const URL = API + `/v3/accounts/me?${qs.join('&')}`; diff --git a/packages/bitcore-wallet-service/src/externalServices/oneInch.ts b/packages/bitcore-wallet-service/src/externalServices/oneInch.ts index e9f425352f..09affb5556 100644 --- a/packages/bitcore-wallet-service/src/externalServices/oneInch.ts +++ b/packages/bitcore-wallet-service/src/externalServices/oneInch.ts @@ -62,7 +62,7 @@ export class OneInchService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; qs.push('fromTokenAddress=' + req.body.fromTokenAddress); qs.push('toTokenAddress=' + req.body.toTokenAddress); qs.push('amount=' + req.body.amount); diff --git a/packages/bitcore-wallet-service/src/externalServices/ramp.ts b/packages/bitcore-wallet-service/src/externalServices/ramp.ts index 57a9c4450f..c38ca985db 100644 --- a/packages/bitcore-wallet-service/src/externalServices/ramp.ts +++ b/packages/bitcore-wallet-service/src/externalServices/ramp.ts @@ -134,7 +134,7 @@ export class RampService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; qs.push('hostApiKey=' + API_KEY); qs.push('selectedCountryCode=' + encodeURIComponent(req.body.selectedCountryCode)); if (req.body.finalUrl) qs.push('finalUrl=' + encodeURIComponent(req.body.finalUrl)); @@ -174,7 +174,7 @@ export class RampService { }; let URL: string; - let qs = []; + let qs: string[] = []; // "Buy" and "Sell" features use the same properties. Use "flow" to target the correct endpoint qs.push('hostApiKey=' + API_KEY); if (req.body.currencyCode) qs.push('currencyCode=' + encodeURIComponent(req.body.currencyCode)); @@ -218,7 +218,7 @@ export class RampService { }; let URL: string; - let qs = []; + let qs: string[] = []; qs.push('secret=' + req.body.saleViewToken); URL = API + `/host-api/v3/offramp/sale/${req.body.id}?${qs.join('&')}`; diff --git a/packages/bitcore-wallet-service/src/externalServices/sardine.ts b/packages/bitcore-wallet-service/src/externalServices/sardine.ts index 607760e3ea..1cb04ac8cf 100644 --- a/packages/bitcore-wallet-service/src/externalServices/sardine.ts +++ b/packages/bitcore-wallet-service/src/externalServices/sardine.ts @@ -50,7 +50,7 @@ export class SardineService { Authorization: `Basic ${secretBase64}`, }; - let qs = []; + let qs: string[] = []; qs.push('asset_type=' + req.body.asset_type); qs.push('network=' + req.body.network); qs.push('total=' + req.body.total); @@ -204,8 +204,8 @@ export class SardineService { Authorization: `Basic ${secretBase64}`, }; - let qs = []; - let URL: string; + let qs: string[] = []; + let URL: string = ''; if (req.body.orderId) { URL = API + `/v1/orders/${req.body.orderId}`; diff --git a/packages/bitcore-wallet-service/src/externalServices/simplex.ts b/packages/bitcore-wallet-service/src/externalServices/simplex.ts index feadbec32c..680c89d816 100644 --- a/packages/bitcore-wallet-service/src/externalServices/simplex.ts +++ b/packages/bitcore-wallet-service/src/externalServices/simplex.ts @@ -121,7 +121,7 @@ export class SimplexService { headers['x-country-code'] = req.body.userCountry.toUpperCase(); } - let qs = []; + let qs: string[] = []; qs.push('base_currency=' + req.body.base_currency); qs.push('base_amount=' + req.body.base_amount); qs.push('quote_currency=' + req.body.quote_currency); diff --git a/packages/bitcore-wallet-service/src/externalServices/thorswap.ts b/packages/bitcore-wallet-service/src/externalServices/thorswap.ts index 080eeda9de..0f8ff41b11 100644 --- a/packages/bitcore-wallet-service/src/externalServices/thorswap.ts +++ b/packages/bitcore-wallet-service/src/externalServices/thorswap.ts @@ -82,7 +82,7 @@ export class ThorswapService { 'x-api-key': API_KEY }; - let qs = []; + let qs: string[] = []; qs.push('categories=' + (req?.body?.categories ?? 'all')); const uriPath: string = req?.body?.includeDetails ? '/tokenlist/utils/currencies/details' : '/tokenlist/utils/currencies'; @@ -119,7 +119,7 @@ export class ThorswapService { 'x-api-key': API_KEY }; - let qs = []; + let qs: string[] = []; if (!checkRequired(req.body, ['sellAsset', 'buyAsset', 'sellAmount'])) { return reject(new ClientError("Thorswap's request missing arguments")); } diff --git a/packages/bitcore-wallet-service/src/externalServices/transak.ts b/packages/bitcore-wallet-service/src/externalServices/transak.ts index b97720b622..8b90294837 100644 --- a/packages/bitcore-wallet-service/src/externalServices/transak.ts +++ b/packages/bitcore-wallet-service/src/externalServices/transak.ts @@ -143,7 +143,7 @@ export class TransakService { Accept: 'application/json', }; - let qs = []; + let qs: string[] = []; qs.push('partnerApiKey=' + API_KEY); qs.push('fiatCurrency=' + req.body.fiatCurrency); qs.push('cryptoCurrency=' + req.body.cryptoCurrency); @@ -200,7 +200,7 @@ export class TransakService { 'Content-Type': 'application/json' }; - let qs = []; + let qs: string[] = []; // Recommended parameters to customize from the app if (req.body.walletAddress) qs.push('walletAddress=' + encodeURIComponent(req.body.walletAddress)); if (req.body.disableWalletAddressForm) qs.push('disableWalletAddressForm=' + encodeURIComponent(req.body.disableWalletAddressForm)); From 83d1bc4ea9ba9ce3f6cf2aba38df202ad6bb2a7d Mon Sep 17 00:00:00 2001 From: lyambo Date: Mon, 5 May 2025 10:15:17 -0400 Subject: [PATCH 03/12] solana support --- packages/bitcore-wallet-client/src/lib/api.ts | 22 +- .../src/lib/common/constants.ts | 16 +- .../src/lib/common/utils.ts | 38 +- .../src/lib/credentials.ts | 12 +- packages/bitcore-wallet-client/src/lib/key.ts | 307 ++++++++++++---- .../bitcore-wallet-client/src/lib/payproV2.ts | 3 +- .../bitcore-wallet-client/src/lib/verifier.ts | 3 +- .../bitcore-wallet-client/test/api.test.js | 38 ++ .../src/lib/blockchainexplorer.ts | 6 +- .../src/lib/blockchainexplorers/v8.ts | 3 +- .../src/lib/blockchainmonitor.ts | 1 + .../src/lib/chain/index.ts | 4 +- .../src/lib/chain/sol/index.ts | 338 ++++++++++++++++++ .../src/lib/common/constants.ts | 15 +- .../src/lib/common/defaults.ts | 18 +- .../src/lib/fiatrateservice.ts | 2 +- .../src/lib/model/address.ts | 14 +- .../src/lib/model/copayer.ts | 5 +- .../src/lib/model/preferences.ts | 4 + .../src/lib/model/txproposal.ts | 42 ++- .../src/lib/model/wallet.ts | 14 +- .../src/lib/pushnotificationsservice.ts | 3 +- .../bitcore-wallet-service/src/lib/server.ts | 39 +- 23 files changed, 813 insertions(+), 134 deletions(-) create mode 100644 packages/bitcore-wallet-service/src/lib/chain/sol/index.ts diff --git a/packages/bitcore-wallet-client/src/lib/api.ts b/packages/bitcore-wallet-client/src/lib/api.ts index 3e8622bb1a..188021dd42 100644 --- a/packages/bitcore-wallet-client/src/lib/api.ts +++ b/packages/bitcore-wallet-client/src/lib/api.ts @@ -32,7 +32,8 @@ const Bitcore_ = { op: CWC.BitcoreLib, xrp: CWC.BitcoreLib, doge: CWC.BitcoreLibDoge, - ltc: CWC.BitcoreLibLtc + ltc: CWC.BitcoreLibLtc, + sol: CWC.BitcoreLib, }; const NetworkChar = { @@ -707,6 +708,7 @@ export class API extends EventEmitter { case 'arb': case 'base': case 'op': + case 'sol': const unsignedTxs = t.uncheckedSerialize(); const signedTxs = []; for (let index = 0; index < signatures.length; index++) { @@ -756,6 +758,7 @@ export class API extends EventEmitter { * @param {string} [opts.customData] * @param {string} [opts.coin] * @param {string} [opts.hardwareSourcePublicKey] + * @param {string} [opts.clientDerivedPublicKey] * @param {function} cb Callback function in the standard form (err, wallet) * @returns {API} Returns instance of API wallet */ @@ -786,7 +789,8 @@ export class API extends EventEmitter { xPubKey, requestPubKey, customData: encCustomData, - hardwareSourcePublicKey: opts.hardwareSourcePublicKey + hardwareSourcePublicKey: opts.hardwareSourcePublicKey, + clientDerivedPublicKey: opts.clientDerivedPublicKey }; if (opts.dryRun) args.dryRun = true; @@ -941,7 +945,8 @@ export class API extends EventEmitter { usePurpose48: n > 1, useNativeSegwit: !!opts.useNativeSegwit, segwitVersion: opts.segwitVersion, - hardwareSourcePublicKey: c.hardwareSourcePublicKey + hardwareSourcePublicKey: c.hardwareSourcePublicKey, + clientDerivedPublicKey: c.clientDerivedPublicKey }; this.request.post('/v2/wallets/', args, (err, res) => { if (err) return cb(err); @@ -967,7 +972,8 @@ export class API extends EventEmitter { { coin, chain, - hardwareSourcePublicKey: c.hardwareSourcePublicKey + hardwareSourcePublicKey: c.hardwareSourcePublicKey, + clientDerivedPublicKey: c.clientDerivedPublicKey }, (err, wallet) => { if (err) return cb(err); @@ -2806,7 +2812,8 @@ export class API extends EventEmitter { network: opts.network, account: opts.account, n: opts.n, - use0forBCH: opts.use0forBCH // only used for server assisted import + use0forBCH: opts.use0forBCH, // only used for server assisted import + algo: opts.algo }); if (copayerIdAlreadyTested[c.copayerId + ':' + opts.n]) { @@ -2831,6 +2838,7 @@ export class API extends EventEmitter { ['eth', 'base', 'livenet'], ['eth', 'op', 'livenet'], ['xrp', 'xrp', 'livenet'], + ['sol', 'sol', 'livenet'], ['doge', 'doge', 'livenet'], ['ltc', 'ltc', 'livenet'], ['btc', 'btc', 'livenet', true], @@ -2868,7 +2876,8 @@ export class API extends EventEmitter { network: opt[2], account: 0, n: opt[3] ? 2 : 1, - use0forBCH: opt[4] + use0forBCH: opt[4], + algo: opt[5], }; generateCredentials(key, optsObj); } @@ -3122,6 +3131,7 @@ export class API extends EventEmitter { { chain: 'arb', tokenAddresses: wallet.status.preferences.arbTokenAddresses, multisigInfo: wallet.status.preferences.multisigArbInfo, tokenOpts: Constants.ARB_TOKEN_OPTS, tokenUrlPath: 'arb' }, { chain: 'op', tokenAddresses: wallet.status.preferences.opTokenAddresses, multisigInfo: wallet.status.preferences.multisigOpInfo, tokenOpts: Constants.OP_TOKEN_OPTS, tokenUrlPath: 'op' }, { chain: 'base', tokenAddresses: wallet.status.preferences.baseTokenAddresses, multisigInfo: wallet.status.preferences.multisigBaseInfo, tokenOpts: Constants.BASE_TOKEN_OPTS, tokenUrlPath: 'base' }, + { chain: 'sol', tokenAddresses: wallet.status.preferences.solTokenAddresses, multisigInfo: wallet.status.preferences.multisigBaseInfo, tokenOpts: Constants.SOL_TOKEN_OPTS, tokenUrlPath: 'sol' }, ]; for (let config of chainConfigurations) { diff --git a/packages/bitcore-wallet-client/src/lib/common/constants.ts b/packages/bitcore-wallet-client/src/lib/common/constants.ts index 5d19a8d846..8b8f0a5164 100644 --- a/packages/bitcore-wallet-client/src/lib/common/constants.ts +++ b/packages/bitcore-wallet-client/src/lib/common/constants.ts @@ -24,6 +24,18 @@ export const Constants = { REQUEST_KEY_AUTH: 'm/2' // relative to BASE }, BIP45_SHARED_INDEX: 0x80000000 - 1, + CURVES: { + ED25519: 'ed25519', + SECP256K1: 'secp256k1', + }, + ALGOS: { + EDDSA: 'EDDSA', + ECDSA: 'ECDSA' + }, + CURVE_KEY: { + ED25519: 'ed25519', + BITCOIN: 'bitcoin', + }, // there is no need to add new entries here ( only for backwards compatiblity ) BITPAY_SUPPORTED_ETH_ERC20: [ @@ -41,14 +53,16 @@ export const Constants = { 'usdt' ], - CHAINS: ['btc', 'bch', 'eth', 'matic', 'xrp', 'doge', 'ltc', 'arb', 'base', 'op'], + CHAINS: ['btc', 'bch', 'eth', 'matic', 'xrp', 'doge', 'ltc', 'arb', 'base', 'op', 'sol'], UTXO_CHAINS: ['btc', 'bch', 'doge', 'ltc'], EVM_CHAINS: ['eth', 'matic', 'arb', 'base', 'op'], + SVM_CHAINS: ['sol'], ETH_TOKEN_OPTS: CWC.Constants.ETH_TOKEN_OPTS, MATIC_TOKEN_OPTS: CWC.Constants.MATIC_TOKEN_OPTS, ARB_TOKEN_OPTS: CWC.Constants.ARB_TOKEN_OPTS, BASE_TOKEN_OPTS: CWC.Constants.BASE_TOKEN_OPTS, OP_TOKEN_OPTS: CWC.Constants.OP_TOKEN_OPTS, + SOL_TOKEN_OPTS: CWC.Constants.SOL_TOKEN_OPTS, UNITS: CWC.Constants.UNITS, EVM_CHAINSUFFIXMAP: { eth: 'e', diff --git a/packages/bitcore-wallet-client/src/lib/common/utils.ts b/packages/bitcore-wallet-client/src/lib/common/utils.ts index 23e46d8360..d83a9b52e6 100644 --- a/packages/bitcore-wallet-client/src/lib/common/utils.ts +++ b/packages/bitcore-wallet-client/src/lib/common/utils.ts @@ -26,7 +26,8 @@ const Bitcore_ = { op: Bitcore, xrp: Bitcore, doge: BitcoreLibDoge, - ltc: BitcoreLibLtc + ltc: BitcoreLibLtc, + sol: Bitcore }; const PrivateKey = Bitcore.PrivateKey; const PublicKey = Bitcore.PublicKey; @@ -181,16 +182,17 @@ export class Utils { network, chain, escrowInputs?, - hardwareSourcePublicKey? + hardwareSourcePublicKey?, + clientDerivedPublicKey? ) { $.checkArgument(Object.values(Constants.SCRIPT_TYPES).includes(scriptType)); - - if (hardwareSourcePublicKey) { - const bitcoreAddress = Deriver.getAddress(chain.toUpperCase(), network, hardwareSourcePublicKey, scriptType); + const externSourcePublicKey = hardwareSourcePublicKey || clientDerivedPublicKey; + if (externSourcePublicKey) { + const bitcoreAddress = Deriver.getAddress(chain.toUpperCase(), network, externSourcePublicKey, scriptType); return { address: bitcoreAddress.toString(), path, - publicKeys: [hardwareSourcePublicKey] + publicKeys: [externSourcePublicKey] } } @@ -467,13 +469,17 @@ export class Utils { } const unsignedTxs = []; // If it is a token swap its an already created ERC20 transaction so we skip it and go directly to ETH transaction create - const isERC20 = tokenAddress && !payProUrl && !isTokenSwap; + const isToken = tokenAddress && !payProUrl && !isTokenSwap; const isMULTISIG = multisigContractAddress; const chainName = chain.toUpperCase(); + let tokenType = 'ERC20' + if (chainName === 'SOL') { + tokenType = 'SPL'; + } const _chain = isMULTISIG ? chainName + 'MULTISIG' - : isERC20 - ? chainName + 'ERC20' + : isToken + ? chainName + tokenType : chainName; if (multiSendContractAddress) { @@ -503,7 +509,7 @@ export class Utils { ...recepient, tag: _tag ? Number(_tag) : undefined, chain: _chain, - nonce: Number(txp.nonce) + Number(index), + nonce: this.formatNonce(chainName, txp.nonce, index), recipients: [recepient] }); unsignedTxs.push(rawTx); @@ -515,8 +521,8 @@ export class Utils { ...recipients[index], tag: destinationTag ? Number(destinationTag) : undefined, chain: _chain, - nonce: Number(txp.nonce) + Number(index), - recipients: [recipients[index]] + nonce: this.formatNonce(chainName, txp.nonce, index), + recipients: [recipients[index]], }); unsignedTxs.push(rawTx); } @@ -525,6 +531,14 @@ export class Utils { } } + static formatNonce(chain, nonce, index) { + if (Constants.SVM_CHAINS.includes(chain.toLowerCase())) { + return nonce + } else { + return Number(nonce) + Number(index) + } + } + static getCurrencyCodeFromCoinAndChain(coin: string, chain: string): string { if (coin.toLowerCase() === chain.toLowerCase()) { return coin.toUpperCase(); diff --git a/packages/bitcore-wallet-client/src/lib/credentials.ts b/packages/bitcore-wallet-client/src/lib/credentials.ts index 346c6f2cd1..ed659ee1fe 100644 --- a/packages/bitcore-wallet-client/src/lib/credentials.ts +++ b/packages/bitcore-wallet-client/src/lib/credentials.ts @@ -43,7 +43,8 @@ export class Credentials { 'keyId', // this is only for information 'token', // this is for a ERC20 token 'multisigEthInfo', // this is for a MULTISIG eth wallet - 'hardwareSourcePublicKey' // public key from a hardware device for this copayer + 'hardwareSourcePublicKey', // public key from a hardware device for this copayer + 'clientDerivedPublicKey' // for public keys generated client side ]; version: number; account: number; @@ -78,6 +79,7 @@ export class Credentials { externalSource?: boolean; // deprecated property? hardwareSourcePublicKey: string; personalEncryptingKey: string; + clientDerivedPublicKey: string constructor() { this.version = 2; @@ -154,7 +156,7 @@ export class Credentials { requestPubKey: x.requestPubKey } ]; - + x.clientDerivedPublicKey = opts.clientDerivedPublicKey return x; } @@ -248,6 +250,8 @@ export class Credentials { chainPath = '3'; } else if (chain == 'ltc') { chainPath = '2'; + } else if (chain == 'sol') { + chainPath = '501'; } else { throw new Error('unknown chain: ' + chain); } @@ -285,8 +289,8 @@ export class Credentials { x.account = x.account || 0; $.checkState( - x.xPrivKey || x.xPubKey || x.xPrivKeyEncrypted || x.hardwareSourcePublicKey, - 'Failed State: x.xPrivKey | x.xPubkey | x.xPrivKeyEncrypted | x.hardwareSourcePublicKey at fromObj' + x.xPrivKey || x.xPubKey || x.xPrivKeyEncrypted || x.hardwareSourcePublicKey || x.clientDerivedPublicKey, + 'Failed State: x.xPrivKey | x.xPubkey | x.xPrivKeyEncrypted | x.hardwareSourcePublicKey | x.clientDerivedPublicKey at fromObj' ); return x; } diff --git a/packages/bitcore-wallet-client/src/lib/key.ts b/packages/bitcore-wallet-client/src/lib/key.ts index d0bfd78c8c..265c802952 100644 --- a/packages/bitcore-wallet-client/src/lib/key.ts +++ b/packages/bitcore-wallet-client/src/lib/key.ts @@ -1,5 +1,6 @@ 'use strict'; +import async from 'async' import Mnemonic from 'bitcore-mnemonic'; import { BitcoreLib as Bitcore, @@ -29,10 +30,38 @@ const wordsForLang: any = { // we always set 'livenet' for xprivs. it has no consequences // other than the serialization const NETWORK: string = 'livenet'; +const ALGOS_BY_CHAIN = { + default: Constants.ALGOS.ECDSA, + sol: Constants.ALGOS.EDDSA, +} +const SUPPORTED_ALGOS = [Constants.ALGOS.ECDSA, Constants.ALGOS.EDDSA]; +const ALGO_TO_KEY_TYPE = { + ECDSA: 'Bitcoin', + EDDSA: 'ed25519' +} + +export interface KeyOptions { + id?: string; + seedType: string; + seedData?: any; + passphrase?: string; // seed passphrase + password?: string; // encrypting password + sjclOpts?: any; // options to SJCL encrypt + use0forBCH?: boolean; + useLegacyPurpose?: boolean; + useLegacyCoinType?: boolean; + nonCompliantDerivation?: boolean; + language?: string; + algo?: string; // eddsa or ecdsa (Bitcoin) by default +}; export class Key { + // ecdsa #xPrivKey: string; #xPrivKeyEncrypted: string; + // eddsa + #xPrivKeyEDDSA: string; + #xPrivKeyEDDSAEncrypted: string; #version: number; #mnemonic: string; #mnemonicEncrypted: string; @@ -43,9 +72,9 @@ export class Key { public use44forMultisig: boolean; public compliantDerivation: boolean; public BIP45: boolean; - + public signatureScheme: string public fingerPrint: string; - + public fingerPrintEDDSA: string /* * public readonly exportFields = { * 'xPrivKey': '#xPrivKey', @@ -71,29 +100,13 @@ export class Key { * @param {String} seedType new|extendedPrivateKey|object|mnemonic * @param {String} seedData */ - constructor( - opts: { - id?: string; - seedType: string; - seedData?: any; - passphrase?: string; // seed passphrase - password?: string; // encrypting password - sjclOpts?: any; // options to SJCL encrypt - use0forBCH?: boolean; - useLegacyPurpose?: boolean; - useLegacyCoinType?: boolean; - nonCompliantDerivation?: boolean; - language?: string; - } = { seedType: 'new' } - ) { + constructor(opts: KeyOptions = { seedType: 'new' }) { this.#version = 1; this.id = opts.id || Uuid.v4(); - // bug backwards compatibility flags this.use0forBCH = opts.useLegacyCoinType; this.use44forMultisig = opts.useLegacyPurpose; this.compliantDerivation = !opts.nonCompliantDerivation; - let x = opts.seedData; switch (opts.seedType) { @@ -121,17 +134,21 @@ export class Key { } catch (e) { throw new Error('Invalid argument'); } - this.fingerPrint = xpriv.fingerPrint.toString('hex'); - + const params = { algo: opts.algo } + this.#setFingerprint({ value: xpriv.fingerPrint.toString('hex'), ...params }); if (opts.password) { - this.#xPrivKeyEncrypted = sjcl.encrypt( - opts.password, - xpriv.toString(), - opts - ); - if (!this.#xPrivKeyEncrypted) throw new Error('Could not encrypt'); + this.#setPrivKeyEncrypted({ + value: sjcl.encrypt( + opts.password, + xpriv.toString(), + opts + ), + ...params + }); + const xPrivKeyEncrypted = this.#getPrivKeyEncrypted(params); + if (xPrivKeyEncrypted) throw new Error('Could not encrypt'); } else { - this.#xPrivKey = xpriv.toString(); + this.#setPrivKey({ value: xpriv.toString(), ...params }); } this.#mnemonic = null; this.#mnemonicHasPassphrase = null; @@ -149,12 +166,15 @@ export class Key { this.#xPrivKey = x.xPrivKey; this.#xPrivKeyEncrypted = x.xPrivKeyEncrypted; + this.#xPrivKeyEDDSA = x.xPrivKeyEDDSA; + this.#xPrivKeyEDDSAEncrypted = x.xPrivKeyEDDSAEncrypted; this.#mnemonic = x.mnemonic; this.#mnemonicEncrypted = x.mnemonicEncrypted; this.#mnemonicHasPassphrase = x.mnemonicHasPassphrase; this.#version = x.version; this.fingerPrint = x.fingerPrint; + this.fingerPrintEDDSA = x.fingerPrintEDDSA; this.compliantDerivation = x.compliantDerivation; this.BIP45 = x.BIP45; this.id = x.id; @@ -180,12 +200,15 @@ export class Key { this.#xPrivKey = x.xPrivKey; this.#xPrivKeyEncrypted = x.xPrivKeyEncrypted; + this.#xPrivKeyEDDSA = x.xPrivKeyEDDSA; + this.#xPrivKeyEDDSAEncrypted = x.xPrivKeyEDDSAEncrypted; this.#mnemonic = x.mnemonic; this.#mnemonicEncrypted = x.mnemonicEncrypted; this.#mnemonicHasPassphrase = x.mnemonicHasPassphrase; this.#version = x.version || 1; this.fingerPrint = x.fingerPrint; + this.fingerPrintEDDSA = x.fingerPrintEDDSA; // If the wallet was single seed... multisig walelts accounts // will be 48' @@ -208,35 +231,36 @@ export class Key { } static match(a, b) { - // fingerPrint is not always available (because xPriv could has - // been imported encrypted) - return a.id == b.id || a.fingerPrint == b.fingerPrint; + // fingerPrint is not always available (because xPriv could have been imported encrypted) + return a.id == b.id || a.fingerPrint == b.fingerPrint || a.fingerPrintEDDSA == b.fingerPrintEDDSA; } private setFromMnemonic( m, - opts: { passphrase?: string; password?: string; sjclOpts?: any } + opts: { passphrase?: string; password?: string; sjclOpts?: any, algo?: string } ) { - const xpriv = m.toHDPrivateKey(opts.passphrase, NETWORK); - this.fingerPrint = xpriv.fingerPrint.toString('hex'); - - if (opts.password) { - this.#xPrivKeyEncrypted = sjcl.encrypt( - opts.password, - xpriv.toString(), - opts.sjclOpts - ); - if (!this.#xPrivKeyEncrypted) throw new Error('Could not encrypt'); - this.#mnemonicEncrypted = sjcl.encrypt( - opts.password, - m.phrase, - opts.sjclOpts - ); - if (!this.#mnemonicEncrypted) throw new Error('Could not encrypt'); - } else { - this.#xPrivKey = xpriv.toString(); - this.#mnemonic = m.phrase; - this.#mnemonicHasPassphrase = !!opts.passphrase; + for (const algo of SUPPORTED_ALGOS) { + const xpriv = m.toHDPrivateKey(opts.passphrase, NETWORK, ALGO_TO_KEY_TYPE[algo]); + this.#setFingerprint({ value: xpriv.fingerPrint.toString('hex'), algo }); + + if (opts.password) { + this.#setPrivKeyEncrypted({ + value: sjcl.encrypt( + opts.password, + xpriv.toString(), + opts.sjclOpts) + }); + if (!this.#getPrivKeyEncrypted({ algo })) throw new Error('Could not encrypt'); + this.#mnemonicEncrypted = this.#mnemonicEncrypted || sjcl.encrypt( + opts.password, + m.phrase, + opts.sjclOpts + ); + } else { + this.#setPrivKey({ value: xpriv.toString(), algo }); + this.#mnemonic = m.phrase; + this.#mnemonicHasPassphrase = !!opts.passphrase; + } } } @@ -244,11 +268,14 @@ export class Key { const ret = { xPrivKey: this.#xPrivKey, xPrivKeyEncrypted: this.#xPrivKeyEncrypted, + xPrivKeyEDDSA: this.#xPrivKeyEDDSA, + xPrivKeyEDDSAEncrypted: this.#xPrivKeyEDDSAEncrypted, mnemonic: this.#mnemonic, mnemonicEncrypted: this.#mnemonicEncrypted, version: this.#version, mnemonicHasPassphrase: this.#mnemonicHasPassphrase, fingerPrint: this.fingerPrint, // 32bit fingerprint + fingerPrintEDDSA: this.fingerPrintEDDSA, compliantDerivation: this.compliantDerivation, BIP45: this.BIP45, @@ -260,14 +287,19 @@ export class Key { return JSON.parse(JSON.stringify(ret)); }; - isPrivKeyEncrypted() { - return !!this.#xPrivKeyEncrypted && !this.#xPrivKey; + isPrivKeyEncrypted(algo?) { + switch (algo?.toUpperCase()) { + case (Constants.ALGOS.EDDSA): + return !!this.#xPrivKeyEDDSAEncrypted && !this.#xPrivKeyEDDSA; + default: + return !!this.#xPrivKeyEncrypted && !this.#xPrivKey; + } }; - checkPassword(password) { - if (this.isPrivKeyEncrypted()) { + checkPassword(password, algo?) { + if (this.isPrivKeyEncrypted(algo)) { try { - sjcl.decrypt(password, this.#xPrivKeyEncrypted); + sjcl.decrypt(password, this.#getPrivKeyEncrypted({ algo })); } catch (ex) { return false; } @@ -276,22 +308,24 @@ export class Key { return null; }; - get(password) { + get(password, algo?) { let keys: any = {}; let fingerPrintUpdated = false; - if (this.isPrivKeyEncrypted()) { + if (this.isPrivKeyEncrypted(algo)) { $.checkArgument( password, 'Private keys are encrypted, a password is needed' ); try { - keys.xPrivKey = sjcl.decrypt(password, this.#xPrivKeyEncrypted); + let xPrivKeyEncrypted = this.#getPrivKeyEncrypted({ algo }); + keys.xPrivKey = sjcl.decrypt(password, xPrivKeyEncrypted); // update fingerPrint if not set. - if (!this.fingerPrint) { - let xpriv = new Bitcore.HDPrivateKey(keys.xPrivKey); - this.fingerPrint = xpriv.fingerPrint.toString('hex'); + if (!this.#getFingerprint({ algo })) { + const xpriv = new Bitcore.HDPrivateKey(keys.xPrivKey); + const fingerPrint = xpriv.fingerPrint.toString('hex'); + this.#setFingerprint({ value: fingerPrint, algo }); fingerPrintUpdated = true; } @@ -302,7 +336,7 @@ export class Key { throw new Error('Could not decrypt'); } } else { - keys.xPrivKey = this.#xPrivKey; + keys.xPrivKey = this.#getPrivKey({ algo }); keys.mnemonic = this.#mnemonic; if (fingerPrintUpdated) { keys.fingerPrintUpdated = true; @@ -345,16 +379,29 @@ export class Key { } }; - derive(password, path): Bitcore.HDPrivateKey { + derive(password, path, algo?): Bitcore.HDPrivateKey { $.checkArgument(path, 'no path at derive()'); - const xPrivKey = new Bitcore.HDPrivateKey( - this.get(password).xPrivKey, - NETWORK - ); - const deriveFn = this.compliantDerivation + let deriveFn; + if (algo?.toUpperCase() === Constants.ALGOS.EDDSA) { + const key = this.#getChildKeyEDDSA(password, path); + return new Bitcore.HDPrivateKey({ + network: 'livenet', + depth: 1, + parentFingerPrint: Buffer.from(this.#getFingerprint({ algo }), 'hex'), + childIndex: 0, + chainCode: Buffer.from(key.pubKey, 'hex'), + privateKey: Bitcore.encoding.Base58.decode(key.privKey), + }); + } else { + let xPrivKey = new Bitcore.HDPrivateKey( + this.get(password, algo).xPrivKey, + NETWORK + ); + deriveFn = this.compliantDerivation ? xPrivKey.deriveChild.bind(xPrivKey) : xPrivKey.deriveNonCompliantChild.bind(xPrivKey); - return deriveFn(path); + return deriveFn(path); + } }; _checkChain(chain) { @@ -379,6 +426,8 @@ export class Key { const chain = opts.chain || Utils.getChain(opts.coin); let purpose = opts.n == 1 || this.use44forMultisig ? '44' : '48'; let coinCode = '0'; + let changeCode = opts.addChange || 0; + let addChange = opts.addChange; // checking in chains for simplicity if ( @@ -410,11 +459,14 @@ export class Key { coinCode = '3'; } else if (chain == 'ltc') { coinCode = '2'; + } else if (chain == 'sol') { + coinCode = '501'; + addChange = true; // Solana does not use change addresses. Standard is keeping this at 0 } else { throw new Error('unknown chain: ' + chain); } - - return 'm/' + purpose + "'/" + coinCode + "'/" + opts.account + "'"; + const basePath = `m/${purpose}'/${coinCode}'/${opts.account}'`; + return addChange ? `${basePath}/${changeCode}'` : basePath; }; /* @@ -422,11 +474,13 @@ export class Key { * opts.network * opts.account * opts.n + * opts.algo */ createCredentials(password, opts) { opts = opts || {}; opts.chain = opts.chain || Utils.getChain(opts.coin); + const algo = opts.algo || (ALGOS_BY_CHAIN[opts.chain.toLowerCase()] || ALGOS_BY_CHAIN['default']); if (password) $.shouldBeString(password, 'provide password'); @@ -438,10 +492,14 @@ export class Key { $.shouldBeUndefined(opts.useLegacyPurpose); let path = this.getBaseAddressDerivationPath(opts); - let xPrivKey = this.derive(password, path); + let xPrivKey = this.derive(password, path, algo); + let clientDerivedPublicKey; + if (algo === Constants.ALGOS.EDDSA) { + clientDerivedPublicKey = this.#getChildKeyEDDSA(password, path)?.pubKey + } let requestPrivKey = this.derive( password, - Constants.PATHS.REQUEST_KEY + Constants.PATHS.REQUEST_KEY, ).privateKey.toString(); if (['testnet', 'regtest'].includes(opts.network)) { @@ -466,7 +524,8 @@ export class Key { keyId: this.id, requestPrivKey, addressType: opts.addressType, - walletPrivKey: opts.walletPrivKey + walletPrivKey: opts.walletPrivKey, + clientDerivedPublicKey, }); }; @@ -534,6 +593,37 @@ export class Key { signatures = signatures.map(sig => sig.signature.toDER().toString('hex')); return signatures; + } else if (chain === 'sol') { + let tx = t.uncheckedSerialize(); + tx = typeof tx === 'string' ? [tx] : tx; + const txArray = Array.isArray(tx) ? tx : [tx]; + const isChange = false; + const addressIndex = 0; + const xPrivKey = this.get(password, Constants.ALGOS.EDDSA).xPrivKey + const { privKey, pubKey } = Deriver.derivePrivateKey( + chain.toUpperCase(), + txp.network, + xPrivKey, // derived + addressIndex, + isChange + ); + async.waterfall([ + function addSignatures( next) { + Promise.all( + txArray.map((rawTx) => + Transactions.getSignature({ + chain: chain.toUpperCase(), + tx: rawTx, + key: { privKey, pubKey } + }) + ) + ) + .then((signatures) => next(null, signatures)) + .catch((err) => next(err)); + } + ], + cb + ); } else { let tx = t.uncheckedSerialize(); tx = typeof tx === 'string' ? [tx] : tx; @@ -559,4 +649,69 @@ export class Key { return signatures; } }; + + #setPrivKey(params: { algo?: string; value: any; }) { + const { value, algo } = params; + switch (algo?.toUpperCase()) { + case (Constants.ALGOS.EDDSA): + this.#xPrivKeyEDDSA = value; + break; + default: + this.#xPrivKey = value; + } + } + + #setPrivKeyEncrypted(params: { value: any; algo?: string; }) { + const { value, algo } = params; + switch (algo?.toUpperCase()) { + case (Constants.ALGOS.EDDSA): + this.#xPrivKeyEDDSAEncrypted = value; + break; + default: + this.#xPrivKeyEncrypted = value; + } + } + + #setFingerprint(params: { value: any; algo?: string; }) { + const { value, algo } = params; + switch (algo?.toUpperCase()) { + case (Constants.ALGOS.EDDSA): + this.fingerPrintEDDSA = value; + break; + default: + this.fingerPrint = value; + } + } + + #getPrivKey(params: { algo?: string; } = {}) { + switch (params?.algo?.toUpperCase()) { + case (Constants.ALGOS.EDDSA): + return this.#xPrivKeyEDDSA; + default: + return this.#xPrivKey; + } + } + + #getPrivKeyEncrypted(params: { algo?: string; } = {}) { + switch (params?.algo?.toUpperCase()) { + case (Constants.ALGOS.EDDSA): + return this.#xPrivKeyEDDSAEncrypted; + default: + return this.#xPrivKeyEncrypted; + } + } + + #getFingerprint(params: { algo?: string; } = {}) { + switch (params?.algo?.toUpperCase()) { + case (Constants.ALGOS.EDDSA): + return this.fingerPrintEDDSA; + default: + return this.#xPrivKey; + } + } + + #getChildKeyEDDSA(password, path) { + const privKey = this.get(password, Constants.ALGOS.EDDSA).xPrivKey; + return Deriver.derivePrivateKeyWithPath('SOL', null, privKey, path, null); + } } diff --git a/packages/bitcore-wallet-client/src/lib/payproV2.ts b/packages/bitcore-wallet-client/src/lib/payproV2.ts index 4321cb82c7..4e900b89f2 100644 --- a/packages/bitcore-wallet-client/src/lib/payproV2.ts +++ b/packages/bitcore-wallet-client/src/lib/payproV2.ts @@ -20,7 +20,8 @@ const MAX_FEE_PER_KB = { op: 1000000000000, // 1000 Gwei xrp: 1000000000000, doge: 10000 * 1000, // 10k sat/b - ltc: 10000 * 1000 // 10k sat/b + ltc: 10000 * 1000, // 10k sat/b + sol: 15000 }; // PayPro Network Map diff --git a/packages/bitcore-wallet-client/src/lib/verifier.ts b/packages/bitcore-wallet-client/src/lib/verifier.ts index 4652515df9..51d3a644b9 100644 --- a/packages/bitcore-wallet-client/src/lib/verifier.ts +++ b/packages/bitcore-wallet-client/src/lib/verifier.ts @@ -49,7 +49,8 @@ export class Verifier { network, credentials.chain, escrowInputs, - credentials.hardwareSourcePublicKey + credentials.hardwareSourcePublicKey, + credentials.clientDerivedPublicKey ); return ( local.address == address.address && diff --git a/packages/bitcore-wallet-client/test/api.test.js b/packages/bitcore-wallet-client/test/api.test.js index 8670af016c..af32629c4f 100644 --- a/packages/bitcore-wallet-client/test/api.test.js +++ b/packages/bitcore-wallet-client/test/api.test.js @@ -3024,6 +3024,44 @@ describe('client API', function() { ); }); }); + + describe('SOL address creation', () => { + it.only('should be able to create address in 1-of-1 wallet', done => { + this.timeout(50000); + var xPriv = + 'xprv9s21ZrQH143K3aKdQ6kXF1vj7R6LtkoLCiUXfM5bdbGXmhQkC1iXdnFfrxAAtaTunPUCCLwUQ3cpNixGLMbLAH1gzeCr8VZDe4gPgmKLb2X'; + let k = new Key({ seedData: xPriv, seedType: 'extendedPrivateKey', algo: 'EDDSA'}); + + clients[0].fromString( + k.createCredentials(null, { + coin: 'sol', + network: 'livenet', + account: 0, + n: 1 + }) + ); + clients[0].createWallet( + 'mywallet', + 'creator', + 1, + 1, + { + network: 'livenet', + coin: 'sol', + singleAddress: true, + addressType: 'P2PKH' + }, + err => { + should.not.exist(err); + clients[0].createAddress((err, res) => { + should.not.exist(err); + res.address.should.equal('7EWwMxKQa5Gru7oTcS1Wi3AaEgTfA6MU3z7MaLUT6hnD'); + done(); + }); + } + ); + }); + }); }); describe('Notifications', () => { diff --git a/packages/bitcore-wallet-service/src/lib/blockchainexplorer.ts b/packages/bitcore-wallet-service/src/lib/blockchainexplorer.ts index 0a959dc66f..91f9fefc92 100644 --- a/packages/bitcore-wallet-service/src/lib/blockchainexplorer.ts +++ b/packages/bitcore-wallet-service/src/lib/blockchainexplorer.ts @@ -44,7 +44,11 @@ const PROVIDERS = { op: { livenet: 'https://api-eth.bitcore.io', sepolia: 'https://api-eth.bitcore.io' - } + }, + sol: { + livenet: 'https://api-sol.bitcore.io', + testnet: 'https://api-sol.bitcore.io' + }, } }; diff --git a/packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts b/packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts index eee4745725..0b83c5b1b0 100644 --- a/packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts +++ b/packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts @@ -21,7 +21,8 @@ const Bitcore_ = { ltc: require('bitcore-lib-ltc'), arb: Bitcore, op: Bitcore, - base: Bitcore + base: Bitcore, + sol: Bitcore }; const Constants = Common.Constants, diff --git a/packages/bitcore-wallet-service/src/lib/blockchainmonitor.ts b/packages/bitcore-wallet-service/src/lib/blockchainmonitor.ts index d6c50467ea..138d30d83a 100644 --- a/packages/bitcore-wallet-service/src/lib/blockchainmonitor.ts +++ b/packages/bitcore-wallet-service/src/lib/blockchainmonitor.ts @@ -75,6 +75,7 @@ export class BlockchainMonitor { arb: {}, base: {}, op: {}, + sol: {}, }; const chainNetworkPairs = []; diff --git a/packages/bitcore-wallet-service/src/lib/chain/index.ts b/packages/bitcore-wallet-service/src/lib/chain/index.ts index 619843411e..b28dfaba66 100644 --- a/packages/bitcore-wallet-service/src/lib/chain/index.ts +++ b/packages/bitcore-wallet-service/src/lib/chain/index.ts @@ -11,6 +11,7 @@ import { EthChain } from './eth'; import { LtcChain } from './ltc'; import { MaticChain } from './matic'; import { OpChain } from './op'; +import { SolChain } from './sol'; import { XrpChain } from './xrp'; const Constants = Common.Constants; @@ -84,7 +85,8 @@ const chains: { [chain: string]: IChain } = { OP: new OpChain(), XRP: new XrpChain(), DOGE: new DogeChain(), - LTC: new LtcChain() + LTC: new LtcChain(), + SOL: new SolChain() }; class ChainProxy { diff --git a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts new file mode 100644 index 0000000000..369e03d1cb --- /dev/null +++ b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts @@ -0,0 +1,338 @@ +import { ed25519 } from '@noble/curves/ed25519'; +import { BitcoreLib as Bitcore, Transactions, Validation } from 'crypto-wallet-core'; +import { IChain } from '..'; +import { Defaults } from '../../common/defaults'; +import { Errors } from '../../errors/errordefinitions'; +import logger from '../../logger'; +import { IWallet } from '../../model'; +import { IAddress } from '../../model/address'; +import { WalletService } from '../../server'; + +export class SolChain implements IChain { + chain: string; + + constructor() { + this.chain = 'SOL'; + } + + private convertBitcoreBalance(bitcoreBalance, locked) { + const { unconfirmed, confirmed, balance } = bitcoreBalance; + const convertedBalance = { + totalAmount: balance, + totalConfirmedAmount: confirmed, + lockedAmount: locked, + lockedConfirmedAmount: locked, + availableAmount: balance - locked, + availableConfirmedAmount: confirmed - locked, + byAddress: [] + }; + return convertedBalance; + } + + getWalletBalance(server, wallet, opts, cb) { + const bc = server._getBlockchainExplorer(wallet.chain || wallet.coin, wallet.network); + + if (opts.tokenAddress) { + wallet.tokenAddress = opts.tokenAddress; + } + + bc.getBalance(wallet, (err, balance) => { + if (err) { + return cb(err); + } + // getPendingTxs returns all txps when given a native currency + server.getPendingTxs(opts, (err, txps) => { + if (err) return cb(err); + let fees = 0; + let amounts = 0; + + txps = txps.filter(txp => { + // Add gas used for tokens when getting native balance + if (!opts.tokenAddress) { + fees += txp.fee || 0; + } + // Filter tokens when getting native balance + if (txp.tokenAddress && !opts.tokenAddress) { + return false; + } + amounts += txp.amount; + return true; + }); + + const lockedSum = (amounts + fees) || 0; // previously set to 0 if opts.multisigContractAddress + const convertedBalance = this.convertBitcoreBalance(balance, lockedSum); + server.storage.fetchAddresses(server.walletId, (err, addresses: IAddress[]) => { + if (err) return cb(err); + if (addresses.length > 0) { + const byAddress = [ + { + address: addresses[0].address, + path: addresses[0].path, + amount: convertedBalance.totalAmount + } + ]; + convertedBalance.byAddress = byAddress; + } + return cb(null, convertedBalance); + }); + }); + }); + } + + getFee(server, wallet, opts) { + return new Promise(resolve => { + const numSignatures = opts.signatures || 1; + return resolve({ fee: 5000 * numSignatures }); + }); + } + + getBitcoreTx(txp, opts = { signed: true }) { + const { + data, + outputs, + payProUrl, + tokenAddress, + isTokenSwap, + multiTx + } = txp; + if (multiTx) { + throw Errors.MULTI_TX_UNSUPPORTED; + } + const isSPL = tokenAddress && !payProUrl && !isTokenSwap; + const chain = isSPL ? `${this.chain}SPL` : this.chain; + const recipients = outputs.map(output => { + return { + amount: output.amount, + address: output.toAddress, + data: output.data + }; + }); + if (data) { + recipients[0].data = data; + } + const unsignedTxs = []; + for (let index = 0; index < recipients.length; index++) { + let params = { + ...recipients[index], + recipients: [recipients[index]] + }; + unsignedTxs.push(Transactions.create({ ...txp, chain, ...params })); + } + + let tx = { + uncheckedSerialize: () => unsignedTxs, + txid: () => txp.txid, + toObject: () => { + let ret = txp; // Utils.deepClone(txp); TODO check this + ret.outputs[0].satoshis = ret.outputs[0].amount; + return ret; + }, + getFee: () => { + return txp.fee; + }, + getChangeOutput: () => null + }; + + if (opts.signed) { + const sigs = txp.getCurrentSignatures(); + sigs.forEach(x => { + this.addSignaturesToBitcoreTx(tx, txp.inputs, txp.inputPaths, x.signatures, x.xpub); + }); + } + + return tx; + } + + addSignaturesToBitcoreTx(tx, inputs, inputPaths, signatures, xpub) { + if (signatures.length === 0) { + throw new Error('Signatures Required'); + } + + const unsignedTxs = tx.uncheckedSerialize(); + const signedTxs = []; + for (let index = 0; index < signatures.length; index++) { + const signed = Transactions.applySignature({ + chain: this.chain, // TODO use lowercase always to avoid confusion + tx: unsignedTxs[index], + signature: signatures[index] + }); // expected to be in raw string format + signedTxs.push(signed); + // TDOD id = the base64 version of the signature. no crucial + tx.id = Transactions.getHash({ tx: signed, chain: this.chain }); + } + tx.uncheckedSerialize = () => signedTxs; + } + + getTransactionCount(server, wallet, from) { + return new Promise((resolve, reject) => { + server._getTransactionCount(wallet, from, (err, count) => { + if (err) return reject(err); + return resolve(count); + }); + }); + } + + getWalletSendMaxInfo(server, wallet, opts, cb) { + server.getBalance({}, (err, balance) => { + if (err) return cb(err); + const { availableAmount } = balance; + const sigs = opts.signatures || 1; + let fee = sigs * 5000 + return cb(null, { + utxosBelowFee: 0, + amountBelowFee: 0, + amount: availableAmount - fee, + feePerKb: opts.feePerKb, + fee + }); + }); + } + + checkValidTxAmount(output): boolean { + try { + if ( + output.amount == null || + output.amount < 0 || + isNaN(output.amount) + ) { + throw new Error('output.amount is not a valid value: ' + output.amount); + } + return true; + } catch (err) { + logger.warn(`Invalid output amount (${output.amount}) in checkValidTxAmount: $o`, err); + return false; + } + } + + checkTx(txp) { + try { + const tx = this.getBitcoreTx(txp); + } catch (ex) { + logger.debug('Error building Bitcore transaction: %o', ex); + return ex; + } + + return null; + } + + selectTxInputs(server, txp, wallet, _opts, cb) { + server.getBalance({ wallet }, (err, balance) => { + if (err) return cb(err); + const { totalAmount, availableAmount } = balance; + // calculate how much spave is needed to find rent amount + const minRentException = Defaults.MIN_XRP_BALANCE; + if (totalAmount - minRentException < txp.getTotalAmount()) { + return cb(Errors.INSUFFICIENT_FUNDS); + } else if (availableAmount < txp.getTotalAmount()) { + return cb(Errors.LOCKED_FUNDS); + } else { + return cb(this.checkTx(txp)); + } + }); + } + + validateAddress(wallet, inaddr, opts) { + const chain = this.chain.toLowerCase(); + const isValidTo = Validation.validateAddress(chain, wallet.network, inaddr); + if (!isValidTo) { + throw Errors.INVALID_ADDRESS; + } + const isValidFrom = Validation.validateAddress(chain, wallet.network, opts.from); + if (!isValidFrom) { + throw Errors.INVALID_ADDRESS; + } + return; + } + + verifyMessage(opts) { + const pub = Bitcore.bs58.decode(opts.address); // 32 bytes + const sig = Bitcore.bs58.decode(opts.signature); // 64 bytes + const msg = typeof opts.message === 'string' + ? new TextEncoder().encode(opts.message) + : opts.message; + + if (pub.length !== 32 || sig.length !== 64) + throw new Error('Bad key or signature length'); + + return ed25519.verify(sig, msg, pub); + } + + addressFromStorageTransform(network, address): void { + if (network != 'livenet') { + const x = address.address.indexOf(':' + network); + if (x >= 0) { + address.address = address.address.substr(0, x); + } + } + } + + addressToStorageTransform(network, address): void { + if (network != 'livenet') address.address += ':' + network; + } + + + getMinimumRent(server: WalletService, wallet: IWallet, cb: (err?, reserve?: number) => void) { + const bc = server._getBlockchainExplorer(wallet.chain || wallet.coin, wallet.network); + bc.getReserve((err, reserve) => { + if (err) { + return cb(err); + } + return cb(null, reserve); + }); + } + + getReserve(server: WalletService, wallet: IWallet, cb: (err?, reserve?: number) => void) { + return cb(null, 0); + } + getSizeSafetyMargin() { + return 0; + } + + getInputSizeSafetyMargin() { + return 0; + } + + notifyConfirmations() { + return false; + } + + supportsMultisig() { + return false; + } + + isUTXOChain() { + return false; + } + + isSingleAddress() { + return true; + } + + getDustAmountValue() { + return 0; + } + + getChangeAddress() { } + + checkDust(_output, _opts) { } + + checkScriptOutput(output) { } + + onCoin(coin) { + return null; + } + + convertFeePerKb(p, feePerKb) { + return [p, feePerKb]; + } + + checkTxUTXOs(server, txp, opts, cb) { + return cb(); + } + + checkUtxos(opts) { } + + onTx(tx) { + return null; + } +} diff --git a/packages/bitcore-wallet-service/src/lib/common/constants.ts b/packages/bitcore-wallet-service/src/lib/common/constants.ts index 9c55b87f14..550d096f2b 100644 --- a/packages/bitcore-wallet-service/src/lib/common/constants.ts +++ b/packages/bitcore-wallet-service/src/lib/common/constants.ts @@ -12,7 +12,8 @@ export const Constants = { OP: 'op', XRP: 'xrp', DOGE: 'doge', - LTC: 'ltc' + LTC: 'ltc', + SOL: 'sol' }, BITPAY_SUPPORTED_COINS: { @@ -36,7 +37,8 @@ export const Constants = { WBTC: 'wbtc', EUROC: 'euroc', USDT: 'usdt', - WETH: 'weth' + WETH: 'weth', + SOL: 'sol' }, BITPAY_SUPPORTED_ETH_ERC20: { @@ -95,6 +97,10 @@ export const Constants = { OP: 'op', }, + SVM_CHAINS: { + SOL: 'sol' + }, + NETWORKS: { btc: ['livenet', 'testnet3', 'testnet4', 'signet', 'regtest'], bch: ['livenet', 'testnet3', 'testnet4', 'scalenet', 'chipnet', 'regtest'], @@ -106,6 +112,7 @@ export const Constants = { arb: ['livenet', 'sepolia', 'holesky', 'regtest'], base: ['livenet', 'sepolia', 'holesky', 'regtest'], op: ['livenet', 'sepolia', 'holesky', 'regtest'], + sol: ['livenet', 'testnet', 'devnet'] } as { [chain: string]: Array }, // These aliases are here to support legacy clients so don't change them lightly @@ -148,6 +155,10 @@ export const Constants = { op: { mainnet: 'livenet', testnet: 'sepolia' + }, + sol: { + mainnet: 'livenet', + testnet: 'testnet' } }, diff --git a/packages/bitcore-wallet-service/src/lib/common/defaults.ts b/packages/bitcore-wallet-service/src/lib/common/defaults.ts index ac852454c7..eeff3a21b2 100644 --- a/packages/bitcore-wallet-service/src/lib/common/defaults.ts +++ b/packages/bitcore-wallet-service/src/lib/common/defaults.ts @@ -231,7 +231,14 @@ export const Defaults = { nbBlocks: 24, defaultValue: 10000 } - ] + ], + sol: [ + { + name: 'normal', + nbBlocks: 1, + defaultValue: 5000 + } + ], }, // How many levels to fallback to if the value returned by the network for a given nbBlocks is -1 @@ -357,7 +364,8 @@ export const Defaults = { op: 1000000000000, // 50 Gwei, xrp: 1000000000000, doge: 100000000 * 100, - ltc: 10000 * 1000 // 10k sat/b + ltc: 10000 * 1000, // 10k sat/b + sol: 15000 // Lamports per signature }, MIN_TX_FEE: { @@ -370,7 +378,8 @@ export const Defaults = { op: 0, xrp: 0, doge: 0, - ltc: 0 + ltc: 0, + sol: 0, }, MAX_TX_FEE: { @@ -400,6 +409,9 @@ export const Defaults = { // XRP has a non-refundable mininum activation fee / balance MIN_XRP_BALANCE: 1000000, + // SOL has a non-refundable rent fee / balance + MIN_SOL_BALANCE: 1002240, + // Time to get the latest push notification subscriptions. In ms. PUSH_NOTIFICATION_SUBS_TIME: 10 * 60 * 1000, // 10 min. diff --git a/packages/bitcore-wallet-service/src/lib/fiatrateservice.ts b/packages/bitcore-wallet-service/src/lib/fiatrateservice.ts index bea38f403c..8c0947fa41 100644 --- a/packages/bitcore-wallet-service/src/lib/fiatrateservice.ts +++ b/packages/bitcore-wallet-service/src/lib/fiatrateservice.ts @@ -278,7 +278,7 @@ export class FiatRateService { // Oldest date in timestamp range in epoch number ex. 24 hours ago const now = Date.now() - Defaults.FIAT_RATE_FETCH_INTERVAL * 60 * 1000; const ts = !isNaN(opts.ts) ? opts.ts : now; - const coins = ['btc', 'bch', 'eth', 'matic', 'xrp', 'doge', 'ltc', 'shib', 'ape']; + const coins = ['btc', 'bch', 'eth', 'matic', 'xrp', 'doge', 'ltc', 'shib', 'ape', 'sol']; async.map( coins, diff --git a/packages/bitcore-wallet-service/src/lib/model/address.ts b/packages/bitcore-wallet-service/src/lib/model/address.ts index 5d289c7ed5..90d4bc4af7 100644 --- a/packages/bitcore-wallet-service/src/lib/model/address.ts +++ b/packages/bitcore-wallet-service/src/lib/model/address.ts @@ -96,15 +96,15 @@ export class Address { return x; } - static _deriveAddress(scriptType, publicKeyRing, path, m, chain, network, noNativeCashAddr, escrowInputs?, hardwareSourcePublicKey?) { + static _deriveAddress(scriptType, publicKeyRing, path, m, chain, network, noNativeCashAddr, escrowInputs?, hardwareSourcePublicKey?, clientDerivedPublicKey?) { $.checkArgument(Utils.checkValueInCollection(scriptType, Constants.SCRIPT_TYPES)); - - if (hardwareSourcePublicKey) { - const bitcoreAddress = Deriver.getAddress(chain.toUpperCase(), network, hardwareSourcePublicKey, scriptType); + const externSourcePublicKey = hardwareSourcePublicKey || clientDerivedPublicKey; + if (externSourcePublicKey) { + const bitcoreAddress = Deriver.getAddress(chain.toUpperCase(), network, externSourcePublicKey, scriptType); return { address: bitcoreAddress.toString(), path, - publicKeys: [hardwareSourcePublicKey] + publicKeys: [externSourcePublicKey] } } @@ -186,7 +186,8 @@ export class Address { chain, noNativeCashAddr = false, escrowInputs?, - hardwareSourcePublicKey? + hardwareSourcePublicKey?, + clientDerivedPublicKey? ) { const raw = Address._deriveAddress( scriptType, @@ -198,6 +199,7 @@ export class Address { noNativeCashAddr, escrowInputs, hardwareSourcePublicKey, + clientDerivedPublicKey ); return Address.create( _.extend(raw, { diff --git a/packages/bitcore-wallet-service/src/lib/model/copayer.ts b/packages/bitcore-wallet-service/src/lib/model/copayer.ts index f144fc65f7..cdab2f15b7 100644 --- a/packages/bitcore-wallet-service/src/lib/model/copayer.ts +++ b/packages/bitcore-wallet-service/src/lib/model/copayer.ts @@ -31,6 +31,7 @@ export class Copayer { chain: string; xPubKey: string; hardwareSourcePublicKey: string; + clientDerivedPublicKey: string; id: string; name: string; requestPubKey: string; @@ -50,7 +51,7 @@ export class Copayer { static create(opts) { opts = opts || {}; - if (!opts.hardwareSourcePublicKey) { + if (!opts.hardwareSourcePublicKey && !opts.clientDerivedPublicKey) { $.checkArgument(opts.xPubKey, 'Missing copayer extended public key') } $.checkArgument(opts.requestPubKey, 'Missing copayer request public key') @@ -68,6 +69,7 @@ export class Copayer { x.chain = opts.chain || opts.coin; x.xPubKey = opts.xPubKey; x.hardwareSourcePublicKey = opts.hardwareSourcePublicKey; + x.clientDerivedPublicKey = opts.clientDerivedPublicKey; x.id = Copayer._xPubToCopayerId(opts.chain, x.xPubKey); x.name = opts.name; x.requestPubKey = opts.requestPubKey; @@ -103,6 +105,7 @@ export class Copayer { x.name = obj.name; x.xPubKey = obj.xPubKey; x.hardwareSourcePublicKey = obj.hardwareSourcePublicKey; + x.clientDerivedPublicKey = obj.clientDerivedPublicKey; x.requestPubKey = obj.requestPubKey; x.signature = obj.signature; diff --git a/packages/bitcore-wallet-service/src/lib/model/preferences.ts b/packages/bitcore-wallet-service/src/lib/model/preferences.ts index a2baf57ba9..df51242403 100644 --- a/packages/bitcore-wallet-service/src/lib/model/preferences.ts +++ b/packages/bitcore-wallet-service/src/lib/model/preferences.ts @@ -12,6 +12,7 @@ export interface IPreferences { opTokenAddresses?: string[]; baseTokenAddresses?: string[]; arbTokenAddresses?: string[]; + solTokenAddresses?: string[]; multisigMaticInfo: object[]; } export class Preferences { @@ -28,6 +29,7 @@ export class Preferences { opTokenAddresses: string[]; baseTokenAddresses: string[]; arbTokenAddresses: string[]; + solTokenAddresses: string[]; multisigMaticInfo: object[]; static create(opts) { @@ -48,6 +50,7 @@ export class Preferences { x.opTokenAddresses = opts.opTokenAddresses; x.baseTokenAddresses = opts.baseTokenAddresses; x.arbTokenAddresses = opts.arbTokenAddresses; + x.solTokenAddresses = opts.solTokenAddresses; x.multisigMaticInfo = opts.multisigMaticInfo; // you can't put useDust here since this is copayer's specific. return x; @@ -69,6 +72,7 @@ export class Preferences { x.opTokenAddresses = obj.opTokenAddresses; x.baseTokenAddresses = obj.baseTokenAddresses; x.arbTokenAddresses = obj.arbTokenAddresses; + x.solTokenAddresses = obj.solTokenAddresses; x.multisigMaticInfo = obj.multisigMaticInfo; return x; } diff --git a/packages/bitcore-wallet-service/src/lib/model/txproposal.ts b/packages/bitcore-wallet-service/src/lib/model/txproposal.ts index cc62319f23..a44959c169 100644 --- a/packages/bitcore-wallet-service/src/lib/model/txproposal.ts +++ b/packages/bitcore-wallet-service/src/lib/model/txproposal.ts @@ -65,11 +65,11 @@ export interface ITxProposal { proposalSignaturePubKeySig: string; signingMethod: string; lowFees: boolean; - nonce?: number; + nonce?: number | string; gasPrice?: number; maxGasFee?: number; priorityGasFee?: number; - txType?: number; + txType?: number | string; gasLimit?: number; // Backward compatibility for BWC <= 8.9.0 data?: string; // Backward compatibility for BWC <= 8.9.0 tokenAddress?: string; @@ -82,6 +82,13 @@ export interface ITxProposal { enableRBF?: boolean; replaceTxByFee?: boolean; multiTx?: boolean; // proposal contains multiple transactions + space?: number; + nonceAddress?: string; + blockHash?: string; + blockHeight?: number; + category?: string; + priorityFee?: number; + computeUnits?: number; } export class TxProposal { @@ -135,11 +142,11 @@ export class TxProposal { proposalSignaturePubKeySig: string; signingMethod: string; raw?: Array | string; - nonce?: number; + nonce?: number | string; gasPrice?: number; maxGasFee?: number; priorityGasFee?: number; - txType?: number; + txType?: number | string; gasLimit?: number; // Backward compatibility for BWC <= 8.9.0 data?: string; // Backward compatibility for BWC <= 8.9.0 tokenAddress?: string; @@ -154,6 +161,13 @@ export class TxProposal { enableRBF?: boolean; replaceTxByFee?: boolean; multiTx?: boolean; + space?: number; + nonceAddress?: string; + blockHash?: string; + blockHeight?: number; + category?: string; + priorityFee?: number; + computeUnits?: number; static create(opts) { opts = opts || {}; @@ -257,7 +271,16 @@ export class TxProposal { x.destinationTag = opts.destinationTag; x.invoiceID = opts.invoiceID; x.multiTx = opts.multiTx; // proposal contains multiple transactions - + + // SOL + x.space = opts.space; // space to allocate for account creation + x.blockHash = opts.blockHash; // recent block hash required for tx creation + x.blockHeight = opts.blockHeight; // max valid block height required for legacy tx creation + x.nonceAddress = opts.nonceAddress; // account address mantaining latest nonce + x.category = opts.category; // kind of transaction: transfer, account creation, nonce creation, etc + x.computeUnits = opts.computeUnits; + x.priorityFee = opts.priorityFee; + return x; } @@ -335,6 +358,15 @@ export class TxProposal { x.invoiceID = obj.invoiceID; x.multiTx = obj.multiTx; + // SOL + x.space = obj.space; // space to allocate for account creation + x.blockHash = obj.blockHash; // recent block hash required for tx creation + x.blockHeight = obj.blockHeight; // max valid block height required for legacy tx creation + x.nonceAddress = obj.nonceAddress; // account address mantaining latest nonce + x.category = obj.category; // kind of transaction: transfer, account creation, nonce creation, etc + x.computeUnits = obj.computeUnits; + x.priorityFee = obj.priorityFee; + if (x.status == 'broadcasted') { x.raw = obj.raw; } diff --git a/packages/bitcore-wallet-service/src/lib/model/wallet.ts b/packages/bitcore-wallet-service/src/lib/model/wallet.ts index 1a22c8bda9..f579dc3bc4 100644 --- a/packages/bitcore-wallet-service/src/lib/model/wallet.ts +++ b/packages/bitcore-wallet-service/src/lib/model/wallet.ts @@ -24,7 +24,8 @@ const Bitcore = { op: require('bitcore-lib'), xrp: require('bitcore-lib'), doge: require('bitcore-lib-doge'), - ltc: require('bitcore-lib-ltc') + ltc: require('bitcore-lib-ltc'), + sol: require('bitcore-lib'), }; export interface IWallet { @@ -38,6 +39,7 @@ export interface IWallet { status: string; publicKeyRing: Array<{ xPubKey: string; requestPubKey: string }>; hardwareSourcePublicKey: string; + clientDerivedPublicKey: string; addressIndex: number; copayers: string[]; pubKey: string; @@ -67,6 +69,7 @@ export class Wallet { status: string; publicKeyRing: Array<{ xPubKey: string; requestPubKey: string }>; hardwareSourcePublicKey: string; + clientDerivedPublicKey: string; addressIndex: number; copayers: Array; pubKey: string; @@ -133,6 +136,8 @@ export class Wallet { // hardware wallet related x.hardwareSourcePublicKey = opts.hardwareSourcePublicKey; + // client derived + x.clientDerivedPublicKey = opts.clientDerivedPublicKey; return x; } @@ -174,7 +179,8 @@ export class Wallet { // hardware wallet related x.hardwareSourcePublicKey = obj.hardwareSourcePublicKey; - + // client derived + x.clientDerivedPublicKey = obj.clientDerivedPublicKey; return x; } @@ -284,7 +290,7 @@ export class Wallet { const path = this.addressManager.getNewAddressPath(isChange, step); logger.debug('Deriving addr:' + path); const scriptType = escrowInputs ? 'P2SH' : this.addressType; - const address = Address.derive( + return Address.derive( this.id, scriptType, this.publicKeyRing, @@ -297,8 +303,8 @@ export class Wallet { !this.nativeCashAddr, escrowInputs, this.hardwareSourcePublicKey, + this.clientDerivedPublicKey ); - return address; } /// Only for power scan diff --git a/packages/bitcore-wallet-service/src/lib/pushnotificationsservice.ts b/packages/bitcore-wallet-service/src/lib/pushnotificationsservice.ts index 39636bc0fa..fc7b4ccab9 100644 --- a/packages/bitcore-wallet-service/src/lib/pushnotificationsservice.ts +++ b/packages/bitcore-wallet-service/src/lib/pushnotificationsservice.ts @@ -487,7 +487,8 @@ export class PushNotificationsService { euroc: 'EUROC', usdt: 'USDT', weth: 'WETH', - 'usdc.e': 'USDC.e' + 'usdc.e': 'USDC.e', + sol: 'SOL' }; const data = _.cloneDeep(notification.data); data.subjectPrefix = _.trim(this.subjectPrefix + ' '); diff --git a/packages/bitcore-wallet-service/src/lib/server.ts b/packages/bitcore-wallet-service/src/lib/server.ts index aa127c220f..1aadb78400 100644 --- a/packages/bitcore-wallet-service/src/lib/server.ts +++ b/packages/bitcore-wallet-service/src/lib/server.ts @@ -64,7 +64,8 @@ const Bitcore_ = { op: Bitcore, xrp: Bitcore, doge: require('bitcore-lib-doge'), - ltc: require('bitcore-lib-ltc') + ltc: require('bitcore-lib-ltc'), + sol: Bitcore, }; const Utils = Common.Utils; @@ -560,6 +561,7 @@ export class WalletService implements IWalletService { * @param {number} opts.n - Total copayers. * @param {string} opts.pubKey - Public key to verify copayers joining have access to the wallet secret. * @param {string} opts.hardwareSourcePublicKey - public key from a hardware device for this copayer + * @param {string} opts.clientDerivedPublicKey - public key from the client for this walet * @param {string} opts.singleAddress[=false] - The wallet will only ever have one address. * @param {string} opts.coin[='btc'] - The coin for this wallet (btc, bch, eth, doge, ltc). * @param {string} opts.chain[='btc'] - The chain for this wallet (btc, bch, eth, doge, ltc). @@ -681,6 +683,7 @@ export class WalletService implements IWalletService { nativeCashAddr: opts.nativeCashAddr, usePurpose48: opts.n > 1 && !!opts.usePurpose48, hardwareSourcePublicKey: opts.hardwareSourcePublicKey, + clientDerivedPublicKey: opts.clientDerivedPublicKey }); this.storage.storeWallet(wallet, err => { this.logd('Wallet created', wallet.id, opts.network); @@ -985,6 +988,7 @@ export class WalletService implements IWalletService { copayerIndex: wallet.copayers.length, xPubKey: opts.xPubKey, hardwareSourcePublicKey: opts.hardwareSourcePublicKey, + clientDerivedPublicKey: opts.clientDerivedPublicKey, requestPubKey: opts.requestPubKey, signature: opts.copayerSignature, customData: opts.customData, @@ -1137,6 +1141,7 @@ export class WalletService implements IWalletService { * @param {string} opts.name - The copayer name. * @param {string} opts.xPubKey - Extended Public Key for this copayer * @param {string} opts.hardwareSourcePublicKey - public key from a hardware device for this copayer + * @param {string} opts.clientDerivedPublicKey - public key from the client for this wallet * @param {string} opts.requestPubKey - Public Key used to check requests from this copayer. * @param {string} opts.copayerSignature - S(name|xPubKey|requestPubKey). Used by other copayers to verify that the copayer joining knows the wallet secret. * @param {string} opts.customData - (optional) Custom data for this copayer. @@ -1153,7 +1158,7 @@ export class WalletService implements IWalletService { if (!Utils.checkValueInCollection(opts.chain, Constants.CHAINS)) return cb(new ClientError('Invalid coin')); let xPubKey; - if (!opts.hardwareSourcePublicKey) { + if (!opts.hardwareSourcePublicKey && !opts.clientDerivedPublicKey) { if (!checkRequired(opts, ['xPubKey'], cb)) return; try { xPubKey = Bitcore_[opts.chain].HDPublicKey(opts.xPubKey); @@ -1171,7 +1176,7 @@ export class WalletService implements IWalletService { if (err) return cb(err); if (!wallet) return cb(Errors.WALLET_NOT_FOUND); - if (opts.hardwareSourcePublicKey) { + if (opts.hardwareSourcePublicKey || opts.clientDerivedPublicKey) { this._addCopayerToWallet(wallet, opts, cb); return; } @@ -1254,6 +1259,7 @@ export class WalletService implements IWalletService { * @param {string} opts.opTokenAddresses - Linked token addresses * @param {string} opts.baseTokenAddresses - Linked token addresses * @param {string} opts.arbTokenAddresses - Linked token addresses + * @param {string} opts.solTokenAddresses - Linked token addresses * @param {string} opts.multisigMaticInfo - Linked multisig eth wallet info * */ @@ -1327,6 +1333,12 @@ export class WalletService implements IWalletService { return Array.isArray(value) && value.every(x => Validation.validateAddress('arb', 'mainnet', x)); } }, + { + name: 'solTokenAddresses', + isValid(value) { + return Array.isArray(value) && value.every(x => Validation.validateAddress('sol', 'mainnet', x)); + } + }, ]; opts = _.pick(opts, preferences.map(p => p.name)); @@ -1422,6 +1434,12 @@ export class WalletService implements IWalletService { preferences.arbTokenAddresses = _.uniq(oldPref.arbTokenAddresses.concat(opts.arbTokenAddresses)); } + if (opts.solTokenAddresses) { + oldPref = oldPref || {} as Preferences; + oldPref.solTokenAddresses = oldPref.solTokenAddresses || []; + preferences.solTokenAddresses = _.uniq(oldPref.solTokenAddresses.concat(opts.solTokenAddresses)); + } + // merge matic multisigMaticInfo if (opts.multisigMaticInfo) { oldPref = oldPref || {} as Preferences; @@ -2065,7 +2083,7 @@ export class WalletService implements IWalletService { if (feePerKb < 0) failed.push(p); // NOTE: ONLY BTC/BCH/DOGE/LTC expect feePerKb to be Bitcoin amounts - // others... expect wei. + // EVM expects wei, Solana expects Lamports. return ChainService.convertFeePerKb(chain, p, feePerKb); }) @@ -2621,7 +2639,7 @@ export class WalletService implements IWalletService { next(); }, async next => { - if (!opts.nonce) { + if (!opts.nonce && !Constants.SVM_CHAINS[wallet.chain.toUpperCase()]) { try { opts.nonce = await ChainService.getTransactionCount(this, wallet, opts.from); } catch (error) { @@ -2697,7 +2715,14 @@ export class WalletService implements IWalletService { isTokenSwap: opts.isTokenSwap, enableRBF: opts.enableRBF, replaceTxByFee: opts.replaceTxByFee, - multiTx: opts.multiTx + multiTx: opts.multiTx, + blockHash: opts.blockHash, + blockHeight: opts.blockHeight, + nonceAddress: opts.nonceAddress, + category: opts.category, + fromKeyPair: opts.fromKeyPair, + priorityFee: opts.priorityFee, + computeUnits: opts.computeUnits }; txp = TxProposal.create(txOpts); next(); @@ -4564,7 +4589,7 @@ export class WalletService implements IWalletService { for (const isChange of [false, true]) { derivators.push({ id: wallet.addressManager.getBaseAddressPath(isChange), - derive: () => wallet.createAddress(isChange, step, null), + derive: () => wallet.createAddress(isChange, step), index: () => wallet.addressManager.getCurrentIndex(isChange), rewind: (n) => wallet.addressManager.rewindIndex(isChange, step, n), getSkippedAddress: () => wallet.getSkippedAddress() From 3f1a564a7de96a0549c4538bd4fbe1ad4b165cc1 Mon Sep 17 00:00:00 2001 From: lyambo Date: Mon, 5 May 2025 10:29:15 -0400 Subject: [PATCH 04/12] make param optional --- .../src/lib/chain/sol/index.ts | 14 -------------- .../bitcore-wallet-service/src/lib/model/wallet.ts | 2 +- .../test/integration/server.js | 5 +++-- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts index 369e03d1cb..694d14edc1 100644 --- a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts +++ b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts @@ -1,4 +1,3 @@ -import { ed25519 } from '@noble/curves/ed25519'; import { BitcoreLib as Bitcore, Transactions, Validation } from 'crypto-wallet-core'; import { IChain } from '..'; import { Defaults } from '../../common/defaults'; @@ -244,19 +243,6 @@ export class SolChain implements IChain { return; } - verifyMessage(opts) { - const pub = Bitcore.bs58.decode(opts.address); // 32 bytes - const sig = Bitcore.bs58.decode(opts.signature); // 64 bytes - const msg = typeof opts.message === 'string' - ? new TextEncoder().encode(opts.message) - : opts.message; - - if (pub.length !== 32 || sig.length !== 64) - throw new Error('Bad key or signature length'); - - return ed25519.verify(sig, msg, pub); - } - addressFromStorageTransform(network, address): void { if (network != 'livenet') { const x = address.address.indexOf(':' + network); diff --git a/packages/bitcore-wallet-service/src/lib/model/wallet.ts b/packages/bitcore-wallet-service/src/lib/model/wallet.ts index f579dc3bc4..5b4cd2cc54 100644 --- a/packages/bitcore-wallet-service/src/lib/model/wallet.ts +++ b/packages/bitcore-wallet-service/src/lib/model/wallet.ts @@ -284,7 +284,7 @@ export class Wallet { return this.coin === 'bch' && this.addressType === 'P2PKH'; } - createAddress(isChange, step, escrowInputs) { + createAddress(isChange, step, escrowInputs?) { $.checkState(this.isComplete(), 'Failed state: this.isComplete() at '); const path = this.addressManager.getNewAddressPath(isChange, step); diff --git a/packages/bitcore-wallet-service/test/integration/server.js b/packages/bitcore-wallet-service/test/integration/server.js index c971eda53e..6b829f29fb 100644 --- a/packages/bitcore-wallet-service/test/integration/server.js +++ b/packages/bitcore-wallet-service/test/integration/server.js @@ -53,7 +53,8 @@ const TO_SAT = { 'usdc': 1e6, 'xrp': 1e6, 'doge': 1e8, - 'ltc': 1e8 + 'ltc': 1e8, + 'sol': 1e9 }; const TOKENS = ['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', '0x056fd409e1d7a124bd7017459dfea2f387b6d5cd']; @@ -795,7 +796,7 @@ describe('Wallet service', function() { }); }); - for (const c of ['eth','xrp','matic','arb','base','op']) { + for (const c of ['eth','xrp','matic','arb','base','op','sol']) { it(`should fail to create a multisig ${c} wallet`, function(done) { var opts = { coin: c, From 7615dc49da6fe2afef4c453000c2c5bc171613ce Mon Sep 17 00:00:00 2001 From: Gabriel Date: Wed, 7 May 2025 15:23:31 -0300 Subject: [PATCH 05/12] [FIX] add sol to index for validations --- .../src/validation/index.ts | 4 ++- .../crypto-wallet-core/test/validation.ts | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/crypto-wallet-core/src/validation/index.ts b/packages/crypto-wallet-core/src/validation/index.ts index fb7b6fd8ec..7e3431c64d 100644 --- a/packages/crypto-wallet-core/src/validation/index.ts +++ b/packages/crypto-wallet-core/src/validation/index.ts @@ -7,6 +7,7 @@ import { EthValidation } from './eth'; import { LtcValidation } from './ltc'; import { MaticValidation } from './matic'; import { OpValidation } from './op'; +import { SolValidation } from './sol'; import { XrpValidation } from './xrp'; export interface IValidation { @@ -24,7 +25,8 @@ const validation: { [chain: string]: IValidation } = { MATIC: new MaticValidation(), ARB: new ArbValidation(), BASE: new BaseValidation(), - OP: new OpValidation() + OP: new OpValidation(), + SOL: new SolValidation(), }; export class ValidationProxy { diff --git a/packages/crypto-wallet-core/test/validation.ts b/packages/crypto-wallet-core/test/validation.ts index 1c30682f94..f2fdf801d9 100644 --- a/packages/crypto-wallet-core/test/validation.ts +++ b/packages/crypto-wallet-core/test/validation.ts @@ -30,6 +30,9 @@ describe('Address Validation', () => { // XRP const xrpAddress = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'; + // SOL + const solAddress = '7EWwMxKQa5Gru7oTcS1Wi3AaEgTfA6MU3z7MaLUT6hnD'; + // Uri const btcUri = 'bitcoin:1NuKwkDtCymgA1FNLUBaUWLD8s4kdKWvgn'; const bchUri = 'bitcoincash:pp8skudq3x5hzw8ew7vzsw8tn4k8wxsqsv0lt0mf3g'; @@ -48,6 +51,7 @@ describe('Address Validation', () => { const maticUri = 'matic:0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A'; const maticUriParams = 'matic:0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A?value=123&gasPrice=123&gas=123&gasLimit=123'; const maticUriSingleParam = 'matic:0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A?value=123'; + const solUri = 'solana:7EWwMxKQa5Gru7oTcS1Wi3AaEgTfA6MU3z7MaLUT6hnD'; // Invalid Address const invalidBtcAddress = '1NuKwkDtCymgA1FNLUBaUWLD8s4kKWvgn'; @@ -57,6 +61,7 @@ describe('Address Validation', () => { const invalidEthAddress = '37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08'; const invalidXrpAddress = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTH'; const invalidMaticAddress = '57d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08'; + const invalidSolAddress = '7EWwMxKQa5Gru7oTcS1Wi3AaEgTfA6MU3z7MaLUT6hn0'; // Invalid Uri const invalidEthPrefix = '0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A'; @@ -65,6 +70,7 @@ describe('Address Validation', () => { const invalidEthUriParams = 'ethereum:0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A?value=invalid&gasLimit=123&gas=123'; const invalidXrpUriParams = 'ripple:rEqj9WKSH7wEkPvWf6b4gCi26Y3F7HbKUF?amount=invalid&dt=123'; const invalidMaticUriParams = 'matic:0x37d7B3bBD88EFdE6a93cF74D2F5b0385D3E3B08A?value=invalid&gasLimit=123&gas=123'; + const invalidSolUri = 'solana:7EWwMxKQa5Gru7oTcS1Wi3AaEgTfA6MU3z7MaLUT6hn0'; it('should be able to validate an BTC address', async () => { const isValidAddress = await Validation.validateAddress('BTC', 'mainnet', btcAddress); @@ -232,4 +238,24 @@ describe('Address Validation', () => { const inValidMaticPrefix = await Validation.validateUri('MATIC', invalidMaticPrefix); expect(inValidMaticPrefix).to.equal(false); }); + + it('should be able to validate a SOL address', async () => { + const isValidAddress = await Validation.validateAddress('SOL', 'mainnet', solAddress); + expect(isValidAddress).to.equal(true); + }); + + it('should be able to invalidate an incorrect SOL address', async () => { + const isValidAddress = await Validation.validateAddress('SOL', 'mainnet', invalidSolAddress); + expect(isValidAddress).to.equal(false); + }); + + it('should be able to validate a SOL Uri', async () => { + const isValidUri = await Validation.validateUri('SOL', solUri); + expect(isValidUri).to.equal(true); + }); + + it('should be able to invalidate an incorrect SOL Uri', async () => { + const isValidUri = await Validation.validateUri('SOL', invalidSolUri); + expect(isValidUri).to.equal(false); + }); }); From 6bd046bd89455861664299b018945751435d1848 Mon Sep 17 00:00:00 2001 From: lyambo Date: Wed, 7 May 2025 16:05:16 -0400 Subject: [PATCH 06/12] update tests --- packages/bitcore-wallet-client/src/lib/api.ts | 2 +- packages/bitcore-wallet-client/src/lib/key.ts | 92 ++++++++++--------- .../bitcore-wallet-client/test/api.test.js | 63 ++++++++++++- packages/bitcore-wallet-service/src/config.ts | 8 ++ .../src/lib/common/constants.ts | 1 + .../src/lib/pushnotificationsservice.ts | 5 +- .../test/integration/fiatrateservice.js | 31 ++++++- 7 files changed, 155 insertions(+), 47 deletions(-) diff --git a/packages/bitcore-wallet-client/src/lib/api.ts b/packages/bitcore-wallet-client/src/lib/api.ts index 188021dd42..191e0ad839 100644 --- a/packages/bitcore-wallet-client/src/lib/api.ts +++ b/packages/bitcore-wallet-client/src/lib/api.ts @@ -3131,7 +3131,7 @@ export class API extends EventEmitter { { chain: 'arb', tokenAddresses: wallet.status.preferences.arbTokenAddresses, multisigInfo: wallet.status.preferences.multisigArbInfo, tokenOpts: Constants.ARB_TOKEN_OPTS, tokenUrlPath: 'arb' }, { chain: 'op', tokenAddresses: wallet.status.preferences.opTokenAddresses, multisigInfo: wallet.status.preferences.multisigOpInfo, tokenOpts: Constants.OP_TOKEN_OPTS, tokenUrlPath: 'op' }, { chain: 'base', tokenAddresses: wallet.status.preferences.baseTokenAddresses, multisigInfo: wallet.status.preferences.multisigBaseInfo, tokenOpts: Constants.BASE_TOKEN_OPTS, tokenUrlPath: 'base' }, - { chain: 'sol', tokenAddresses: wallet.status.preferences.solTokenAddresses, multisigInfo: wallet.status.preferences.multisigBaseInfo, tokenOpts: Constants.SOL_TOKEN_OPTS, tokenUrlPath: 'sol' }, + { chain: 'sol', tokenAddresses: wallet.status.preferences.solTokenAddresses, multisigInfo: wallet.status.preferences.multisigSolInfo, tokenOpts: Constants.SOL_TOKEN_OPTS, tokenUrlPath: 'sol' }, ]; for (let config of chainConfigurations) { diff --git a/packages/bitcore-wallet-client/src/lib/key.ts b/packages/bitcore-wallet-client/src/lib/key.ts index 265c802952..c46c843da0 100644 --- a/packages/bitcore-wallet-client/src/lib/key.ts +++ b/packages/bitcore-wallet-client/src/lib/key.ts @@ -134,21 +134,23 @@ export class Key { } catch (e) { throw new Error('Invalid argument'); } - const params = { algo: opts.algo } - this.#setFingerprint({ value: xpriv.fingerPrint.toString('hex'), ...params }); - if (opts.password) { - this.#setPrivKeyEncrypted({ - value: sjcl.encrypt( - opts.password, - xpriv.toString(), - opts - ), - ...params - }); - const xPrivKeyEncrypted = this.#getPrivKeyEncrypted(params); - if (xPrivKeyEncrypted) throw new Error('Could not encrypt'); - } else { - this.#setPrivKey({ value: xpriv.toString(), ...params }); + for (const algo of SUPPORTED_ALGOS) { + const params = { algo } + this.#setFingerprint({ value: xpriv.fingerPrint.toString('hex'), ...params }); + if (opts.password) { + this.#setPrivKeyEncrypted({ + value: sjcl.encrypt( + opts.password, + xpriv.toString(), + opts + ), + ...params + }); + const xPrivKeyEncrypted = this.#getPrivKeyEncrypted(params); + if (xPrivKeyEncrypted) throw new Error('Could not encrypt'); + } else { + this.#setPrivKey({ value: xpriv.toString(), ...params }); + } } this.#mnemonic = null; this.#mnemonicHasPassphrase = null; @@ -248,7 +250,8 @@ export class Key { value: sjcl.encrypt( opts.password, xpriv.toString(), - opts.sjclOpts) + opts.sjclOpts), + algo }); if (!this.#getPrivKeyEncrypted({ algo })) throw new Error('Could not encrypt'); this.#mnemonicEncrypted = this.#mnemonicEncrypted || sjcl.encrypt( @@ -288,7 +291,7 @@ export class Key { }; isPrivKeyEncrypted(algo?) { - switch (algo?.toUpperCase()) { + switch (String(algo).toUpperCase()) { case (Constants.ALGOS.EDDSA): return !!this.#xPrivKeyEDDSAEncrypted && !this.#xPrivKeyEDDSA; default: @@ -382,7 +385,7 @@ export class Key { derive(password, path, algo?): Bitcore.HDPrivateKey { $.checkArgument(path, 'no path at derive()'); let deriveFn; - if (algo?.toUpperCase() === Constants.ALGOS.EDDSA) { + if (String(algo).toUpperCase() === Constants.ALGOS.EDDSA) { const key = this.#getChildKeyEDDSA(password, path); return new Bitcore.HDPrivateKey({ network: 'livenet', @@ -600,29 +603,36 @@ export class Key { const isChange = false; const addressIndex = 0; const xPrivKey = this.get(password, Constants.ALGOS.EDDSA).xPrivKey - const { privKey, pubKey } = Deriver.derivePrivateKey( + const key = Deriver.derivePrivateKey( chain.toUpperCase(), txp.network, xPrivKey, // derived addressIndex, isChange ); - async.waterfall([ - function addSignatures( next) { - Promise.all( - txArray.map((rawTx) => - Transactions.getSignature({ - chain: chain.toUpperCase(), - tx: rawTx, - key: { privKey, pubKey } - }) - ) - ) - .then((signatures) => next(null, signatures)) - .catch((err) => next(err)); + async.map( + txArray, + function addSignatures(rawTx, next) { + (Transactions.getSignature({ + chain: chain.toUpperCase(), + tx: rawTx, + keys: [key] + }) as any) + .then(signatures => { + next(null, signatures); + }) + .catch(err => { + next(err); + }); + }, + function(err, signatures) { + try { + if (err) return cb(err); + return cb(null, signatures); + } catch (e) { + throw new Error('Missing Callback', e) + } } - ], - cb ); } else { let tx = t.uncheckedSerialize(); @@ -652,7 +662,7 @@ export class Key { #setPrivKey(params: { algo?: string; value: any; }) { const { value, algo } = params; - switch (algo?.toUpperCase()) { + switch (String(algo).toUpperCase()) { case (Constants.ALGOS.EDDSA): this.#xPrivKeyEDDSA = value; break; @@ -663,7 +673,7 @@ export class Key { #setPrivKeyEncrypted(params: { value: any; algo?: string; }) { const { value, algo } = params; - switch (algo?.toUpperCase()) { + switch (String(algo).toUpperCase()) { case (Constants.ALGOS.EDDSA): this.#xPrivKeyEDDSAEncrypted = value; break; @@ -674,7 +684,7 @@ export class Key { #setFingerprint(params: { value: any; algo?: string; }) { const { value, algo } = params; - switch (algo?.toUpperCase()) { + switch (String(algo).toUpperCase()) { case (Constants.ALGOS.EDDSA): this.fingerPrintEDDSA = value; break; @@ -684,7 +694,7 @@ export class Key { } #getPrivKey(params: { algo?: string; } = {}) { - switch (params?.algo?.toUpperCase()) { + switch (String(params?.algo).toUpperCase()) { case (Constants.ALGOS.EDDSA): return this.#xPrivKeyEDDSA; default: @@ -693,7 +703,7 @@ export class Key { } #getPrivKeyEncrypted(params: { algo?: string; } = {}) { - switch (params?.algo?.toUpperCase()) { + switch (String(params?.algo).toUpperCase()) { case (Constants.ALGOS.EDDSA): return this.#xPrivKeyEDDSAEncrypted; default: @@ -702,11 +712,11 @@ export class Key { } #getFingerprint(params: { algo?: string; } = {}) { - switch (params?.algo?.toUpperCase()) { + switch (String(params?.algo).toUpperCase()) { case (Constants.ALGOS.EDDSA): return this.fingerPrintEDDSA; default: - return this.#xPrivKey; + return this.fingerPrint; } } diff --git a/packages/bitcore-wallet-client/test/api.test.js b/packages/bitcore-wallet-client/test/api.test.js index af32629c4f..ee403289f3 100644 --- a/packages/bitcore-wallet-client/test/api.test.js +++ b/packages/bitcore-wallet-client/test/api.test.js @@ -636,6 +636,31 @@ describe('client API', function() { '0xec068504a817c80082520894a062a07a0a56beb2872b12f388f511d694626730870dd764300b80008081898080' ]); }); + it('should build a sol txp correctly', () => { + const toAddress = 'F7FknkRckx4yvA3Gexnx1H3nwPxndMxVt58BwAzEQhcY'; + const from = '8WyoNvKsmfdG6zrbzNBVN8DETyLra3ond61saU9C52YR'; + const txp = { + version: 3, + from: from, + coin: 'sol', + chain: 'sol', + outputs: [ + { + toAddress: toAddress, + amount: 3896000000000000, + } + ], + fee: 5000, + blockHash: 'GtV1Hb3FvP3HURHAsj8mGwEqCumvP3pv3i6CVCzYNj3d', + blockHeight: 18446744, + amount: 389600000 + }; + var t = Utils.buildTx(txp); + const rawTxp = t.uncheckedSerialize(); + rawTxp.should.deep.equal([ + 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDb6/gH5XxrVl86CZd+DpqA1jN8YSz91e8yXxOlyeS8tLRnckLdZVIkhi0iAExccvYpTw5tIfPZ8z/OJGQtnvg9QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7A+XJrI4siFXUreDo+M94DBeuJwm0Oq5kHqeWuAw7xgBAgIAAQwCAAAAAIALMGTXDQA=' + ]); + }); it('should protect from creating excessive fee DOGE', () => { var toAddress = 'msj42CCGruhRsFrGATiUuh25dtxYtnpbTx'; var changeAddress = 'msj42CCGruhRsFrGATiUuh25dtxYtnpbTx'; @@ -1352,6 +1377,37 @@ describe('client API', function() { '6b1494a6e8121215f40268f58b728585589c6933844b9bbcdae3fdd69be7c000d72c06143f554c5f9fd858a14e9d11cbb7c141901d8fc701c1f3c8c7328d6dc7' ); }); + + it('should sign SOL proposal correctly', () => { + const phrase = 'crush desk brain index action subject tackle idea trim unveil lawn live'; + let k = new Key({ seedData: phrase, seedType: 'mnemonic', algo: 'EDDSA'}); + const toAddress = 'F7FknkRckx4yvA3Gexnx1H3nwPxndMxVt58BwAzEQhcY'; + const from = '7EWwMxKQa5Gru7oTcS1Wi3AaEgTfA6MU3z7MaLUT6hnD'; + const txp = { + version: 3, + from: from, + coin: 'sol', + chain: 'sol', + outputs: [ + { + toAddress: toAddress, + amount: 3896000000000000, + } + ], + fee: 5000, + blockHash: 'GtV1Hb3FvP3HURHAsj8mGwEqCumvP3pv3i6CVCzYNj3d', + blockHeight: 18446744, + amount: 389600000 + }; + const path = "m/44'/501'/0'"; + k.sign(path, txp, undefined, (err, signatures) => { + should.not.exist(err); + signatures.length.should.be.equal(1); + signatures[0].should.equal( + 'AUzAejjW6EhgUAOb25p3DaLMVaKsd3aD9g+iepV6Qs/GqbgraCnwwEpizxMfA8aom9OqwutpliLXg1g9oA4ucwc=' + ); + }); + }); }); }); @@ -2406,6 +2462,9 @@ describe('client API', function() { clients[0].getMainAddresses({}, (err, addr) => { should.not.exist(err); addr.length.should.equal(2); + for(const a of addr) { + a.isChange?.should.not.equal(true); + } done(); }); }); @@ -3026,7 +3085,7 @@ describe('client API', function() { }); describe('SOL address creation', () => { - it.only('should be able to create address in 1-of-1 wallet', done => { + it('should be able to create address in 1-of-1 wallet', done => { this.timeout(50000); var xPriv = 'xprv9s21ZrQH143K3aKdQ6kXF1vj7R6LtkoLCiUXfM5bdbGXmhQkC1iXdnFfrxAAtaTunPUCCLwUQ3cpNixGLMbLAH1gzeCr8VZDe4gPgmKLb2X'; @@ -7310,7 +7369,7 @@ describe('client API', function() { it('should be able to gain access to a 1-1 wallet with just the xPriv', done => { helpers.createAndJoinWallet(clients, keys, 1, 1, {}, () => { - var xPrivKey = keys[0].get(null, true).xPrivKey; + var xPrivKey = keys[0].get(null).xPrivKey; var walletName = clients[0].credentials.walletName; var copayerName = clients[0].credentials.copayerName; clients[0].createAddress((err, addr) => { diff --git a/packages/bitcore-wallet-service/src/config.ts b/packages/bitcore-wallet-service/src/config.ts index 51d956f935..19f4c33924 100644 --- a/packages/bitcore-wallet-service/src/config.ts +++ b/packages/bitcore-wallet-service/src/config.ts @@ -128,6 +128,14 @@ const Config = (): any => { url: 'https://api-eth.bitcore.io' } }, + sol: { + livenet: { + url: 'https://api-sol.bitcore.io' + }, + testnet: { + url: 'https://api-sol.bitcore.io' + } + }, socketApiKey: 'socketApiKey' }, pushNotificationsOpts: { diff --git a/packages/bitcore-wallet-service/src/lib/common/constants.ts b/packages/bitcore-wallet-service/src/lib/common/constants.ts index 550d096f2b..e0bc502e58 100644 --- a/packages/bitcore-wallet-service/src/lib/common/constants.ts +++ b/packages/bitcore-wallet-service/src/lib/common/constants.ts @@ -200,6 +200,7 @@ export const Constants = { ARB_TOKEN_OPTS: CWC.Constants.ARB_TOKEN_OPTS, OP_TOKEN_OPTS: CWC.Constants.OP_TOKEN_OPTS, BASE_TOKEN_OPTS: CWC.Constants.BASE_TOKEN_OPTS, + SOL_TOKEN_OPTS: CWC.Constants.SOL_TOKEN_OPTS, BITPAY_CONTRACTS: { MULTISEND: 'MULTISEND' diff --git a/packages/bitcore-wallet-service/src/lib/pushnotificationsservice.ts b/packages/bitcore-wallet-service/src/lib/pushnotificationsservice.ts index fc7b4ccab9..4cccecd35a 100644 --- a/packages/bitcore-wallet-service/src/lib/pushnotificationsservice.ts +++ b/packages/bitcore-wallet-service/src/lib/pushnotificationsservice.ts @@ -514,7 +514,10 @@ export class PushNotificationsService { } else if (Constants.BASE_TOKEN_OPTS[tokenAddress]) { unit = Constants.BASE_TOKEN_OPTS[tokenAddress].symbol.toLowerCase(); label = UNIT_LABELS[unit]; - } else { + } else if (Constants.SOL_TOKEN_OPTS[tokenAddress]) { + unit = Constants.SOL_TOKEN_OPTS[tokenAddress].symbol.toLowerCase(); + label = UNIT_LABELS[unit]; + }else { let customTokensData; try { customTokensData = await this.getTokenData(data.address.coin); diff --git a/packages/bitcore-wallet-service/test/integration/fiatrateservice.js b/packages/bitcore-wallet-service/test/integration/fiatrateservice.js index 4f5fbc81e8..9122c36649 100644 --- a/packages/bitcore-wallet-service/test/integration/fiatrateservice.js +++ b/packages/bitcore-wallet-service/test/integration/fiatrateservice.js @@ -568,6 +568,16 @@ describe('Fiat rate service', function() { rate: 6.64 } ]; + var sol = [ + { + code: 'USD', + rate: 150 + }, + { + code: 'EUR', + rate: 156 + } + ]; request.get .withArgs({ @@ -623,6 +633,12 @@ describe('Fiat rate service', function() { json: true }) .yields(null, null, ape); + request.get + .withArgs({ + url: 'https://bitpay.com/api/rates/SOL?p=bws', + json: true + }) + .yields(null, null, sol); request.get .withArgs({ url: 'https://bitpay.com/api/rates/WBTC?p=bws', @@ -744,8 +760,19 @@ describe('Fiat rate service', function() { should.not.exist(err); res.fetchedOn.should.equal(100); res.rate.should.equal(121); - clock.restore(); - done(); + service.getRate( + { + code: 'USD', + coin: 'sol' + }, + function(err, res) { + should.not.exist(err); + res.fetchedOn.should.equal(100); + res.rate.should.equal(150); + clock.restore(); + done(); + } + ); } ); } From 85edf2ada78c97e35e05860f7b31088e6e36dd8e Mon Sep 17 00:00:00 2001 From: lyambo Date: Thu, 8 May 2025 09:07:12 -0400 Subject: [PATCH 07/12] cleanup --- .../src/lib/common/utils.ts | 5 +-- packages/bitcore-wallet-client/src/lib/key.ts | 40 +++++++------------ .../src/lib/chain/sol/index.ts | 8 ++-- 3 files changed, 20 insertions(+), 33 deletions(-) diff --git a/packages/bitcore-wallet-client/src/lib/common/utils.ts b/packages/bitcore-wallet-client/src/lib/common/utils.ts index d83a9b52e6..ddbb486233 100644 --- a/packages/bitcore-wallet-client/src/lib/common/utils.ts +++ b/packages/bitcore-wallet-client/src/lib/common/utils.ts @@ -472,10 +472,7 @@ export class Utils { const isToken = tokenAddress && !payProUrl && !isTokenSwap; const isMULTISIG = multisigContractAddress; const chainName = chain.toUpperCase(); - let tokenType = 'ERC20' - if (chainName === 'SOL') { - tokenType = 'SPL'; - } + const tokenType = chainName === 'SOL' ? 'SPL' : 'ERC20' const _chain = isMULTISIG ? chainName + 'MULTISIG' : isToken diff --git a/packages/bitcore-wallet-client/src/lib/key.ts b/packages/bitcore-wallet-client/src/lib/key.ts index c46c843da0..8e5245eeb9 100644 --- a/packages/bitcore-wallet-client/src/lib/key.ts +++ b/packages/bitcore-wallet-client/src/lib/key.ts @@ -72,7 +72,6 @@ export class Key { public use44forMultisig: boolean; public compliantDerivation: boolean; public BIP45: boolean; - public signatureScheme: string public fingerPrint: string; public fingerPrintEDDSA: string /* @@ -147,7 +146,7 @@ export class Key { ...params }); const xPrivKeyEncrypted = this.#getPrivKeyEncrypted(params); - if (xPrivKeyEncrypted) throw new Error('Could not encrypt'); + if (!xPrivKeyEncrypted) throw new Error('Could not encrypt'); } else { this.#setPrivKey({ value: xpriv.toString(), ...params }); } @@ -254,11 +253,12 @@ export class Key { algo }); if (!this.#getPrivKeyEncrypted({ algo })) throw new Error('Could not encrypt'); - this.#mnemonicEncrypted = this.#mnemonicEncrypted || sjcl.encrypt( + this.#mnemonicEncrypted = sjcl.encrypt( opts.password, m.phrase, opts.sjclOpts ); + if (!this.#mnemonicEncrypted) throw new Error('Could not encrypt'); } else { this.#setPrivKey({ value: xpriv.toString(), algo }); this.#mnemonic = m.phrase; @@ -313,7 +313,6 @@ export class Key { get(password, algo?) { let keys: any = {}; - let fingerPrintUpdated = false; if (this.isPrivKeyEncrypted(algo)) { $.checkArgument( @@ -321,17 +320,9 @@ export class Key { 'Private keys are encrypted, a password is needed' ); try { - let xPrivKeyEncrypted = this.#getPrivKeyEncrypted({ algo }); + const xPrivKeyEncrypted = this.#getPrivKeyEncrypted({ algo }); keys.xPrivKey = sjcl.decrypt(password, xPrivKeyEncrypted); - // update fingerPrint if not set. - if (!this.#getFingerprint({ algo })) { - const xpriv = new Bitcore.HDPrivateKey(keys.xPrivKey); - const fingerPrint = xpriv.fingerPrint.toString('hex'); - this.#setFingerprint({ value: fingerPrint, algo }); - fingerPrintUpdated = true; - } - if (this.#mnemonicEncrypted) { keys.mnemonic = sjcl.decrypt(password, this.#mnemonicEncrypted); } @@ -341,9 +332,13 @@ export class Key { } else { keys.xPrivKey = this.#getPrivKey({ algo }); keys.mnemonic = this.#mnemonic; - if (fingerPrintUpdated) { - keys.fingerPrintUpdated = true; - } + } + // update fingerPrint if not set. + if (!this.#getFingerprint({ algo })) { + const xpriv = new Bitcore.HDPrivateKey(keys.xPrivKey); + const fingerPrint = xpriv.fingerPrint.toString('hex'); + this.#setFingerprint({ value: fingerPrint, algo }); + keys.fingerPrintUpdated = true;keys.fingerPrintUpdated = true; } keys.mnemonicHasPassphrase = this.#mnemonicHasPassphrase || false; return keys; @@ -384,7 +379,6 @@ export class Key { derive(password, path, algo?): Bitcore.HDPrivateKey { $.checkArgument(path, 'no path at derive()'); - let deriveFn; if (String(algo).toUpperCase() === Constants.ALGOS.EDDSA) { const key = this.#getChildKeyEDDSA(password, path); return new Bitcore.HDPrivateKey({ @@ -400,7 +394,7 @@ export class Key { this.get(password, algo).xPrivKey, NETWORK ); - deriveFn = this.compliantDerivation + const deriveFn = this.compliantDerivation ? xPrivKey.deriveChild.bind(xPrivKey) : xPrivKey.deriveNonCompliantChild.bind(xPrivKey); return deriveFn(path); @@ -494,12 +488,8 @@ export class Key { $.shouldBeUndefined(opts.useLegacyCoinType); $.shouldBeUndefined(opts.useLegacyPurpose); - let path = this.getBaseAddressDerivationPath(opts); + const path = this.getBaseAddressDerivationPath(opts); let xPrivKey = this.derive(password, path, algo); - let clientDerivedPublicKey; - if (algo === Constants.ALGOS.EDDSA) { - clientDerivedPublicKey = this.#getChildKeyEDDSA(password, path)?.pubKey - } let requestPrivKey = this.derive( password, Constants.PATHS.REQUEST_KEY, @@ -528,7 +518,7 @@ export class Key { requestPrivKey, addressType: opts.addressType, walletPrivKey: opts.walletPrivKey, - clientDerivedPublicKey, + clientDerivedPublicKey: algo === Constants.ALGOS.EDDSA ? this.#getChildKeyEDDSA(password, path)?.pubKey : undefined, }); }; @@ -596,7 +586,7 @@ export class Key { signatures = signatures.map(sig => sig.signature.toDER().toString('hex')); return signatures; - } else if (chain === 'sol') { + } else if (Constants.SVM_CHAINS.includes(chain)) { let tx = t.uncheckedSerialize(); tx = typeof tx === 'string' ? [tx] : tx; const txArray = Array.isArray(tx) ? tx : [tx]; diff --git a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts index 694d14edc1..fcf2477407 100644 --- a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts +++ b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts @@ -1,4 +1,5 @@ import { BitcoreLib as Bitcore, Transactions, Validation } from 'crypto-wallet-core'; +import _ from 'lodash'; import { IChain } from '..'; import { Defaults } from '../../common/defaults'; import { Errors } from '../../errors/errordefinitions'; @@ -122,7 +123,7 @@ export class SolChain implements IChain { uncheckedSerialize: () => unsignedTxs, txid: () => txp.txid, toObject: () => { - let ret = txp; // Utils.deepClone(txp); TODO check this + let ret = _.clone(txp) ret.outputs[0].satoshis = ret.outputs[0].amount; return ret; }, @@ -156,7 +157,6 @@ export class SolChain implements IChain { signature: signatures[index] }); // expected to be in raw string format signedTxs.push(signed); - // TDOD id = the base64 version of the signature. no crucial tx.id = Transactions.getHash({ tx: signed, chain: this.chain }); } tx.uncheckedSerialize = () => signedTxs; @@ -218,8 +218,8 @@ export class SolChain implements IChain { server.getBalance({ wallet }, (err, balance) => { if (err) return cb(err); const { totalAmount, availableAmount } = balance; - // calculate how much spave is needed to find rent amount - const minRentException = Defaults.MIN_XRP_BALANCE; + // calculate how much space is needed to find rent amount + const minRentException = Defaults.MIN_SOL_BALANCE; if (totalAmount - minRentException < txp.getTotalAmount()) { return cb(Errors.INSUFFICIENT_FUNDS); } else if (availableAmount < txp.getTotalAmount()) { From 84c3b8c5c7182ea73022bd6df9b4484a775c406e Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 8 May 2025 11:14:44 -0300 Subject: [PATCH 08/12] [REF] normalize chain value --- packages/crypto-wallet-core/src/derivation/index.ts | 3 ++- packages/crypto-wallet-core/src/transactions/index.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/crypto-wallet-core/src/derivation/index.ts b/packages/crypto-wallet-core/src/derivation/index.ts index 927cee24ce..2a740b515b 100644 --- a/packages/crypto-wallet-core/src/derivation/index.ts +++ b/packages/crypto-wallet-core/src/derivation/index.ts @@ -45,7 +45,8 @@ const derivers: { [chain: string]: IDeriver } = { export class DeriverProxy { get(chain) { - return derivers[chain]; + const normalizedChain = chain.toUpperCase(); + return derivers[normalizedChain]; } /** diff --git a/packages/crypto-wallet-core/src/transactions/index.ts b/packages/crypto-wallet-core/src/transactions/index.ts index 63424935df..3b76f2968e 100644 --- a/packages/crypto-wallet-core/src/transactions/index.ts +++ b/packages/crypto-wallet-core/src/transactions/index.ts @@ -47,7 +47,8 @@ const providers = { export class TransactionsProxy { get({ chain }) { - return providers[chain]; + const normalizedChain = chain.toUpperCase(); + return providers[normalizedChain]; } create(params) { From 4961a907779dc2f2f1e7d0a1373479d38d08df3e Mon Sep 17 00:00:00 2001 From: lyambo Date: Thu, 8 May 2025 11:02:19 -0400 Subject: [PATCH 09/12] feedback --- .../src/lib/chain/sol/index.ts | 28 +++++++++---------- .../src/transactions/index.ts | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts index fcf2477407..34cd423653 100644 --- a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts +++ b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts @@ -43,21 +43,19 @@ export class SolChain implements IChain { // getPendingTxs returns all txps when given a native currency server.getPendingTxs(opts, (err, txps) => { if (err) return cb(err); - let fees = 0; - let amounts = 0; - - txps = txps.filter(txp => { + const { fees, amounts } = txps.reduce((acc, txp) => { // Add gas used for tokens when getting native balance if (!opts.tokenAddress) { - fees += txp.fee || 0; + acc.fees += txp.fee || 0; } + // Filter tokens when getting native balance - if (txp.tokenAddress && !opts.tokenAddress) { - return false; + if (!(txp.tokenAddress && !opts.tokenAddress)) { + acc.amounts += txp.amount; } - amounts += txp.amount; - return true; - }); + + return acc; + }, { fees: 0, amounts: 0 }); const lockedSum = (amounts + fees) || 0; // previously set to 0 if opts.multisigContractAddress const convertedBalance = this.convertBitcoreBalance(balance, lockedSum); @@ -119,7 +117,7 @@ export class SolChain implements IChain { unsignedTxs.push(Transactions.create({ ...txp, chain, ...params })); } - let tx = { + const tx = { uncheckedSerialize: () => unsignedTxs, txid: () => txp.txid, toObject: () => { @@ -135,9 +133,9 @@ export class SolChain implements IChain { if (opts.signed) { const sigs = txp.getCurrentSignatures(); - sigs.forEach(x => { + for (const x of sigs) { this.addSignaturesToBitcoreTx(tx, txp.inputs, txp.inputPaths, x.signatures, x.xpub); - }); + } } return tx; @@ -152,7 +150,7 @@ export class SolChain implements IChain { const signedTxs = []; for (let index = 0; index < signatures.length; index++) { const signed = Transactions.applySignature({ - chain: this.chain, // TODO use lowercase always to avoid confusion + chain: this.chain, tx: unsignedTxs[index], signature: signatures[index] }); // expected to be in raw string format @@ -171,7 +169,7 @@ export class SolChain implements IChain { }); } - getWalletSendMaxInfo(server, wallet, opts, cb) { + getWalletSendMaxInfo(server: WalletService, wallet, opts, cb) { server.getBalance({}, (err, balance) => { if (err) return cb(err); const { availableAmount } = balance; diff --git a/packages/crypto-wallet-core/src/transactions/index.ts b/packages/crypto-wallet-core/src/transactions/index.ts index 63424935df..3e37caa651 100644 --- a/packages/crypto-wallet-core/src/transactions/index.ts +++ b/packages/crypto-wallet-core/src/transactions/index.ts @@ -42,7 +42,7 @@ const providers = { OP: new OPTxProvider(), OPERC20: new OPERC20TxProvider(), SOL: new SOLTxProvider(), - SPL: new SPLTxProvider(), + SOLSPL: new SPLTxProvider(), }; export class TransactionsProxy { From 61bebc90e224ef9120db8f52f3f4514066e873e9 Mon Sep 17 00:00:00 2001 From: lyambo Date: Thu, 8 May 2025 13:55:52 -0400 Subject: [PATCH 10/12] add comment --- packages/bitcore-wallet-service/src/lib/server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/bitcore-wallet-service/src/lib/server.ts b/packages/bitcore-wallet-service/src/lib/server.ts index 1aadb78400..da463d6d2a 100644 --- a/packages/bitcore-wallet-service/src/lib/server.ts +++ b/packages/bitcore-wallet-service/src/lib/server.ts @@ -2639,7 +2639,8 @@ export class WalletService implements IWalletService { next(); }, async next => { - if (!opts.nonce && !Constants.SVM_CHAINS[wallet.chain.toUpperCase()]) { + // SOL is skipped since its a non necessary field that is expected to be provided by the client. + if (!opts.nonce && !Constants.SVM_CHAINS[wallet.chain.toUpperCase()]) { try { opts.nonce = await ChainService.getTransactionCount(this, wallet, opts.from); } catch (error) { From 56d1cb88cc5dbd5ec0a5285b4f403741a146e32b Mon Sep 17 00:00:00 2001 From: lyambo Date: Thu, 8 May 2025 14:59:14 -0400 Subject: [PATCH 11/12] feedback --- packages/bitcore-wallet-service/src/lib/chain/index.ts | 2 +- .../bitcore-wallet-service/src/lib/chain/sol/index.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/bitcore-wallet-service/src/lib/chain/index.ts b/packages/bitcore-wallet-service/src/lib/chain/index.ts index b28dfaba66..4d880bd14a 100644 --- a/packages/bitcore-wallet-service/src/lib/chain/index.ts +++ b/packages/bitcore-wallet-service/src/lib/chain/index.ts @@ -47,7 +47,7 @@ export interface IChain { getChangeAddress(server: WalletService, wallet: IWallet, opts: { changeAddress: string } & any); checkDust(output: { amount: number; toAddress: string; valid: boolean }, opts: { outputs: any[] } & any); checkScriptOutput(output: { script: string; amount: number; }); - getFee(server: WalletService, wallet: IWallet, opts: { fee: number; feePerKb: number } & any); + getFee(server: WalletService, wallet: IWallet, opts: { fee: number; feePerKb: number; signtures?: number } & any); getBitcoreTx(txp: TxProposal, opts: { signed: boolean }); convertFeePerKb(p: number, feePerKb: number); checkTx(server: WalletService, txp: ITxProposal); diff --git a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts index 34cd423653..99e22da80e 100644 --- a/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts +++ b/packages/bitcore-wallet-service/src/lib/chain/sol/index.ts @@ -1,4 +1,4 @@ -import { BitcoreLib as Bitcore, Transactions, Validation } from 'crypto-wallet-core'; +import { BitcoreLib as Bitcore, Transactions, Validation, Web3 } from 'crypto-wallet-core'; import _ from 'lodash'; import { IChain } from '..'; import { Defaults } from '../../common/defaults'; @@ -190,9 +190,11 @@ export class SolChain implements IChain { if ( output.amount == null || output.amount < 0 || - isNaN(output.amount) + isNaN(output.amount) || + Web3.utils.toBN(BigInt(output.amount).toString()).toString() !== BigInt(output.amount).toString() ) { - throw new Error('output.amount is not a valid value: ' + output.amount); + logger.warn('output.amount is not a valid value: ' + output.amount); + return false; } return true; } catch (err) { From f4bc4c283f588a9c3819a6db9e5c84ad50aab253 Mon Sep 17 00:00:00 2001 From: lyambo Date: Thu, 8 May 2025 15:45:34 -0400 Subject: [PATCH 12/12] fix typo --- packages/bitcore-wallet-client/src/lib/key.ts | 2 +- packages/bitcore-wallet-service/src/lib/chain/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bitcore-wallet-client/src/lib/key.ts b/packages/bitcore-wallet-client/src/lib/key.ts index 8e5245eeb9..f29c6c3c30 100644 --- a/packages/bitcore-wallet-client/src/lib/key.ts +++ b/packages/bitcore-wallet-client/src/lib/key.ts @@ -382,7 +382,7 @@ export class Key { if (String(algo).toUpperCase() === Constants.ALGOS.EDDSA) { const key = this.#getChildKeyEDDSA(password, path); return new Bitcore.HDPrivateKey({ - network: 'livenet', + network: NETWORK, depth: 1, parentFingerPrint: Buffer.from(this.#getFingerprint({ algo }), 'hex'), childIndex: 0, diff --git a/packages/bitcore-wallet-service/src/lib/chain/index.ts b/packages/bitcore-wallet-service/src/lib/chain/index.ts index 4d880bd14a..0573609fdc 100644 --- a/packages/bitcore-wallet-service/src/lib/chain/index.ts +++ b/packages/bitcore-wallet-service/src/lib/chain/index.ts @@ -47,7 +47,7 @@ export interface IChain { getChangeAddress(server: WalletService, wallet: IWallet, opts: { changeAddress: string } & any); checkDust(output: { amount: number; toAddress: string; valid: boolean }, opts: { outputs: any[] } & any); checkScriptOutput(output: { script: string; amount: number; }); - getFee(server: WalletService, wallet: IWallet, opts: { fee: number; feePerKb: number; signtures?: number } & any); + getFee(server: WalletService, wallet: IWallet, opts: { fee: number; feePerKb: number; signatures?: number } & any); getBitcoreTx(txp: TxProposal, opts: { signed: boolean }); convertFeePerKb(p: number, feePerKb: number); checkTx(server: WalletService, txp: ITxProposal);