Skip to content
Merged
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
36 changes: 36 additions & 0 deletions packages/bitcore-wallet-client/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4087,6 +4087,42 @@ export class API extends EventEmitter {
async oneInchGetSwap(data) {
return this.request.post('/v1/service/oneInch/getSwap', data);
}

async moralisGetWalletTokenBalances(data) {
return this.request.post('/v1/moralis/getWalletTokenBalances', data);
}

async moralisGetTokenAllowance(data) {
return this.request.post('/v1/moralis/moralisGetTokenAllowance', data);
}

async moralisGetNativeBalance(data) {
return this.request.post('/v1/moralis/moralisGetNativeBalance', data);
}

async moralisGetTokenPrice(data) {
return this.request.post('/v1/moralis/GetTokenPrice', data);
}

async moralisGetMultipleERC20TokenPrices(data) {
return this.request.post('/v1/moralis/getMultipleERC20TokenPrices', data);
}

async moralisGetERC20TokenBalancesWithPricesByWallet(data) {
return this.request.post('/v1/moralis/getERC20TokenBalancesWithPricesByWallet', data);
}

async moralisGetSolWalletPortfolio(data) {
return this.request.post('/v1/moralis/getSolWalletPortfolio', data);
}

async moralisGetTransactionVerbose(data) {
return this.request.post('/v1/moralis/getTransactionVerbose', data);
}

async moralisGetMultipleSolTokenPrices(data) {
return this.request.post('/v1/moralis/getMultipleSolTokenPrices', data);
}
};

export type Network = 'livenet' | 'testnet' | 'regtest';
Expand Down
1 change: 1 addition & 0 deletions packages/bitcore-wallet-service/src/lib/expressapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export class ExpressApp {

registerMoralisRoutes(router, {
getServer,
getServerWithAuth,
returnError
});

Expand Down
34 changes: 25 additions & 9 deletions packages/bitcore-wallet-service/src/lib/routes/moralis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { WalletService } from '../server';

interface RouteContext {
getServer: Types.GetServerFn;
getServerWithAuth: Types.GetServerWithAuthFn;
returnError: Types.ReturnErrorFn;
}

Expand Down Expand Up @@ -33,6 +34,21 @@ function respondWithPublicServer(req, res, context: RouteContext, handler: Route
});
}

function walletAuthOrPublic(context: RouteContext) {
return function(req, res, next) {
if (!req.header('x-identity')) {
// Not from wallet
return next();
}
return context.getServerWithAuth(req, res, _server => {
if (!req.header('origin')) {
req.headers['origin'] = config.moralis?.whitelist?.[0];
}
return next();
});
};
}

export function registerMoralisRoutes(router: express.Router, context: RouteContext) {
const moralisCorsOptions = {
origin: (origin, cb) => {
Expand All @@ -45,55 +61,55 @@ export function registerMoralisRoutes(router: express.Router, context: RouteCont
}
};

router.post('/v1/moralis/getWalletTokenBalances', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/getWalletTokenBalances', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetWalletTokenBalances(req);
});
});

router.post('/v1/moralis/moralisGetTokenAllowance', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/moralisGetTokenAllowance', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetTokenAllowance(req);
});
});

router.post('/v1/moralis/moralisGetNativeBalance', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/moralisGetNativeBalance', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetNativeBalance(req);
});
});

router.post('/v1/moralis/GetTokenPrice', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/GetTokenPrice', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetTokenPrice(req);
});
});

router.post('/v1/moralis/getMultipleERC20TokenPrices', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/getMultipleERC20TokenPrices', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetMultipleERC20TokenPrices(req);
});
});

router.post('/v1/moralis/getERC20TokenBalancesWithPricesByWallet', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/getERC20TokenBalancesWithPricesByWallet', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetERC20TokenBalancesWithPricesByWallet(req);
});
});

router.post('/v1/moralis/getSolWalletPortfolio', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/getSolWalletPortfolio', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetSolWalletPortfolio(req);
});
});

router.post('/v1/moralis/getTransactionVerbose', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/getTransactionVerbose', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetTransactionVerbose(req);
});
});

router.post('/v1/moralis/getMultipleSolTokenPrices', cors(moralisCorsOptions), (req, res) => {
router.post('/v1/moralis/getMultipleSolTokenPrices', walletAuthOrPublic(context), cors(moralisCorsOptions), (req, res) => {
respondWithPublicServer(req, res, context, server => {
return server.moralisGetMultipleSolTokenPrices(req);
});
Expand Down
83 changes: 83 additions & 0 deletions packages/bitcore-wallet-service/test/expressapp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,89 @@ describe('ExpressApp', function() {
});
});

describe('Moralis routes (wallet-authenticated or public)', function() {
const whitelistedOrigin = 'https://whitelisted-dapp.example';
let originalMoralisConfig;
beforeEach(function() {
originalMoralisConfig = config.moralis;
config.moralis = { whitelist: [whitelistedOrigin] };
});
afterEach(function() {
config.moralis = originalMoralisConfig;
});

it('/v1/moralis/getMultipleERC20TokenPrices should work without auth headers (public/dApp)', function(done) {
const server = {
moralisGetMultipleERC20TokenPrices: sinon.stub().resolves([{ tokenAddress: '0xaaa', usdPrice: 1 }]),
};
sandbox.stub(WalletService, 'initialize').callsArg(1);
sandbox.stub(WalletService, 'getInstance').returns(server);
start(ExpressApp, function() {
const requestOptions = {
url: testHost + ':' + testPort + config.basePath + '/v1/moralis/getMultipleERC20TokenPrices',
method: 'post',
json: { chain: '0x1', tokens: [{ tokenAddress: '0xaaa' }] },
headers: {
origin: whitelistedOrigin
}
};
request(requestOptions, function(err, res, body) {
should.not.exist(err);
res.statusCode.should.equal(200);
body[0].tokenAddress.should.equal('0xaaa');
done();
});
});
});

it('/v1/moralis/getMultipleERC20TokenPrices should work with valid wallet auth headers', function(done) {
const server = {
moralisGetMultipleERC20TokenPrices: sinon.stub().resolves([{ tokenAddress: '0xaaa', usdPrice: 1 }]),
};
sandbox.stub(WalletService, 'initialize').callsArg(1);
sandbox.stub(WalletService, 'getInstance').returns(server);
sandbox.stub(WalletService, 'getInstanceWithAuth').callsArgWith(1, null, server);
start(ExpressApp, function() {
const requestOptions = {
url: testHost + ':' + testPort + config.basePath + '/v1/moralis/getMultipleERC20TokenPrices',
method: 'post',
json: { chain: '0x1', tokens: [{ tokenAddress: '0xaaa' }] },
headers: {
'x-identity': 'identity',
'x-signature': 'signature'
}
};
request(requestOptions, function(err, res, body) {
should.not.exist(err);
res.statusCode.should.equal(200);
body[0].tokenAddress.should.equal('0xaaa');
done();
});
});
});

it('/v1/moralis/getMultipleERC20TokenPrices should fail with invalid wallet auth headers', function(done) {
sandbox.stub(WalletService, 'initialize').callsArg(1);
sandbox.stub(WalletService, 'getInstanceWithAuth').callsArgWith(1, new ClientError('NOT_AUTHORIZED', 'Not authorized'));
start(ExpressApp, function() {
const requestOptions = {
url: testHost + ':' + testPort + config.basePath + '/v1/moralis/getMultipleERC20TokenPrices',
method: 'post',
json: { chain: '0x1', tokens: [{ tokenAddress: '0xaaa' }] },
headers: {
'x-identity': 'identity',
'x-signature': 'bad-signature'
}
};
request(requestOptions, function(err, res) {
should.not.exist(err);
res.statusCode.should.equal(401);
done();
});
});
});
});

describe('Token allowance', function() {
it('/v1/token/allowance', function(done) {
const server = {
Expand Down