Skip to content

Commit 8a6f17f

Browse files
authored
Merge pull request #95 from Uchechukwu-Ekezie/feat/issue-75-cross-asset-ui
feat: Integrate Cross-Asset Payment UI with pathfinding and Soroban (#75)
2 parents 706e809 + c0f0296 commit 8a6f17f

4 files changed

Lines changed: 268 additions & 123 deletions

File tree

backend/src/controllers/paymentController.ts

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { Request, Response } from 'express';
2-
import { AnchorService } from '../services/anchorService';
3-
import { Keypair } from '@stellar/stellar-sdk';
1+
import type { Request, Response } from 'express';
2+
import { AnchorService } from '../services/anchorService.js';
3+
import { Keypair, Asset } from '@stellar/stellar-sdk';
4+
import { StellarService } from '../services/stellarService.js';
45

56
export class PaymentController {
67
/**
@@ -32,10 +33,10 @@ export class PaymentController {
3233
const clientKeypair = Keypair.fromSecret(secretKey);
3334

3435
// 1. Authenticate
35-
const token = await AnchorService.authenticate(domain, clientKeypair);
36+
const token = await AnchorService.authenticate(domain as string, clientKeypair);
3637

3738
// 2. Initiate Payment
38-
const result = await AnchorService.initiatePayment(domain, token, paymentData);
39+
const result = await AnchorService.initiatePayment(domain as string, token, paymentData);
3940

4041
res.json(result);
4142
} catch (error: any) {
@@ -59,12 +60,66 @@ export class PaymentController {
5960
const clientKeypair = Keypair.fromSecret(secretKey as string);
6061
// Re-auth to get a fresh token or use a session-based approach
6162
// For simplicity in this implementation, we re-auth
62-
const token = await AnchorService.authenticate(domain, clientKeypair);
63+
const token = await AnchorService.authenticate(domain as string, clientKeypair);
6364

64-
const status = await AnchorService.getTransaction(domain, token, id);
65+
const status = await AnchorService.getTransaction(domain as string, token, id);
6566
res.json(status);
6667
} catch (error: any) {
6768
res.status(500).json({ error: error.message });
6869
}
6970
}
71+
72+
/**
73+
* GET /api/payments/paths
74+
* Proxy to Stellar Horizon strictSendPaths
75+
*/
76+
static async getCrossAssetPaths(req: Request, res: Response) {
77+
const { sourceAsset, sourceAmount, destAssets } = req.query;
78+
79+
if (
80+
typeof sourceAsset !== 'string' ||
81+
typeof sourceAmount !== 'string' ||
82+
typeof destAssets !== 'string'
83+
) {
84+
return res.status(400).json({
85+
error: 'Missing or invalid query params: sourceAsset, sourceAmount, destAssets must be strings'
86+
});
87+
}
88+
89+
try {
90+
const server = StellarService.getServer();
91+
92+
// Parse Source Asset
93+
let sourceAssetObj: Asset;
94+
if (sourceAsset === 'XLM') {
95+
sourceAssetObj = Asset.native();
96+
} else {
97+
// Format parsing: CODE:ISSUER
98+
const parts = sourceAsset.split(':');
99+
if (parts.length !== 2) throw new Error('Invalid sourceAsset format. Use CODE:ISSUER or XLM');
100+
sourceAssetObj = new Asset(parts[0] as string, parts[1] as string);
101+
}
102+
103+
// Parse Destination Assets
104+
const destAssetList: Asset[] = destAssets.split(',').map((assetStr) => {
105+
if (assetStr === 'XLM') return Asset.native();
106+
const parts = assetStr.split(':');
107+
if (parts.length !== 2) throw new Error(`Invalid destAsset format: ${assetStr}`);
108+
return new Asset(parts[0] as string, parts[1] as string);
109+
});
110+
111+
// Call Horizon
112+
const pathsResponse = await server
113+
.strictSendPaths(sourceAssetObj, sourceAmount, destAssetList)
114+
.call();
115+
116+
res.json({
117+
paths: pathsResponse.records
118+
});
119+
120+
} catch (error: any) {
121+
console.error('Pathfinding Error:', error);
122+
res.status(500).json({ error: error.message || 'Error fetching conversion paths' });
123+
}
124+
}
70125
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Router } from 'express';
2-
import { PaymentController } from '../controllers/paymentController';
3-
import { require2FA } from '../middlewares/require2fa';
4-
import { authenticateJWT } from '../middlewares/auth';
5-
import { isolateOrganization } from '../middlewares/rbac';
2+
import { PaymentController } from '../controllers/paymentController.js';
3+
import { require2FA } from '../middlewares/require2fa.js';
4+
import authenticateJWT from '../middlewares/auth.js';
5+
import { isolateOrganization } from '../middlewares/rbac.js';
66

77
const router = Router();
88

@@ -11,5 +11,6 @@ router.use(authenticateJWT);
1111
router.get('/anchor-info', PaymentController.getAnchorInfo);
1212
router.post('/sep31/initiate', isolateOrganization, require2FA, PaymentController.initiateSEP31);
1313
router.get('/sep31/status/:domain/:id', PaymentController.getStatus);
14+
router.get('/paths', PaymentController.getCrossAssetPaths);
1415

1516
export default router;

0 commit comments

Comments
 (0)