Skip to content

Commit 2bc2ba9

Browse files
authored
Merge pull request #173 from Jopsan-gm/feature/issue-165-soroswap-smart-wallet
feat: enable Soroswap Smart Wallet support with DeFi safety validation (#165)
2 parents c9da86e + cff1f80 commit 2bc2ba9

File tree

13 files changed

+885
-44
lines changed

13 files changed

+885
-44
lines changed

packages/api/rest/output.txt

Whitespace-only changes.

packages/core/defi-protocols/__tests__/protocols/blend-protocol-operations.test.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,41 @@ describe('BlendProtocol - Operations Tests', () => {
355355
blendProtocol.supply(testAddress, testPrivateKey, invalidAsset, '1000000')
356356
).rejects.toThrow('Invalid asset');
357357
});
358+
359+
it('should return pending transaction when privateKey is not provided', async () => {
360+
// Mock transaction builder
361+
const mockTx = {
362+
toXDR: jest.fn(() => 'mocked-xdr')
363+
};
364+
(TransactionBuilder as jest.MockedClass<typeof TransactionBuilder>).mockImplementation(
365+
() =>
366+
({
367+
addOperation: jest.fn().mockReturnThis(),
368+
setTimeout: jest.fn().mockReturnThis(),
369+
build: jest.fn(() => mockTx)
370+
} as any)
371+
);
372+
373+
// Mock simulation success
374+
const mockSimulation = {
375+
result: { mock: 'result' }
376+
};
377+
mockSorobanServer.simulateTransaction = jest
378+
.fn()
379+
.mockResolvedValue(mockSimulation);
380+
(rpc.Api.isSimulationError as jest.Mock).mockReturnValue(false);
381+
382+
// Mock prepareTransaction
383+
const mockPreparedTx = {
384+
toXDR: jest.fn(() => 'prepared-xdr')
385+
};
386+
mockSorobanServer.prepareTransaction = jest.fn().mockResolvedValue(mockPreparedTx);
387+
388+
const result = await blendProtocol.supply(testAddress, null as any, testAsset, '1000000');
389+
390+
expect(result.status).toBe('pending');
391+
expect(mockSorobanServer.prepareTransaction).toHaveBeenCalled();
392+
});
358393
});
359394

360395
// ========================================
@@ -550,6 +585,41 @@ describe('BlendProtocol - Operations Tests', () => {
550585
blendProtocol.withdraw(testAddress, testPrivateKey, invalidAsset, '500000')
551586
).rejects.toThrow('Invalid asset');
552587
});
588+
589+
it('should return pending transaction when privateKey is not provided', async () => {
590+
// Mock transaction builder
591+
const mockTx = {
592+
toXDR: jest.fn(() => 'mocked-xdr')
593+
};
594+
(TransactionBuilder as jest.MockedClass<typeof TransactionBuilder>).mockImplementation(
595+
() =>
596+
({
597+
addOperation: jest.fn().mockReturnThis(),
598+
setTimeout: jest.fn().mockReturnThis(),
599+
build: jest.fn(() => mockTx)
600+
} as any)
601+
);
602+
603+
// Mock simulation success
604+
const mockSimulation = {
605+
result: { mock: 'result' }
606+
};
607+
mockSorobanServer.simulateTransaction = jest
608+
.fn()
609+
.mockResolvedValue(mockSimulation);
610+
(rpc.Api.isSimulationError as jest.Mock).mockReturnValue(false);
611+
612+
// Mock prepareTransaction
613+
const mockPreparedTx = {
614+
toXDR: jest.fn(() => 'prepared-xdr')
615+
};
616+
mockSorobanServer.prepareTransaction = jest.fn().mockResolvedValue(mockPreparedTx);
617+
618+
const result = await blendProtocol.withdraw(testAddress, null as any, testAsset, '500000');
619+
620+
expect(result.status).toBe('pending');
621+
expect(mockSorobanServer.prepareTransaction).toHaveBeenCalled();
622+
});
553623
});
554624

