Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions packages/bitcore-wallet-client/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <getTokenAllowance()>');
$.checkArgument(opts?.network, 'Missing argument: network at <getTokenAllowance()>');
$.checkArgument(opts?.tokenAddress, 'Missing argument: tokenAddress at <getTokenAllowance()>');
$.checkArgument(opts?.ownerAddress, 'Missing argument: ownerAddress at <getTokenAllowance()>');
$.checkArgument(opts?.spenderAddress, 'Missing argument: spenderAddress at <getTokenAllowance()>');
const { body: allowance } = await this.request.post<object, number>('/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 <getAaveUserAccountData()>');
$.checkArgument(opts?.network, 'Missing argument: network at <getAaveUserAccountData()>');
$.checkArgument(opts?.address, 'Missing argument: address at <getAaveUserAccountData()>');
const { body: accountData } = await this.request.post<object, object>('/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 <getAaveReserveData()>');
$.checkArgument(opts?.network, 'Missing argument: network at <getAaveReserveData()>');
$.checkArgument(opts?.asset, 'Missing argument: asset at <getAaveReserveData()>');
const { body: reserveData } = await this.request.post<object, object>('/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 <getAaveReserveTokensAddresses()>');
$.checkArgument(opts?.network, 'Missing argument: network at <getAaveReserveTokensAddresses()>');
$.checkArgument(opts?.asset, 'Missing argument: asset at <getAaveReserveTokensAddresses()>');
const { body: tokensAddresses } = await this.request.post<object, object>('/v1/service/aave/reserveTokensAddresses', opts);
return tokensAddresses;
}

/**
* Get wallet status based on a string identifier
*/
Expand Down
89 changes: 89 additions & 0 deletions packages/bitcore-wallet-client/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <getAaveUserAccountData()>');
}
});
});

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 <getAaveReserveData()>');
}
});
});

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 <getAaveReserveTokensAddresses()>');
}
});
});

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 <getTokenAllowance()>');
}
});
});
});
});
12 changes: 12 additions & 0 deletions packages/bitcore-wallet-client/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down
42 changes: 42 additions & 0 deletions packages/bitcore-wallet-service/src/lib/blockchainexplorers/v8.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions packages/bitcore-wallet-service/src/lib/expressapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2391,6 +2402,7 @@ export class ExpressApp {
});

/** Imported routes */
router.use(new AaveRouter({ returnError, getServer }).router);
router.use(new TssRouter({ returnError, opts }).router);


Expand Down
48 changes: 48 additions & 0 deletions packages/bitcore-wallet-service/src/lib/routes/aave.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading