Skip to content

Commit

Permalink
Feat: oneInch integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Gamboster authored and Ben Clabby committed Jul 9, 2021
1 parent d3f747f commit 81fb954
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 5 deletions.
15 changes: 15 additions & 0 deletions packages/bitcore-wallet-client/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,7 @@ export class API extends EventEmitter {
API._encryptMessage(opts.message, this.credentials.sharedEncryptingKey) ||
null;
args.payProUrl = opts.payProUrl || null;
args.isTokenSwap = opts.isTokenSwap || null;
_.each(args.outputs, o => {
o.message =
API._encryptMessage(o.message, this.credentials.sharedEncryptingKey) ||
Expand Down Expand Up @@ -1434,6 +1435,7 @@ export class API extends EventEmitter {
// * @param {number} opts.fee - Optional. Use an fixed fee for this TX (only when opts.inputs is specified)
// * @param {Boolean} opts.noShuffleOutputs - Optional. If set, TX outputs won't be shuffled. Defaults to false
// * @param {String} opts.signingMethod - Optional. If set, force signing method (ecdsa or schnorr) otherwise use default for coin
// * @param {Boolean} opts.isTokenSwap - Optional. To specify if we are trying to make a token swap
// * @returns {Callback} cb - Return error or the transaction proposal
// * @param {String} baseUrl - Optional. ONLY FOR TESTING
// */
Expand Down Expand Up @@ -3184,4 +3186,17 @@ export class API extends EventEmitter {
);
});
}

oneInchGetSwap(data): Promise<any> {
return new Promise((resolve, reject) => {
this.request.post(
'/v1/service/oneInch/getSwap',
data,
(err, data) => {
if (err) return reject(err);
return resolve(data);
}
);
});
}
}
5 changes: 3 additions & 2 deletions packages/bitcore-wallet-client/src/lib/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,8 @@ export class Utils {
outputs,
payProUrl,
tokenAddress,
multisigContractAddress
multisigContractAddress,
isTokenSwap
} = txp;
const recipients = outputs.map(output => {
return {
Expand All @@ -428,7 +429,7 @@ export class Utils {
recipients[0].data = data;
}
const unsignedTxs = [];
const isERC20 = tokenAddress && !payProUrl;
const isERC20 = tokenAddress && !payProUrl && !isTokenSwap;
const isETHMULTISIG = multisigContractAddress;
const chain = isETHMULTISIG
? 'ETHMULTISIG'
Expand Down
5 changes: 5 additions & 0 deletions packages/bitcore-wallet-service/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ module.exports = {
// secret: 'changelly_secret',
// api: 'https://api.changelly.com'
// },
// oneInch: {
// api: 'https://api.1inch.exchange',
// referrerAddress: 'one_inch_referrer_address', // ETH
// referrerFee: 'one_inch_referrer_fee', // min: 0; max: 3; (represents percentage)
// },
// To use email notifications uncomment this:
// emailOpts: {
// host: 'localhost',
Expand Down
4 changes: 2 additions & 2 deletions packages/bitcore-wallet-service/src/lib/chain/eth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ export class EthChain implements IChain {
}

getBitcoreTx(txp, opts = { signed: true }) {
const { data, outputs, payProUrl, tokenAddress, multisigContractAddress } = txp;
const isERC20 = tokenAddress && !payProUrl;
const { data, outputs, payProUrl, tokenAddress, multisigContractAddress, isTokenSwap } = txp;
const isERC20 = tokenAddress && !payProUrl && !isTokenSwap;
const isETHMULTISIG = multisigContractAddress;
const chain = isETHMULTISIG ? 'ETHMULTISIG' : isERC20 ? 'ERC20' : 'ETH';
const recipients = outputs.map(output => {
Expand Down
30 changes: 30 additions & 0 deletions packages/bitcore-wallet-service/src/lib/expressapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,36 @@ export class ExpressApp {
});
});

router.get('/v1/service/oneInch/getReferrerFee', (req, res) => {
let server;
try {
server = getServer(req, res);
} catch (ex) {
return returnError(ex, res, req);
}
server
.oneInchGetReferrerFee(req)
.then(response => {
res.json(response);
})
.catch(err => {
if (err) return returnError(err, res, req);
});
});

router.post('/v1/service/oneInch/getSwap', (req, res) => {
getServerWithAuth(req, res, server => {
server
.oneInchGetSwap(req)
.then(response => {
res.json(response);
})
.catch(err => {
if (err) return returnError(err, res, req);
});
});
});

router.get('/v1/service/payId/:payId', (req, res) => {
let server;
const payId = req.params['payId'];
Expand Down
4 changes: 4 additions & 0 deletions packages/bitcore-wallet-service/src/lib/model/txproposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export interface ITxProposal {
destinationTag?: string;
invoiceID?: string;
lockUntilBlockHeight?: number;
isTokenSwap?: boolean;
}

export class TxProposal {
Expand Down Expand Up @@ -129,6 +130,7 @@ export class TxProposal {
destinationTag?: string;
invoiceID?: string;
lockUntilBlockHeight?: number;
isTokenSwap?: boolean;

static create(opts) {
opts = opts || {};
Expand Down Expand Up @@ -200,6 +202,7 @@ export class TxProposal {
x.gasLimit = opts.gasLimit; // Backward compatibility for BWC <= 8.9.0
x.data = opts.data; // Backward compatibility for BWC <= 8.9.0
x.tokenAddress = opts.tokenAddress;
x.isTokenSwap = opts.isTokenSwap;
x.multisigContractAddress = opts.multisigContractAddress;

// XRP
Expand Down Expand Up @@ -263,6 +266,7 @@ export class TxProposal {
x.gasLimit = obj.gasLimit; // Backward compatibility for BWC <= 8.9.0
x.data = obj.data; // Backward compatibility for BWC <= 8.9.0
x.tokenAddress = obj.tokenAddress;
x.isTokenSwap = obj.isTokenSwap;
x.multisigContractAddress = obj.multisigContractAddress;
x.multisigTxId = obj.multisigTxId;

Expand Down
68 changes: 67 additions & 1 deletion packages/bitcore-wallet-service/src/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2215,6 +2215,7 @@ export class WalletService {
* @param {Boolean} opts.signingMethod[=ecdsa] - do not use cashaddress for bch
* @param {string} opts.tokenAddress - optional. ERC20 Token Contract Address
* @param {string} opts.multisigContractAddress - optional. MULTISIG ETH Contract Address
* @param {Boolean} opts.isTokenSwap - Optional. To specify if we are trying to make a token swap
* @returns {TxProposal} Transaction proposal. outputs address format will use the same format as inpunt.
*/
createTx(opts, cb) {
Expand Down Expand Up @@ -2352,7 +2353,8 @@ export class WalletService {
multisigContractAddress: opts.multisigContractAddress,
destinationTag: opts.destinationTag,
invoiceID: opts.invoiceID,
signingMethod: opts.signingMethod
signingMethod: opts.signingMethod,
isTokenSwap: opts.isTokenSwap
};
txp = TxProposal.create(txOpts);
next();
Expand Down Expand Up @@ -4862,6 +4864,70 @@ export class WalletService {
});
}

oneInchGetCredentials() {
if (!config.oneInch) throw new Error('1Inch missing credentials');

const credentials = {
API: config.oneInch.api,
referrerAddress: config.oneInch.referrerAddress,
referrerFee: config.oneInch.referrerFee
};

return credentials;
}

oneInchGetReferrerFee(req): Promise<any> {
return new Promise((resolve, reject) => {
const credentials = this.oneInchGetCredentials();

const referrerFee: number = credentials.referrerFee;

resolve({ referrerFee });
});
}

oneInchGetSwap(req): Promise<any> {
return new Promise((resolve, reject) => {
const credentials = this.oneInchGetCredentials();

if (!checkRequired(req.body, ['fromTokenAddress', 'toTokenAddress', 'amount', 'fromAddress', 'slippage', 'destReceiver'])) {
return reject(new ClientError('oneInchGetSwap request missing arguments'));
}

const headers = {
'Content-Type': 'application/json'
};

let qs = [];
qs.push('fromTokenAddress=' + req.body.fromTokenAddress);
qs.push('toTokenAddress=' + req.body.toTokenAddress);
qs.push('amount=' + req.body.amount);
qs.push('fromAddress=' + req.body.fromAddress);
qs.push('slippage=' + req.body.slippage);
qs.push('destReceiver=' + req.body.destReceiver);

if(credentials.referrerFee) qs.push('fee=' + credentials.referrerFee);
if(credentials.referrerAddress) qs.push('referrerAddress=' + credentials.referrerAddress);

const URL: string = credentials.API + '/v3.0/1/swap/?' + qs.join('&');

this.request.get(
URL,
{
headers,
json: true
},
(err, data) => {
if (err) {
return reject(err.body ?? err);
} else {
return resolve(data.body);
}
}
);
});
}

getPayId(url: string): Promise<any> {
return new Promise((resolve, reject) => {
const headers = {
Expand Down
148 changes: 148 additions & 0 deletions packages/bitcore-wallet-service/test/integration/oneInch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
'use strict';

const chai = require('chai');
const should = chai.should();
const { WalletService } = require('../../ts_build/lib/server');
const TestData = require('../testdata');
const helpers = require('./helpers');

let config = require('../../ts_build/config.js');
let server, wallet, fakeRequest, req;

describe('OneInch integration', () => {
before((done) => {
helpers.before((res) => {
done();
});
});
beforeEach((done) => {
config.suspendedChains = [];
config.oneInch = {
api: 'xxxx',
referrerAddress: 'referrerAddress',
referrerFee: 'referrerFee1'
}

fakeRequest = {
post: (_url, _opts, _cb) => { return _cb(null, { body: 'data'}) },
get: (_url, _opts, _cb) => { return _cb(null, { body: 'data'}) },
};

helpers.beforeEach((res) => {
helpers.createAndJoinWallet(1, 1, (s, w) => {
wallet = w;
const priv = TestData.copayers[0].privKey_1H_0;
const sig = helpers.signMessage('hello world', priv);

WalletService.getInstanceWithAuth({
// test assumes wallet's copayer[0] is TestData's copayer[0]
copayerId: wallet.copayers[0].id,
message: 'hello world',
signature: sig,
clientVersion: 'bwc-2.0.0',
walletId: '123',
}, (err, s) => {
should.not.exist(err);
server = s;
done();
});
});
});
});
after((done) => {
helpers.after(done);
});

describe('#oneInchGetReferrerFee', () => {
beforeEach(() => {
req = {}
});

it('should get referrel fee if it is defined in config', () => {
server.request = fakeRequest;
server.oneInchGetReferrerFee(req).then(data => {
should.exist(data);
data.referrerFee.should.equal('referrerFee1');
}).catch(err => {
should.not.exist(err);
});
});

it('should return error if oneInch is commented in config', () => {
config.oneInch = undefined;

server.request = fakeRequest;
server.oneInchGetReferrerFee(req).then(data => {
should.not.exist(data);
}).catch(err => {
should.exist(err);
err.message.should.equal('1Inch missing credentials');
});
});
});

describe('#oneInchGetSwap', () => {
beforeEach(() => {
req = {
headers: {},
body: {
fromTokenAddress: 'fromTokenAddress1',
toTokenAddress: 'toTokenAddress1',
amount: 100,
fromAddress: 'fromAddress1',
slippage: 0.5,
destReceiver: 'destReceiver1'
}
}
});

it('should work properly if req is OK', () => {
server.request = fakeRequest;
server.oneInchGetSwap(req).then(data => {
should.exist(data);
}).catch(err => {
should.not.exist(err);
});
});

it('should return error if there is some missing arguments', () => {
delete req.body.fromTokenAddress;

server.request = fakeRequest;
server.oneInchGetSwap(req).then(data => {
should.not.exist(data);
}).catch(err => {
should.exist(err);
err.message.should.equal('oneInchGetSwap request missing arguments');
});
});

it('should return error if request returns error', () => {
req.body.fromTokenAddress = 'fromTokenAddress1';
const fakeRequest2 = {
post: (_url, _opts, _cb) => { return _cb(new Error('Error')) },
get: (_url, _opts, _cb) => { return _cb(new Error('Error')) }
};

server.request = fakeRequest2;
server.oneInchGetSwap(req).then(data => {
should.not.exist(data);
}).catch(err => {
should.exist(err);
err.message.should.equal('Error');
});
});

it('should return error if oneInch is commented in config', () => {
config.oneInch = undefined;

server.request = fakeRequest;
server.oneInchGetSwap(req).then(data => {
should.not.exist(data);
}).catch(err => {
should.exist(err);
err.message.should.equal('1Inch missing credentials');
});
});
});
});

0 comments on commit 81fb954

Please sign in to comment.