Skip to content

Commit 335e26d

Browse files
committed
feat(express): add typed routes for TRX delegation APIs
Add dedicated Express routes for accountresources and activedelegations endpoints with io-ts validation. TICKET: CHALO-346
1 parent 30e144d commit 335e26d

4 files changed

Lines changed: 229 additions & 0 deletions

File tree

modules/express/src/clientRoutes.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,41 @@ async function handleV2SendMany(req: ExpressApiRouteRequest<'express.v2.wallet.s
10121012
return result;
10131013
}
10141014

1015+
/**
1016+
* handle get account resources
1017+
* @param req
1018+
*/
1019+
async function handleV2AccountResources(req: ExpressApiRouteRequest<'express.v2.wallet.accountresources', 'get'>) {
1020+
const bitgo = req.bitgo;
1021+
const coin = bitgo.coin(req.decoded.coin);
1022+
const wallet = await coin.wallets().get({ id: req.decoded.id });
1023+
const addresses = Array.isArray(req.decoded.addresses) ? req.decoded.addresses : [req.decoded.addresses];
1024+
return wallet.getAccountResources({
1025+
addresses,
1026+
destinationAddress: req.decoded.destinationAddress,
1027+
});
1028+
}
1029+
1030+
/**
1031+
* handle get resource delegations
1032+
* @param req
1033+
*/
1034+
async function handleV2ResourceDelegations(
1035+
req: ExpressApiRouteRequest<'express.v2.wallet.resourcedelegations', 'get'>
1036+
) {
1037+
const bitgo = req.bitgo;
1038+
const coin = req.decoded.coin;
1039+
const walletId = req.decoded.id;
1040+
const query: Record<string, string> = {};
1041+
if (req.decoded.type) query.type = req.decoded.type;
1042+
if (req.decoded.resource) query.resource = req.decoded.resource;
1043+
if (req.decoded.limit) query.limit = req.decoded.limit;
1044+
return bitgo
1045+
.get(bitgo.url(`/${coin}/wallet/${walletId}/resourcedelegations`, 2))
1046+
.query(query)
1047+
.result();
1048+
}
1049+
10151050
/**
10161051
* payload meant for prebuildAndSignTransaction() in sdk-core which
10171052
* validates the payload and makes the appropriate request to WP to
@@ -1770,6 +1805,16 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
17701805
typedPromiseWrapper(handleV2ConsolidateAccount),
17711806
]);
17721807

1808+
// TRX resource delegation
1809+
router.get('express.v2.wallet.accountresources', [
1810+
prepareBitGo(config),
1811+
typedPromiseWrapper(handleV2AccountResources),
1812+
]);
1813+
router.get('express.v2.wallet.resourcedelegations', [
1814+
prepareBitGo(config),
1815+
typedPromiseWrapper(handleV2ResourceDelegations),
1816+
]);
1817+
17731818
// Miscellaneous
17741819
router.post('express.canonicaladdress', [prepareBitGo(config), typedPromiseWrapper(handleCanonicalAddress)]);
17751820
router.post('express.verifycoinaddress', [prepareBitGo(config), typedPromiseWrapper(handleV2VerifyAddress)]);

modules/express/src/typedRoutes/api/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import { PostWalletEnableTokens } from './v2/walletEnableTokens';
5151
import { PostWalletSweep } from './v2/walletSweep';
5252
import { PostWalletAccelerateTx } from './v2/walletAccelerateTx';
5353
import { PostIsWalletAddress } from './v2/isWalletAddress';
54+
import { GetAccountResources } from './v2/accountResources';
55+
import { GetResourceDelegations } from './v2/resourceDelegations';
5456

5557
// Too large types can cause the following error
5658
//
@@ -322,6 +324,18 @@ export const ExpressV2WalletAccelerateTxApiSpec = apiSpec({
322324
},
323325
});
324326

327+
export const ExpressV2WalletAccountResourcesApiSpec = apiSpec({
328+
'express.v2.wallet.accountresources': {
329+
get: GetAccountResources,
330+
},
331+
});
332+
333+
export const ExpressV2WalletResourceDelegationsApiSpec = apiSpec({
334+
'express.v2.wallet.resourcedelegations': {
335+
get: GetResourceDelegations,
336+
},
337+
});
338+
325339
export type ExpressApi = typeof ExpressPingApiSpec &
326340
typeof ExpressPingExpressApiSpec &
327341
typeof ExpressLoginApiSpec &
@@ -360,6 +374,8 @@ export type ExpressApi = typeof ExpressPingApiSpec &
360374
typeof ExpressV2CanonicalAddressApiSpec &
361375
typeof ExpressV2WalletSweepApiSpec &
362376
typeof ExpressV2WalletAccelerateTxApiSpec &
377+
typeof ExpressV2WalletAccountResourcesApiSpec &
378+
typeof ExpressV2WalletResourceDelegationsApiSpec &
363379
typeof ExpressWalletManagementApiSpec;
364380

365381
export const ExpressApi: ExpressApi = {
@@ -401,6 +417,8 @@ export const ExpressApi: ExpressApi = {
401417
...ExpressV2CanonicalAddressApiSpec,
402418
...ExpressV2WalletSweepApiSpec,
403419
...ExpressV2WalletAccelerateTxApiSpec,
420+
...ExpressV2WalletAccountResourcesApiSpec,
421+
...ExpressV2WalletResourceDelegationsApiSpec,
404422
...ExpressWalletManagementApiSpec,
405423
};
406424

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import * as t from 'io-ts';
2+
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
3+
import { BitgoExpressError } from '../../schemas/error';
4+
5+
/**
6+
* Path parameters for account resources endpoint
7+
*/
8+
export const AccountResourcesParams = {
9+
/** Coin identifier (e.g., 'trx', 'ttrx') */
10+
coin: t.string,
11+
/** Wallet ID */
12+
id: t.string,
13+
} as const;
14+
15+
/**
16+
* Query parameters for account resources endpoint
17+
*/
18+
export const AccountResourcesQuery = {
19+
/** On-chain addresses to query resources for (comma-separated string or repeated query params) */
20+
addresses: t.union([t.string, t.array(t.string)]),
21+
/** Optional destination address to calculate energy deficit for token transfers */
22+
destinationAddress: optional(t.string),
23+
} as const;
24+
25+
/**
26+
* Account resource information for a single address
27+
*/
28+
export const AccountResourceInfo = t.intersection([
29+
t.type({
30+
address: t.string,
31+
free_bandwidth_available: t.number,
32+
free_bandwidth_used: t.number,
33+
staked_bandwidth_available: t.number,
34+
staked_bandwidth_used: t.number,
35+
energy_available: t.number,
36+
energy_used: t.number,
37+
resourceDeficitForAssetTransfer: t.intersection([
38+
t.type({
39+
bandwidthDeficit: t.number,
40+
bandwidthSunRequired: t.string,
41+
}),
42+
t.partial({
43+
energyDeficit: t.number,
44+
energySunRequired: t.string,
45+
}),
46+
]),
47+
}),
48+
t.partial({
49+
maxResourcesDelegatable: t.type({
50+
bandwidthWeight: t.string,
51+
energyWeight: t.string,
52+
}),
53+
}),
54+
]);
55+
56+
/**
57+
* Failed address information
58+
*/
59+
export const FailedAddressInfo = t.type({
60+
address: t.string,
61+
error: t.string,
62+
});
63+
64+
/**
65+
* Response for account resources
66+
*/
67+
export const AccountResourcesResponse = {
68+
/** Account resources for the queried addresses */
69+
200: t.type({
70+
resources: t.array(AccountResourceInfo),
71+
failedAddresses: t.array(FailedAddressInfo),
72+
}),
73+
/** Invalid request */
74+
400: BitgoExpressError,
75+
} as const;
76+
77+
/**
78+
* Get Account Resources
79+
*
80+
* Query BANDWIDTH and ENERGY resource information for TRX wallet addresses.
81+
* Returns resource availability, usage, and optional deficit calculations
82+
* for token transfers.
83+
*
84+
* @operationId express.v2.wallet.accountresources
85+
* @tag express
86+
*/
87+
export const GetAccountResources = httpRoute({
88+
path: '/api/v2/{coin}/wallet/{id}/accountresources',
89+
method: 'GET',
90+
request: httpRequest({
91+
params: AccountResourcesParams,
92+
query: AccountResourcesQuery,
93+
}),
94+
response: AccountResourcesResponse,
95+
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import * as t from 'io-ts';
2+
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
3+
import { BitgoExpressError } from '../../schemas/error';
4+
5+
/**
6+
* Path parameters for resource delegations endpoint
7+
*/
8+
export const ResourceDelegationsParams = {
9+
/** Coin identifier (e.g., 'trx', 'ttrx') */
10+
coin: t.string,
11+
/** Wallet ID */
12+
id: t.string,
13+
} as const;
14+
15+
/**
16+
* Query parameters for resource delegations endpoint
17+
*/
18+
export const ResourceDelegationsQuery = {
19+
/** Filter by delegation type: 'owner' for outgoing, 'receiver' for incoming */
20+
type: optional(t.union([t.literal('owner'), t.literal('receiver')])),
21+
/** Filter by resource type (case-insensitive: energy, ENERGY, bandwidth, BANDWIDTH) */
22+
resource: optional(t.string),
23+
/** Maximum number of results to return */
24+
limit: optional(t.string),
25+
} as const;
26+
27+
/**
28+
* A single delegation record
29+
*/
30+
export const DelegationRecord = t.type({
31+
from: t.string,
32+
to: t.string,
33+
amount: t.string,
34+
resource: t.string,
35+
});
36+
37+
/**
38+
* Response for resource delegations
39+
*/
40+
export const ResourceDelegationsResponse = {
41+
/** Resource delegations for the wallet */
42+
200: t.type({
43+
address: t.string,
44+
coin: t.string,
45+
delegations: t.type({
46+
outgoing: t.array(DelegationRecord),
47+
incoming: t.array(DelegationRecord),
48+
}),
49+
}),
50+
/** Invalid request */
51+
400: BitgoExpressError,
52+
} as const;
53+
54+
/**
55+
* Get Resource Delegations
56+
*
57+
* Query active outgoing and incoming ENERGY/BANDWIDTH resource delegations
58+
* for a TRX wallet.
59+
*
60+
* @operationId express.v2.wallet.resourcedelegations
61+
* @tag express
62+
*/
63+
export const GetResourceDelegations = httpRoute({
64+
path: '/api/v2/{coin}/wallet/{id}/resourcedelegations',
65+
method: 'GET',
66+
request: httpRequest({
67+
params: ResourceDelegationsParams,
68+
query: ResourceDelegationsQuery,
69+
}),
70+
response: ResourceDelegationsResponse,
71+
});

0 commit comments

Comments
 (0)