555625
// ========================================
@@ -682,6 +752,41 @@ describe('BlendProtocol - Operations Tests', () => {
682752
blendProtocol.borrow(testAddress, testPrivateKey, invalidAsset, '250000')
683753
).rejects.toThrow('Invalid asset');
684754
});
755+
756+
it('should return pending transaction when privateKey is not provided', async () => {
757+
// Mock transaction builder
758+
const mockTx = {
759+
toXDR: jest.fn(() => 'mocked-xdr')
760+
};
761+
(TransactionBuilder as jest.MockedClass<typeof TransactionBuilder>).mockImplementation(
762+
() =>
763+
({
764+
addOperation: jest.fn().mockReturnThis(),
765+
setTimeout: jest.fn().mockReturnThis(),
766+
build: jest.fn(() => mockTx)
767+
} as any)
768+
);
769+
770+
// Mock simulation success
771+
const mockSimulation = {
772+
result: { mock: 'result' }
773+
};
774+
mockSorobanServer.simulateTransaction = jest
775+
.fn()
776+
.mockResolvedValue(mockSimulation);
777+
(rpc.Api.isSimulationError as jest.Mock).mockReturnValue(false);
778+
779+
// Mock prepareTransaction
780+
const mockPreparedTx = {
781+
toXDR: jest.fn(() => 'prepared-xdr')
782+
};
783+
mockSorobanServer.prepareTransaction = jest.fn().mockResolvedValue(mockPreparedTx);
784+
785+
const result = await blendProtocol.borrow(testAddress, null as any, testAsset, '250000');
786+
787+
expect(result.status).toBe('pending');
788+
expect(mockSorobanServer.prepareTransaction).toHaveBeenCalled();
789+
});
685790
});
686791

687792
// ========================================
@@ -814,6 +919,41 @@ describe('BlendProtocol - Operations Tests', () => {
814919
blendProtocol.repay(testAddress, testPrivateKey, invalidAsset, '250000')
815920
).rejects.toThrow('Invalid asset');
816921
});
922+
923+
it('should return pending transaction when privateKey is not provided', async () => {
924+
// Mock transaction builder
925+
const mockTx = {
926+
toXDR: jest.fn(() => 'mocked-xdr')
927+
};
928+
(TransactionBuilder as jest.MockedClass<typeof TransactionBuilder>).mockImplementation(
929+
() =>
930+
({
931+
addOperation: jest.fn().mockReturnThis(),
932+
setTimeout: jest.fn().mockReturnThis(),
933+
build: jest.fn(() => mockTx)
934+
} as any)
935+
);
936+
937+
// Mock simulation success
938+
const mockSimulation = {
939+
result: { mock: 'result' }
940+
};
941+
mockSorobanServer.simulateTransaction = jest
942+
.fn()
943+
.mockResolvedValue(mockSimulation);
944+
(rpc.Api.isSimulationError as jest.Mock).mockReturnValue(false);
945+
946+
// Mock prepareTransaction
947+
const mockPreparedTx = {
948+
toXDR: jest.fn(() => 'prepared-xdr')
949+
};
950+
mockSorobanServer.prepareTransaction = jest.fn().mockResolvedValue(mockPreparedTx);
951+
952+
const result = await blendProtocol.repay(testAddress, null as any, testAsset, '250000');
953+
954+
expect(result.status).toBe('pending');
955+
expect(mockSorobanServer.prepareTransaction).toHaveBeenCalled();
956+
});
817957
});
818958

819959
// ========================================

