Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions modules/express/src/clientRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,41 @@ async function handleV2SendMany(req: ExpressApiRouteRequest<'express.v2.wallet.s
return result;
}

/**
* handle get account resources
* @param req
*/
async function handleV2AccountResources(req: ExpressApiRouteRequest<'express.v2.wallet.accountresources', 'get'>) {
const bitgo = req.bitgo;
const coin = bitgo.coin(req.decoded.coin);
const wallet = await coin.wallets().get({ id: req.decoded.id });
const addresses = Array.isArray(req.decoded.addresses) ? req.decoded.addresses : [req.decoded.addresses];
return wallet.getAccountResources({
addresses,
destinationAddress: req.decoded.destinationAddress,
});
}

/**
* handle get resource delegations
* @param req
*/
async function handleV2ResourceDelegations(
req: ExpressApiRouteRequest<'express.v2.wallet.resourcedelegations', 'get'>
) {
const bitgo = req.bitgo;
const coin = req.decoded.coin;
const walletId = req.decoded.id;
const query: Record<string, string> = {};
if (req.decoded.type) query.type = req.decoded.type;
if (req.decoded.resource) query.resource = req.decoded.resource;
if (req.decoded.limit) query.limit = req.decoded.limit;
return bitgo
.get(bitgo.url(`/${coin}/wallet/${walletId}/resourcedelegations`, 2))
.query(query)
.result();
}

/**
* payload meant for prebuildAndSignTransaction() in sdk-core which
* validates the payload and makes the appropriate request to WP to
Expand Down Expand Up @@ -1770,6 +1805,16 @@ export function setupAPIRoutes(app: express.Application, config: Config): void {
typedPromiseWrapper(handleV2ConsolidateAccount),
]);

// TRX resource delegation
router.get('express.v2.wallet.accountresources', [
prepareBitGo(config),
typedPromiseWrapper(handleV2AccountResources),
]);
router.get('express.v2.wallet.resourcedelegations', [
prepareBitGo(config),
typedPromiseWrapper(handleV2ResourceDelegations),
]);

// Miscellaneous
router.post('express.canonicaladdress', [prepareBitGo(config), typedPromiseWrapper(handleCanonicalAddress)]);
router.post('express.verifycoinaddress', [prepareBitGo(config), typedPromiseWrapper(handleV2VerifyAddress)]);
Expand Down
18 changes: 18 additions & 0 deletions modules/express/src/typedRoutes/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import { PostWalletEnableTokens } from './v2/walletEnableTokens';
import { PostWalletSweep } from './v2/walletSweep';
import { PostWalletAccelerateTx } from './v2/walletAccelerateTx';
import { PostIsWalletAddress } from './v2/isWalletAddress';
import { GetAccountResources } from './v2/accountResources';
import { GetResourceDelegations } from './v2/resourceDelegations';

// Too large types can cause the following error
//
Expand Down Expand Up @@ -322,6 +324,18 @@ export const ExpressV2WalletAccelerateTxApiSpec = apiSpec({
},
});

export const ExpressV2WalletAccountResourcesApiSpec = apiSpec({
'express.v2.wallet.accountresources': {
get: GetAccountResources,
},
});

export const ExpressV2WalletResourceDelegationsApiSpec = apiSpec({
'express.v2.wallet.resourcedelegations': {
get: GetResourceDelegations,
},
});

export type ExpressApi = typeof ExpressPingApiSpec &
typeof ExpressPingExpressApiSpec &
typeof ExpressLoginApiSpec &
Expand Down Expand Up @@ -360,6 +374,8 @@ export type ExpressApi = typeof ExpressPingApiSpec &
typeof ExpressV2CanonicalAddressApiSpec &
typeof ExpressV2WalletSweepApiSpec &
typeof ExpressV2WalletAccelerateTxApiSpec &
typeof ExpressV2WalletAccountResourcesApiSpec &
typeof ExpressV2WalletResourceDelegationsApiSpec &
typeof ExpressWalletManagementApiSpec;

export const ExpressApi: ExpressApi = {
Expand Down Expand Up @@ -401,6 +417,8 @@ export const ExpressApi: ExpressApi = {
...ExpressV2CanonicalAddressApiSpec,
...ExpressV2WalletSweepApiSpec,
...ExpressV2WalletAccelerateTxApiSpec,
...ExpressV2WalletAccountResourcesApiSpec,
...ExpressV2WalletResourceDelegationsApiSpec,
...ExpressWalletManagementApiSpec,
};

Expand Down
95 changes: 95 additions & 0 deletions modules/express/src/typedRoutes/api/v2/accountResources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as t from 'io-ts';
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
import { BitgoExpressError } from '../../schemas/error';

/**
* Path parameters for account resources endpoint
*/
export const AccountResourcesParams = {
/** Coin identifier (e.g., 'trx', 'ttrx') */
coin: t.string,
/** Wallet ID */
id: t.string,
} as const;

