Skip to content

Commit 25f26da

Browse files
authored
Merge pull request #8344 from BitGo/CHALO-346.trx-express-delegation-routes
feat(express): add typed routes for TRX delegation APIs
2 parents befb86e + 784adf4 commit 25f26da

7 files changed

Lines changed: 443 additions & 21 deletions

File tree

modules/express/src/clientRoutes.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,43 @@ 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+
export async function handleV2AccountResources(
1020+
req: ExpressApiRouteRequest<'express.v2.wallet.getaccountresources', 'post'>
1021+
) {
1022+
const bitgo = req.bitgo;
1023+
const coin = bitgo.coin(req.decoded.coin);
1024+
const wallet = await coin.wallets().get({ id: req.decoded.id });
1025+
return wallet.getAccountResources({
1026+
addresses: req.decoded.addresses,
1027+
destinationAddress: req.decoded.destinationAddress,
1028+
});
1029+
}
1030+
1031+
/**
1032+
* handle get resource delegations
1033+
* @param req
1034+
*/
1035+
export async function handleV2ResourceDelegations(
1036+
req: ExpressApiRouteRequest<'express.v2.wallet.resourcedelegations', 'get'>
1037+
) {
1038+
const bitgo = req.bitgo;
1039+
const coin = req.decoded.coin;
1040+
const walletId = req.decoded.id;
1041+
const query: Record<string, string> = {};
1042+
if (req.decoded.type) query.type = req.decoded.type;
1043+
if (req.decoded.resource) query.resource = req.decoded.resource;
1044+
if (req.decoded.limit !== undefined) query.limit = String(req.decoded.limit);
1045+
if (req.decoded.nextBatchPrevId) query.nextBatchPrevId = req.decoded.nextBatchPrevId;
1046+
return bitgo
1047+
.get(bitgo.url(`/${coin}/wallet/${walletId}/resourcedelegations`, 2))
1048+
.query(query)
1049+
.result();
1050+
}
1051+
10151052
/**
10161053
* payload meant for prebuildAndSignTransaction() in sdk-core which
10171054
* validates the payload and makes the appropriate request to WP to
@@ -1770,6 +1807,16 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
17701807
typedPromiseWrapper(handleV2ConsolidateAccount),
17711808
]);
17721809

1810+
// TRX resource delegation
1811+
router.post('express.v2.wallet.getaccountresources', [
1812+
prepareBitGo(config),
1813+
typedPromiseWrapper(handleV2AccountResources),
1814+
]);
1815+
router.get('express.v2.wallet.resourcedelegations', [
1816+
prepareBitGo(config),
1817+
typedPromiseWrapper(handleV2ResourceDelegations),
1818+
]);
1819+
17731820
// Miscellaneous
17741821
router.post('express.canonicaladdress', [prepareBitGo(config), typedPromiseWrapper(handleCanonicalAddress)]);
17751822
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.getaccountresources': {
329+
post: 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+
* Body parameters for account resources endpoint
17+
*/
18+
export const AccountResourcesBody = {
19+
/** On-chain addresses to query resources for */
20+
addresses: 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+
}),
38+
t.partial({
39+
resourceDeficitForAssetTransfer: t.intersection([
40+
t.type({
41+
bandwidthDeficit: t.number,
42+
bandwidthSunRequired: t.string,
43+
}),
44+
t.partial({
45+
energyDeficit: t.number,
46+
energySunRequired: t.string,
47+
}),
48+
]),
49+
maxResourcesDelegatable: t.type({
50+
bandwidthSun: t.string,
51+
energySun: 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.getaccountresources
85+
* @tag express
86+
*/
87+
export const GetAccountResources = httpRoute({
88+
path: '/api/v2/{coin}/wallet/{id}/getaccountresources',
89+
method: 'POST',
90+
request: httpRequest({
91+
params: AccountResourcesParams,
92+
body: AccountResourcesBody,
93+
}),
94+
response: AccountResourcesResponse,
95+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as t from 'io-ts';
2+
import { NumberFromString } from 'io-ts-types';
3+
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
4+
import { BitgoExpressError } from '../../schemas/error';
5+
6+
/**
7+
* Path parameters for resource delegations endpoint
8+
*/
9+
export const ResourceDelegationsParams = {
10+
/** Coin identifier (e.g., 'trx', 'ttrx') */
11+
coin: t.string,
12+
/** Wallet ID */
13+
id: t.string,
14+
} as const;
15+
16+
/**
17+
* Query parameters for resource delegations endpoint
18+
*/
19+
export const ResourceDelegationsQuery = {
20+
/** Filter by delegation direction: 'outgoing' for delegations from this address, 'incoming' for delegations to this address; omit to fetch both */
21+
type: optional(t.union([t.literal('outgoing'), t.literal('incoming')])),
22+
/** Filter by resource type (case-insensitive: ENERGY, energy, BANDWIDTH, bandwidth) */
23+
resource: optional(t.string),
24+
/** Maximum number of results to return */
25+
limit: optional(NumberFromString),
26+
/** Pagination cursor from previous response */
27+
nextBatchPrevId: optional(t.string),
28+
} as const;
29+
30+
/**
31+
* A single delegation record
32+
*/
33+
export const DelegationRecord = t.type({
34+
id: t.string,
35+
coin: t.string,
36+
ownerAddress: t.string,
37+
receiverAddress: t.string,
38+
resource: t.union([t.literal('ENERGY'), t.literal('BANDWIDTH')]),
39+
balance: t.string,
40+
updatedAt: t.string,
41+
});
42+
43+
/**
44+
* Response for resource delegations
45+
*/
46+
export const ResourceDelegationsResponse = {
47+
/** Resource delegations for the wallet */
48+
200: t.type({
49+
address: t.string,
50+
coin: t.string,
51+
delegations: t.intersection([
52+
t.type({
53+
outgoing: t.array(DelegationRecord),
54+
incoming: t.array(DelegationRecord),
55+
}),
56+
t.partial({
57+
nextBatchPrevId: t.string,
58+
}),
59+
]),
60+
}),
61+
/** Invalid request */
62+
400: BitgoExpressError,
63+
} as const;
64+
65+
/**
66+
* Get Resource Delegations
67+
*
68+
* Query active outgoing and incoming ENERGY/BANDWIDTH resource delegations
69+
* for a TRX wallet.
70+
*
71+
* @operationId express.v2.wallet.resourcedelegations
72+
* @tag express
73+
*/
74+
export const GetResourceDelegations = httpRoute({
75+
path: '/api/v2/{coin}/wallet/{id}/resourcedelegations',
76+
method: 'GET',
77+
request: httpRequest({
78+
params: ResourceDelegationsParams,
79+
query: ResourceDelegationsQuery,
80+
}),
81+
response: ResourceDelegationsResponse,
82+
});

0 commit comments

Comments
 (0)