packages/core/defi-protocols/__tests__/protocols/blend-protocol.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ describe('BlendProtocol', () => {
111111
let mockHorizonServer: any;
112112

113113
const testAddress = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5';
114+
const testBorrowerAddress = 'GDXFZ4UXBQPTPLQHZJ2IZ3MJRZ6G7CRGSKXM3XDMIFV4KQDLXP2KPXK5';
114115
const testPrivateKey = 'SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4';
115116
const testAsset: Asset = {
116117
code: 'USDC',
@@ -416,7 +417,7 @@ describe('BlendProtocol', () => {
416417
const result = await blendProtocol.liquidate(
417418
testAddress,
418419
testPrivateKey,
419-
'borrower-address',
420+
testBorrowerAddress,
420421
testAsset,
421422
debtAmount,
422423
testAsset
@@ -442,7 +443,7 @@ describe('BlendProtocol', () => {
442443
await expect(blendProtocol.liquidate(
443444
testAddress,
444445
testPrivateKey,
445-
'borrower-address',
446+
testBorrowerAddress,
446447
testAsset,
447448
'50',
448449
testAsset

packages/core/defi-protocols/__tests__/protocols/soroswap-protocol.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,20 @@ describe('SoroswapProtocol', () => {
245245
expect(stats.utilizationRate).toBe(0);
246246
expect(stats.timestamp).toBeInstanceOf(Date);
247247
});
248+
249+
it('should handle errors in getStats gracefully', async () => {
250+
// Force an error by corrupting the internal state
251+
const originalNew = Date.prototype.getTime;
252+
Date.prototype.getTime = (() => { throw new Error('Time error'); }) as any;
253+
254+
const stats = await soroswapProtocol.getStats();
255+
256+
// Should still return default stats due to error handling
257+
expect(stats).toBeDefined();
258+
259+
// Restore original function
260+
Date.prototype.getTime = originalNew;
261+
});
248262
});
249263

250264
// ==========================================
@@ -775,6 +789,25 @@ describe('SoroswapProtocol', () => {
775789
const pool = await soroswapProtocol.getLiquidityPool(tokenA, tokenB);
776790
expect(pool.address).toBe('');
777791
});
792+
793+
it('should handle fromScVal error when parsing pair address', async () => {
794+
(soroswapProtocol as any).sorobanServer.simulateTransaction.mockResolvedValueOnce({
795+
result: { retval: { type: 'address' } }
796+
});
797+
(Address.fromScVal as jest.Mock).mockImplementationOnce(() => {
798+
throw new Error('fromScVal parse error');
799+
});
800+
801+
const pool = await soroswapProtocol.getLiquidityPool(tokenA, tokenB);
802+
expect(pool.address).toBe('');
803+
});
804+
805+
it('should throw on general errors in getLiquidityPool', async () => {
806+
// Force an error by corrupting factoryContract
807+
(soroswapProtocol as any).factoryContract = { call: () => { throw new Error('Contract error'); } };
808+
809+
await expect(soroswapProtocol.getLiquidityPool(tokenA, tokenB)).rejects.toThrow('Contract error');
810+
});
778811
});
779812

780813
// ==========================================
@@ -1034,6 +1067,17 @@ describe('SoroswapProtocol', () => {
10341067

10351068
expect(analytics.feeApr).toBe(0);
10361069
});
1070+
1071+
it('should throw on general errors in getPoolAnalytics', async () => {
1072+
// Force an error by making sorobanServer undefined
1073+
const originalServer = (soroswapProtocol as any).sorobanServer;
1074+
(soroswapProtocol as any).sorobanServer = undefined;
1075+
1076+
await expect(soroswapProtocol.getPoolAnalytics(poolAddress)).rejects.toThrow();
1077+
1078+
// Restore original server
1079+
(soroswapProtocol as any).sorobanServer = originalServer;
1080+
});
10371081
});
10381082

10391083
// ==========================================
@@ -1087,5 +1131,12 @@ describe('SoroswapProtocol', () => {
10871131

10881132
await expect(uninitProtocol.getAllPoolsAnalytics()).rejects.toThrow(/not initialized/);
10891133
});
1134+
1135+
it('should throw on general errors in getAllPoolsAnalytics', async () => {
1136+
// Force an error by making getAllPairs throw an error
1137+
jest.spyOn(soroswapProtocol, 'getAllPairs').mockRejectedValueOnce(new Error('Network error'));
1138+
1139+
await expect(soroswapProtocol.getAllPoolsAnalytics()).rejects.toThrow('Network error');
1140+
});
10901141
});
10911142
});
12.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)