/**
* Query parameters for account resources endpoint
*/
export const AccountResourcesQuery = {
/** On-chain addresses to query resources for (comma-separated string or repeated query params) */
addresses: t.union([t.string, t.array(t.string)]),
/** Optional destination address to calculate energy deficit for token transfers */
destinationAddress: optional(t.string),
} as const;

/**
* Account resource information for a single address
*/
export const AccountResourceInfo = t.intersection([
t.type({
address: t.string,
free_bandwidth_available: t.number,
free_bandwidth_used: t.number,
staked_bandwidth_available: t.number,
staked_bandwidth_used: t.number,
energy_available: t.number,
energy_used: t.number,
resourceDeficitForAssetTransfer: t.intersection([
t.type({
bandwidthDeficit: t.number,
bandwidthSunRequired: t.string,
}),
t.partial({
energyDeficit: t.number,
energySunRequired: t.string,
}),
]),
}),
t.partial({
maxResourcesDelegatable: t.type({
bandwidthWeight: t.string,
energyWeight: t.string,
}),
}),
]);

/**
* Failed address information
*/
export const FailedAddressInfo = t.type({
address: t.string,
error: t.string,
});

/**
* Response for account resources
*/
export const AccountResourcesResponse = {
/** Account resources for the queried addresses */
200: t.type({
resources: t.array(AccountResourceInfo),
failedAddresses: t.array(FailedAddressInfo),
}),
/** Invalid request */
400: BitgoExpressError,
} as const;

/**
* Get Account Resources
*
* Query BANDWIDTH and ENERGY resource information for TRX wallet addresses.
* Returns resource availability, usage, and optional deficit calculations
* for token transfers.
*
* @operationId express.v2.wallet.accountresources
* @tag express
*/
export const GetAccountResources = httpRoute({
path: '/api/v2/{coin}/wallet/{id}/accountresources',
method: 'GET',
request: httpRequest({
params: AccountResourcesParams,
query: AccountResourcesQuery,
}),
response: AccountResourcesResponse,
});
71 changes: 71 additions & 0 deletions modules/express/src/typedRoutes/api/v2/resourceDelegations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as t from 'io-ts';
import { httpRoute, httpRequest, optional } from '@api-ts/io-ts-http';
import { BitgoExpressError } from '../../schemas/error';

/**
* Path parameters for resource delegations endpoint
*/
export const ResourceDelegationsParams = {
/** Coin identifier (e.g., 'trx', 'ttrx') */
coin: t.string,
/** Wallet ID */
id: t.string,
} as const;

/**
* Query parameters for resource delegations endpoint
*/
export const ResourceDelegationsQuery = {
/** Filter by delegation type: 'owner' for outgoing, 'receiver' for incoming */
type: optional(t.union([t.literal('owner'), t.literal('receiver')])),
/** Filter by resource type (case-insensitive: energy, ENERGY, bandwidth, BANDWIDTH) */
resource: optional(t.string),
/** Maximum number of results to return */
limit: optional(t.string),
} as const;

/**
* A single delegation record
*/
export const DelegationRecord = t.type({
from: t.string,
to: t.string,
amount: t.string,
resource: t.string,
});

/**
* Response for resource delegations
*/
export const ResourceDelegationsResponse = {
/** Resource delegations for the wallet */
200: t.type({
address: t.string,
coin: t.string,
delegations: t.type({
outgoing: t.array(DelegationRecord),
incoming: t.array(DelegationRecord),
}),
}),
/** Invalid request */
400: BitgoExpressError,
} as const;

/**
* Get Resource Delegations
*
* Query active outgoing and incoming ENERGY/BANDWIDTH resource delegations
* for a TRX wallet.
*
* @operationId express.v2.wallet.resourcedelegations
* @tag express
*/
export const GetResourceDelegations = httpRoute({
path: '/api/v2/{coin}/wallet/{id}/resourcedelegations',
method: 'GET',
request: httpRequest({
params: ResourceDelegationsParams,
query: ResourceDelegationsQuery,
}),
response: ResourceDelegationsResponse,
});
Loading