diff --git a/packages/bitcore-wallet-client/src/lib/api.ts b/packages/bitcore-wallet-client/src/lib/api.ts index fffe6a33ad..331d068805 100644 --- a/packages/bitcore-wallet-client/src/lib/api.ts +++ b/packages/bitcore-wallet-client/src/lib/api.ts @@ -3161,6 +3161,98 @@ export class API extends EventEmitter { } } + /** + * Get ERC20 token allowance for a given owner/spender pair + */ + async getTokenAllowance( + opts: { + /** EVM chain name (e.g. 'eth', 'matic') */ + chain: string; + /** Network name (e.g. 'mainnet', 'sepolia') */ + network: string; + /** Token contract address */ + tokenAddress: string; + /** Token owner address */ + ownerAddress: string; + /** Spender contract address */ + spenderAddress: string; + } + ) { + $.checkArgument(opts?.chain, 'Missing argument: chain at '); + $.checkArgument(opts?.network, 'Missing argument: network at '); + $.checkArgument(opts?.tokenAddress, 'Missing argument: tokenAddress at '); + $.checkArgument(opts?.ownerAddress, 'Missing argument: ownerAddress at '); + $.checkArgument(opts?.spenderAddress, 'Missing argument: spenderAddress at '); + const { body: allowance } = await this.request.post('/v1/token/allowance', opts); + return allowance; + } + + /** + * Get Aave user account data (health factor, collateral, debt, etc.) + */ + async getAaveUserAccountData( + opts: { + /** EVM chain name (e.g. 'eth', 'matic') */ + chain: string; + /** Network name (e.g. 'mainnet', 'sepolia') */ + network: string; + /** User wallet address */ + address: string; + /** Aave protocol version. Default: 'v3' */ + version?: string; + } + ) { + $.checkArgument(opts?.chain, 'Missing argument: chain at '); + $.checkArgument(opts?.network, 'Missing argument: network at '); + $.checkArgument(opts?.address, 'Missing argument: address at '); + const { body: accountData } = await this.request.post('/v1/service/aave/userAccountData', opts); + return accountData; + } + + /** + * Get Aave reserve data (variable borrow rate, etc.) + */ + async getAaveReserveData( + opts: { + /** EVM chain name (e.g. 'eth', 'matic') */ + chain: string; + /** Network name (e.g. 'mainnet', 'sepolia') */ + network: string; + /** Reserve asset token address */ + asset: string; + /** Aave protocol version. Default: 'v3' */ + version?: string; + } + ) { + $.checkArgument(opts?.chain, 'Missing argument: chain at '); + $.checkArgument(opts?.network, 'Missing argument: network at '); + $.checkArgument(opts?.asset, 'Missing argument: asset at '); + const { body: reserveData } = await this.request.post('/v1/service/aave/reserveData', opts); + return reserveData; + } + + /** + * Get Aave reserve token addresses (aToken, variableDebtToken, etc.) + */ + async getAaveReserveTokensAddresses( + opts: { + /** EVM chain name (e.g. 'eth', 'matic') */ + chain: string; + /** Network name (e.g. 'mainnet', 'sepolia') */ + network: string; + /** Reserve asset token address */ + asset: string; + /** Aave protocol version. Default: 'v3' */ + version?: string; + } + ) { + $.checkArgument(opts?.chain, 'Missing argument: chain at '); + $.checkArgument(opts?.network, 'Missing argument: network at '); + $.checkArgument(opts?.asset, 'Missing argument: asset at '); + const { body: tokensAddresses } = await this.request.post('/v1/service/aave/reserveTokensAddresses', opts); + return tokensAddresses; + } + /** * Get wallet status based on a string identifier */ diff --git a/packages/bitcore-wallet-client/test/api.test.ts b/packages/bitcore-wallet-client/test/api.test.ts index 13286598f0..36f1d0333f 100644 --- a/packages/bitcore-wallet-client/test/api.test.ts +++ b/packages/bitcore-wallet-client/test/api.test.ts @@ -9023,4 +9023,93 @@ describe('client API', function() { }); }); }); + + describe('Aave service', () => { + beforeEach(function(done) { + helpers.createAndJoinWallet(clients, keys, 1, 1, { coin: 'eth', chain: 'eth' }, () => { + done(); + }); + }); + + describe('#getAaveUserAccountData', () => { + it('should get aave user account data', async function() { + const accountData: any = await clients[0].getAaveUserAccountData({ + chain: 'eth', network: 'mainnet', address: '0x123', version: 'v3' + }); + should.exist(accountData); + accountData.totalCollateralBase.should.equal('1000'); + accountData.totalDebtBase.should.equal('500'); + accountData.availableBorrowsBase.should.equal('200'); + accountData.currentLiquidationThreshold.should.equal('8000'); + accountData.ltv.should.equal('7500'); + accountData.healthFactor.should.equal('2.0'); + }); + + it('should fail with missing arguments', async function() { + try { + await clients[0].getAaveUserAccountData({ network: 'mainnet' } as any); + should.fail('should have thrown'); + } catch (err) { + err.message.should.equal('Missing argument: chain at '); + } + }); + }); + + describe('#getAaveReserveData', () => { + it('should get aave reserve data', async function() { + const reserveData: any = await clients[0].getAaveReserveData({ + chain: 'eth', network: 'mainnet', asset: '0xabc', version: 'v3' + }); + should.exist(reserveData); + reserveData.currentVariableBorrowRate.should.equal('35000000000000000000000000'); + }); + + it('should fail with missing arguments', async function() { + try { + await clients[0].getAaveReserveData({ network: 'mainnet' } as any); + should.fail('should have thrown'); + } catch (err) { + err.message.should.equal('Missing argument: chain at '); + } + }); + }); + + describe('#getAaveReserveTokensAddresses', () => { + it('should get aave reserve tokens addresses', async function() { + const tokensAddresses: any = await clients[0].getAaveReserveTokensAddresses({ + chain: 'eth', network: 'mainnet', asset: '0xabc', version: 'v3' + }); + should.exist(tokensAddresses); + tokensAddresses.variableDebtTokenAddress.should.equal('0xdef456'); + }); + + it('should fail with missing arguments', async function() { + try { + await clients[0].getAaveReserveTokensAddresses({ network: 'mainnet' } as any); + should.fail('should have thrown'); + } catch (err) { + err.message.should.equal('Missing argument: chain at '); + } + }); + }); + + describe('#getTokenAllowance', () => { + it('should get token allowance', async function() { + const allowance = await clients[0].getTokenAllowance({ + chain: 'eth', network: 'mainnet', tokenAddress: '0xtoken', ownerAddress: '0xowner', spenderAddress: '0xspender' + }); + should.exist(allowance); + allowance.should.equal(5000000); + }); + + it('should fail with missing arguments', async function() { + try { + await clients[0].getTokenAllowance({ network: 'mainnet' } as any); + should.fail('should have thrown'); + } catch (err) { + err.message.should.equal('Missing argument: chain at '); + } + }); + }); + }); }); diff --git a/packages/bitcore-wallet-client/test/helpers.ts b/packages/bitcore-wallet-client/test/helpers.ts index 48d6036647..ecedd79b8a 100644 --- a/packages/bitcore-wallet-client/test/helpers.ts +++ b/packages/bitcore-wallet-client/test/helpers.ts @@ -346,6 +346,18 @@ export const blockchainExplorerMock = { getTransactionCount: (addr, cb) => { return cb(null, 0); }, + getAaveUserAccountData: (opts, cb) => { + return cb(null, { totalCollateralBase: '1000', totalDebtBase: '500', availableBorrowsBase: '200', currentLiquidationThreshold: '8000', ltv: '7500', healthFactor: '2.0' }); + }, + getAaveReserveData: (opts, cb) => { + return cb(null, { currentVariableBorrowRate: '35000000000000000000000000' }); + }, + getAaveReserveTokensAddresses: (opts, cb) => { + return cb(null, { variableDebtTokenAddress: '0xdef456' }); + }, + getTokenAllowance: (opts, cb) => { + return cb(null, 5000000); + }, reset: () => { blockchainExplorerMock.utxos = []; blockchainExplorerMock.txHistory = []; diff --git a/packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts b/packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts index a9c3af0c27..c588673fe2 100644 --- a/packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts +++ b/packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts @@ -486,6 +486,48 @@ export class V8 { }); } + getAaveUserAccountData(opts: { address: string; version?: string }, cb) { + const url = this.baseUrl + '/aave/account/' + opts.address + '?version=' + (opts.version || 'v3'); + logger.debug('[v8.js] GETTING AAVE USER ACCOUNT DATA %o', url); + this.request + .get(url, {}) + .then(accountData => { + accountData = JSON.parse(accountData); + return cb(null, accountData); + }) + .catch(err => { + return cb(err); + }); + } + + getAaveReserveData(opts: { asset: string; version?: string }, cb) { + const url = this.baseUrl + '/aave/reserve/' + opts.asset + '?version=' + (opts.version || 'v3'); + logger.debug('[v8.js] GETTING AAVE RESERVE DATA %o', url); + this.request + .get(url, {}) + .then(reserveData => { + reserveData = JSON.parse(reserveData); + return cb(null, reserveData); + }) + .catch(err => { + return cb(err); + }); + } + + getAaveReserveTokensAddresses(opts: { asset: string; version?: string }, cb) { + const url = this.baseUrl + '/aave/reserve-tokens/' + opts.asset + '?version=' + (opts.version || 'v3'); + logger.debug('[v8.js] GETTING AAVE RESERVE TOKENS ADDRESSES %o', url); + this.request + .get(url, {}) + .then(tokensAddresses => { + tokensAddresses = JSON.parse(tokensAddresses); + return cb(null, tokensAddresses); + }) + .catch(err => { + return cb(err); + }); + } + getMultisigTxpsInfo(opts: { multisigContractAddress: string }, cb) { const url = this.baseUrl + '/ethmultisig/txps/' + opts.multisigContractAddress; logger.debug('[v8.js] CHECKING CONTRACT TXPS INFO %o', url); diff --git a/packages/bitcore-wallet-service/src/lib/expressapp.ts b/packages/bitcore-wallet-service/src/lib/expressapp.ts index 349ca34ef9..4a2651bd58 100644 --- a/packages/bitcore-wallet-service/src/lib/expressapp.ts +++ b/packages/bitcore-wallet-service/src/lib/expressapp.ts @@ -13,6 +13,7 @@ import { Common } from './common'; import { ClientError } from './errors/clienterror'; import { Errors } from './errors/errordefinitions'; import { logger, transports } from './logger'; +import { AaveRouter } from './routes/aave'; import { error } from './routes/helpers'; import { createWalletLimiter } from './routes/middleware/createWalletLimiter'; import { LogMiddleware } from './routes/middleware/log'; @@ -973,6 +974,16 @@ export class ExpressApp { }); }); + router.post('/v1/token/allowance', async (req, res) => { + try { + const server = getServer(req, res); + const allowance = await server.getTokenAllowance(req.body); + res.json(allowance); + } catch (err) { + returnError(err, res, req); + } + }); + router.get('/v1/sendmaxinfo/', (req, res) => { getServerWithAuth(req, res, server => { const q = req.query; @@ -2391,6 +2402,7 @@ export class ExpressApp { }); /** Imported routes */ + router.use(new AaveRouter({ returnError, getServer }).router); router.use(new TssRouter({ returnError, opts }).router); diff --git a/packages/bitcore-wallet-service/src/lib/routes/aave.ts b/packages/bitcore-wallet-service/src/lib/routes/aave.ts new file mode 100644 index 0000000000..ba8292c52c --- /dev/null +++ b/packages/bitcore-wallet-service/src/lib/routes/aave.ts @@ -0,0 +1,48 @@ +import express from 'express'; +import * as Types from '../../types/expressapp'; + +interface AaveRouterOpts { + returnError: Types.ReturnErrorFn; + getServer: Types.GetServerFn; +} + +export class AaveRouter { + router: express.Router; + + constructor(params: AaveRouterOpts) { + const { returnError, getServer } = params; + const router = express.Router(); + + router.post('/v1/service/aave/userAccountData', async (req, res) => { + try { + const server = getServer(req, res); + const accountData = await server.getAaveUserAccountData(req.body); + res.json(accountData); + } catch (err) { + returnError(err, res, req); + } + }); + + router.post('/v1/service/aave/reserveData', async (req, res) => { + try { + const server = getServer(req, res); + const reserveData = await server.getAaveReserveData(req.body); + res.json(reserveData); + } catch (err) { + returnError(err, res, req); + } + }); + + router.post('/v1/service/aave/reserveTokensAddresses', async (req, res) => { + try { + const server = getServer(req, res); + const tokensAddresses = await server.getAaveReserveTokensAddresses(req.body); + res.json(tokensAddresses); + } catch (err) { + returnError(err, res, req); + } + }); + + this.router = router; + } +} diff --git a/packages/bitcore-wallet-service/src/lib/server.ts b/packages/bitcore-wallet-service/src/lib/server.ts index a53a98ada9..0dd18fda06 100644 --- a/packages/bitcore-wallet-service/src/lib/server.ts +++ b/packages/bitcore-wallet-service/src/lib/server.ts @@ -2559,6 +2559,120 @@ export class WalletService implements IWalletService { }); } + /** + * Get ERC20 token allowance for a given owner/spender pair. + * @param {Object} opts + * @param {string} opts.chain - EVM chain name (e.g. 'eth', 'matic') + * @param {string} opts.network - Network name (e.g. 'mainnet', 'sepolia') + * @param {string} opts.tokenAddress - Token contract address + * @param {string} opts.ownerAddress - Token owner address + * @param {string} opts.spenderAddress - Spender contract address + * @returns {Promise} Token allowance amount + */ + getTokenAllowance(opts) { + if (!checkRequired(opts, ['chain', 'network', 'tokenAddress', 'ownerAddress', 'spenderAddress'])) { + return Promise.reject(new ClientError('getTokenAllowance request missing arguments')); + } + const bc = this._getBlockchainExplorer(opts.chain, opts.network); + return new Promise((resolve, reject) => { + if (!bc) return reject(new Error('Could not get blockchain explorer instance')); + bc.getTokenAllowance(opts, (err, allowance) => { + if (err) { + this.logw('Error getting token allowance:', err); + return reject(err); + } + return resolve(allowance); + }); + }); + } + + /** + * Get Aave user account data (health factor, collateral, debt, etc.). + * @param {Object} opts + * @param {string} opts.chain - EVM chain name (e.g. 'eth', 'matic') + * @param {string} opts.network - Network name (e.g. 'mainnet', 'sepolia') + * @param {string} opts.address - User wallet address + * @param {string} [opts.version='v3'] - Aave protocol version ('v2' or 'v3') + * @returns {Promise} Aave account data (totalCollateralBase, totalDebtBase, healthFactor, etc.) + */ + getAaveUserAccountData(opts) { + if (!checkRequired(opts, ['chain', 'network', 'address'])) { + return Promise.reject(new ClientError('getAaveUserAccountData request missing arguments')); + } + if (opts.version && !['v2', 'v3'].includes(opts.version)) { + return Promise.reject(new ClientError('Invalid Aave version. Supported: v2, v3')); + } + const bc = this._getBlockchainExplorer(opts.chain, opts.network); + return new Promise((resolve, reject) => { + if (!bc) return reject(new Error('Could not get blockchain explorer instance')); + bc.getAaveUserAccountData(opts, (err, accountData) => { + if (err) { + this.logw('Error getting Aave user account data:', err); + return reject(err); + } + return resolve(accountData); + }); + }); + } + + /** + * Get Aave reserve data (variable borrow rate, etc.). + * @param {Object} opts + * @param {string} opts.chain - EVM chain name (e.g. 'eth', 'matic') + * @param {string} opts.network - Network name (e.g. 'mainnet', 'sepolia') + * @param {string} opts.asset - Reserve asset token address + * @param {string} [opts.version='v3'] - Aave protocol version ('v2' or 'v3') + * @returns {Promise} Aave reserve data (currentVariableBorrowRate, etc.) + */ + getAaveReserveData(opts) { + if (!checkRequired(opts, ['chain', 'network', 'asset'])) { + return Promise.reject(new ClientError('getAaveReserveData request missing arguments')); + } + if (opts.version && !['v2', 'v3'].includes(opts.version)) { + return Promise.reject(new ClientError('Invalid Aave version. Supported: v2, v3')); + } + const bc = this._getBlockchainExplorer(opts.chain, opts.network); + return new Promise((resolve, reject) => { + if (!bc) return reject(new Error('Could not get blockchain explorer instance')); + bc.getAaveReserveData(opts, (err, reserveData) => { + if (err) { + this.logw('Error getting Aave reserve data:', err); + return reject(err); + } + return resolve(reserveData); + }); + }); + } + + /** + * Get Aave reserve token addresses (aToken, variableDebtToken, etc.). + * @param {Object} opts + * @param {string} opts.chain - EVM chain name (e.g. 'eth', 'matic') + * @param {string} opts.network - Network name (e.g. 'mainnet', 'sepolia') + * @param {string} opts.asset - Reserve asset token address + * @param {string} [opts.version='v3'] - Aave protocol version ('v2' or 'v3') + * @returns {Promise} Aave reserve token addresses (variableDebtTokenAddress, etc.) + */ + getAaveReserveTokensAddresses(opts) { + if (!checkRequired(opts, ['chain', 'network', 'asset'])) { + return Promise.reject(new ClientError('getAaveReserveTokensAddresses request missing arguments')); + } + if (opts.version && !['v2', 'v3'].includes(opts.version)) { + return Promise.reject(new ClientError('Invalid Aave version. Supported: v2, v3')); + } + const bc = this._getBlockchainExplorer(opts.chain, opts.network); + return new Promise((resolve, reject) => { + if (!bc) return reject(new Error('Could not get blockchain explorer instance')); + bc.getAaveReserveTokensAddresses(opts, (err, tokensAddresses) => { + if (err) { + this.logw('Error getting Aave reserve tokens addresses:', err); + return reject(err); + } + return resolve(tokensAddresses); + }); + }); + } + getMultisigTxpsInfo(opts) { const bc = this._getBlockchainExplorer(opts.chain || Defaults.EVM_CHAIN, opts.network); return new Promise((resolve, reject) => { diff --git a/packages/bitcore-wallet-service/test/expressapp.test.ts b/packages/bitcore-wallet-service/test/expressapp.test.ts index ed61a3ff38..e2a3bde87f 100644 --- a/packages/bitcore-wallet-service/test/expressapp.test.ts +++ b/packages/bitcore-wallet-service/test/expressapp.test.ts @@ -10,6 +10,7 @@ import config from '../src/config'; import { Common } from '../src/lib/common'; import { WalletService } from '../src/lib/server'; import { ExpressApp } from '../src/lib/expressapp'; +import { ClientError } from '../src/lib/errors/clienterror'; const Defaults = Common.Defaults; const should = chai.should(); @@ -483,6 +484,175 @@ describe('ExpressApp', function() { }); }); + describe('Aave service routes', function() { + it('/v1/service/aave/userAccountData', function(done) { + const server = { + getAaveUserAccountData: sinon.stub().resolves({ totalCollateralBase: '1000', healthFactor: '2.0' }), + }; + sandbox.stub(WalletService, 'initialize').callsArg(1); + sandbox.stub(WalletService, 'getInstance').returns(server); + start(ExpressApp, function() { + const requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/userAccountData', + method: 'post', + json: { chain: 'eth', network: 'mainnet', address: '0x123', version: 'v3' } + }; + request(requestOptions, function(err, res, body) { + should.not.exist(err); + res.statusCode.should.equal(200); + body.totalCollateralBase.should.equal('1000'); + body.healthFactor.should.equal('2.0'); + done(); + }); + }); + }); + + it('/v1/service/aave/userAccountData should fail with missing arguments', function(done) { + const server = { + getAaveUserAccountData: sinon.stub().rejects(new ClientError('getAaveUserAccountData request missing arguments')), + }; + sandbox.stub(WalletService, 'initialize').callsArg(1); + sandbox.stub(WalletService, 'getInstance').returns(server); + start(ExpressApp, function() { + const requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/userAccountData', + method: 'post', + json: { version: 'v3' } + }; + request(requestOptions, function(err, res) { + should.not.exist(err); + res.statusCode.should.equal(400); + done(); + }); + }); + }); + + it('/v1/service/aave/reserveData', function(done) { + const server = { + getAaveReserveData: sinon.stub().resolves({ currentVariableBorrowRate: '35000000' }), + }; + sandbox.stub(WalletService, 'initialize').callsArg(1); + sandbox.stub(WalletService, 'getInstance').returns(server); + start(ExpressApp, function() { + const requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/reserveData', + method: 'post', + json: { chain: 'eth', network: 'mainnet', asset: '0xabc', version: 'v3' } + }; + request(requestOptions, function(err, res, body) { + should.not.exist(err); + res.statusCode.should.equal(200); + body.currentVariableBorrowRate.should.equal('35000000'); + done(); + }); + }); + }); + + it('/v1/service/aave/reserveData should fail with missing arguments', function(done) { + const server = { + getAaveReserveData: sinon.stub().rejects(new ClientError('getAaveReserveData request missing arguments')), + }; + sandbox.stub(WalletService, 'initialize').callsArg(1); + sandbox.stub(WalletService, 'getInstance').returns(server); + start(ExpressApp, function() { + const requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/reserveData', + method: 'post', + json: { version: 'v3' } + }; + request(requestOptions, function(err, res) { + should.not.exist(err); + res.statusCode.should.equal(400); + done(); + }); + }); + }); + + it('/v1/service/aave/reserveTokensAddresses', function(done) { + const server = { + getAaveReserveTokensAddresses: sinon.stub().resolves({ variableDebtTokenAddress: '0xdef' }), + }; + sandbox.stub(WalletService, 'initialize').callsArg(1); + sandbox.stub(WalletService, 'getInstance').returns(server); + start(ExpressApp, function() { + const requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/reserveTokensAddresses', + method: 'post', + json: { chain: 'eth', network: 'mainnet', asset: '0xabc', version: 'v3' } + }; + request(requestOptions, function(err, res, body) { + should.not.exist(err); + res.statusCode.should.equal(200); + body.variableDebtTokenAddress.should.equal('0xdef'); + done(); + }); + }); + }); + + it('/v1/service/aave/reserveTokensAddresses should fail with missing arguments', function(done) { + const server = { + getAaveReserveTokensAddresses: sinon.stub().rejects(new ClientError('getAaveReserveTokensAddresses request missing arguments')), + }; + sandbox.stub(WalletService, 'initialize').callsArg(1); + sandbox.stub(WalletService, 'getInstance').returns(server); + start(ExpressApp, function() { + const requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/service/aave/reserveTokensAddresses', + method: 'post', + json: { version: 'v3' } + }; + request(requestOptions, function(err, res) { + should.not.exist(err); + res.statusCode.should.equal(400); + done(); + }); + }); + }); + }); + + describe('Token allowance', function() { + it('/v1/token/allowance', function(done) { + const server = { + getTokenAllowance: sinon.stub().resolves(5000000), + }; + sandbox.stub(WalletService, 'initialize').callsArg(1); + sandbox.stub(WalletService, 'getInstance').returns(server); + start(ExpressApp, function() { + const requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/token/allowance', + method: 'post', + json: { chain: 'eth', network: 'mainnet', tokenAddress: '0xtoken', ownerAddress: '0xowner', spenderAddress: '0xspender' } + }; + request(requestOptions, function(err, res, body) { + should.not.exist(err); + res.statusCode.should.equal(200); + body.should.equal(5000000); + done(); + }); + }); + }); + + it('/v1/token/allowance should fail with missing arguments', function(done) { + const server = { + getTokenAllowance: sinon.stub().rejects(new ClientError('getTokenAllowance request missing arguments')), + }; + sandbox.stub(WalletService, 'initialize').callsArg(1); + sandbox.stub(WalletService, 'getInstance').returns(server); + start(ExpressApp, function() { + const requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/token/allowance', + method: 'post', + json: {} + }; + request(requestOptions, function(err, res) { + should.not.exist(err); + res.statusCode.should.equal(400); + done(); + }); + }); + }); + }); + describe('Clear cache', function() { it('/v1/clearcache/', function(done) { const resolveStub = sinon.stub().callsFake( () => { return Promise.resolve(true);}); diff --git a/packages/bitcore-wallet-service/test/v8.test.ts b/packages/bitcore-wallet-service/test/v8.test.ts index f7edf033b9..59310e205b 100644 --- a/packages/bitcore-wallet-service/test/v8.test.ts +++ b/packages/bitcore-wallet-service/test/v8.test.ts @@ -247,6 +247,109 @@ describe('V8', () => { }); + describe('#getAaveUserAccountData', () => { + it('should get aave user account data', (done) => { + const fakeRequest = { + get: sinon.stub().resolves('{"totalCollateralBase":"1000","totalDebtBase":"500","availableBorrowsBase":"200","currentLiquidationThreshold":"8000","ltv":"7500","healthFactor":"2.0"}'), + }; + + const be = new V8({ + chain: 'eth', + network: 'livenet', + url: 'http://dummy/', + apiPrefix: 'dummyPath', + userAgent: 'testAgent', + request: fakeRequest, + }); + + be.getAaveUserAccountData({ address: '0x123', version: 'v3' }, (err, accountData) => { + should.not.exist(err); + should.exist(accountData); + accountData.totalCollateralBase.should.equal('1000'); + accountData.totalDebtBase.should.equal('500'); + accountData.availableBorrowsBase.should.equal('200'); + accountData.currentLiquidationThreshold.should.equal('8000'); + accountData.ltv.should.equal('7500'); + accountData.healthFactor.should.equal('2.0'); + return done(); + }); + }); + }); + + describe('#getAaveReserveData', () => { + it('should get aave reserve data', (done) => { + const fakeRequest = { + get: sinon.stub().resolves('{"currentVariableBorrowRate":"35000000000000000000000000"}'), + }; + + const be = new V8({ + chain: 'eth', + network: 'livenet', + url: 'http://dummy/', + apiPrefix: 'dummyPath', + userAgent: 'testAgent', + request: fakeRequest, + }); + + be.getAaveReserveData({ asset: '0xabc', version: 'v3' }, (err, reserveData) => { + should.not.exist(err); + should.exist(reserveData); + reserveData.currentVariableBorrowRate.should.equal('35000000000000000000000000'); + return done(); + }); + }); + }); + + describe('#getAaveReserveTokensAddresses', () => { + it('should get aave reserve tokens addresses', (done) => { + const fakeRequest = { + get: sinon.stub().resolves('{"variableDebtTokenAddress":"0xdef456"}'), + }; + + const be = new V8({ + chain: 'eth', + network: 'livenet', + url: 'http://dummy/', + apiPrefix: 'dummyPath', + userAgent: 'testAgent', + request: fakeRequest, + }); + + be.getAaveReserveTokensAddresses({ asset: '0xabc', version: 'v3' }, (err, tokensAddresses) => { + should.not.exist(err); + should.exist(tokensAddresses); + tokensAddresses.variableDebtTokenAddress.should.equal('0xdef456'); + return done(); + }); + }); + }); + + describe('#getTokenAllowance', () => { + it('should get token allowance', (done) => { + const fakeRequest = { + get: sinon.stub().resolves('5000000'), + }; + + const be = new V8({ + chain: 'eth', + network: 'livenet', + url: 'http://dummy/', + apiPrefix: 'dummyPath', + userAgent: 'testAgent', + request: fakeRequest, + }); + + be.getTokenAllowance( + { tokenAddress: '0xtoken', ownerAddress: '0xowner', spenderAddress: '0xspender' }, + (err, allowance) => { + should.not.exist(err); + should.exist(allowance); + allowance.should.equal(5000000); + return done(); + } + ); + }); + }); describe('#broadcast', () => { it('should broadcast a TX', (done) => {