Skip to content

Commit

Permalink
add /balances endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Dec 5, 2024
1 parent 1cbb2ea commit fbe6f63
Show file tree
Hide file tree
Showing 4 changed files with 417 additions and 26 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,13 @@ All compact operations require a valid session ID in the `x-session-id` header.
```

4. **Get Resource Lock Balance**

```http
GET /balance/:chainId/:lockId
```

Returns balance information for a specific resource lock. Example response:

```json
{
"allocatableBalance": "1000000000000000000",
Expand All @@ -172,11 +175,34 @@ All compact operations require a valid session ID in the `x-session-id` header.
"withdrawalStatus": 0
}
```

The `balanceAvailableToAllocate` will be:

- `"0"` if `withdrawalStatus` is non-zero
- `"0"` if `allocatedBalance` >= `allocatableBalance`
- `allocatableBalance - allocatedBalance` otherwise

5. **Get All Resource Lock Balances**
```http
GET /balances
```
Returns balance information for all resource locks managed by this allocator. Example response:
```json
{
"balances": [
{
"chainId": "1",
"lockId": "0x1234567890123456789012345678901234567890123456789012345678901234",
"allocatableBalance": "1000000000000000000",
"allocatedBalance": "500000000000000000",
"balanceAvailableToAllocate": "500000000000000000",
"withdrawalStatus": 0
}
]
}
```
Each balance entry follows the same rules as the single balance endpoint.

## API Endpoints

1. **Health Check**
Expand Down
187 changes: 182 additions & 5 deletions src/__tests__/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
AllocatorResponse,
AccountDeltasResponse,
AccountResponse,
AllResourceLocksResponse,
} from '../graphql';
import { RequestDocument, Variables, RequestOptions } from 'graphql-request';

describe('API Routes', () => {
let server: FastifyInstance;
Expand All @@ -26,7 +28,13 @@ describe('API Routes', () => {
originalRequest = graphqlClient.request;

// Mock GraphQL response
graphqlClient.request = async (): Promise<
graphqlClient.request = async <
V extends Variables = Variables,
T = AllocatorResponse & AccountDeltasResponse & AccountResponse,
>(
_documentOrOptions: RequestDocument | RequestOptions<V, T>,
..._variablesAndRequestHeaders: unknown[]
): Promise<
AllocatorResponse & AccountDeltasResponse & AccountResponse
> => ({
allocator: {
Expand Down Expand Up @@ -234,7 +242,13 @@ describe('API Routes', () => {
const originalRequest = graphqlClient.request;

// Mock GraphQL response
graphqlClient.request = async (): Promise<
graphqlClient.request = async <
V extends Variables = Variables,
T = AllocatorResponse & AccountDeltasResponse & AccountResponse,
>(
_documentOrOptions: RequestDocument | RequestOptions<V, T>,
..._variablesAndRequestHeaders: unknown[]
): Promise<
AllocatorResponse & AccountDeltasResponse & AccountResponse
> => ({
allocator: {
Expand Down Expand Up @@ -416,12 +430,18 @@ describe('API Routes', () => {
const originalRequest = graphqlClient.request;

// Mock GraphQL response with no resource lock
graphqlClient.request = async (): Promise<
graphqlClient.request = async <
V extends Variables = Variables,
T = AllocatorResponse & AccountDeltasResponse & AccountResponse,
>(
_documentOrOptions: RequestDocument | RequestOptions<V, T>,
..._variablesAndRequestHeaders: unknown[]
): Promise<
AllocatorResponse & AccountDeltasResponse & AccountResponse
> => ({
allocator: {
supportedChains: {
items: [{ allocatorId: '1' }],
items: [],
},
},
accountDeltas: {
Expand Down Expand Up @@ -458,7 +478,13 @@ describe('API Routes', () => {
const originalRequest = graphqlClient.request;

// Mock GraphQL response with withdrawal status = 1
graphqlClient.request = async (): Promise<
graphqlClient.request = async <
V extends Variables = Variables,
T = AllocatorResponse & AccountDeltasResponse & AccountResponse,
>(
_documentOrOptions: RequestDocument | RequestOptions<V, T>,
..._variablesAndRequestHeaders: unknown[]
): Promise<
AllocatorResponse & AccountDeltasResponse & AccountResponse
> => ({
allocator: {
Expand Down Expand Up @@ -506,5 +532,156 @@ describe('API Routes', () => {
}
});
});

describe('GET /balances', () => {
it('should return balances for all resource locks', async () => {
// Store original function
const originalRequest = graphqlClient.request;

// Mock GraphQL response for getAllResourceLocks
let requestCount = 0;
graphqlClient.request = async <
V extends Variables = Variables,
T =
| AllResourceLocksResponse
| (AllocatorResponse & AccountDeltasResponse & AccountResponse),
>(
_documentOrOptions: RequestDocument | RequestOptions<V, T>,
..._variablesAndRequestHeaders: unknown[]
): Promise<
| AllResourceLocksResponse
| (AllocatorResponse & AccountDeltasResponse & AccountResponse)
> => {
requestCount++;
if (requestCount === 1) {
// First request - getAllResourceLocks
return {
account: {
resourceLocks: {
items: [
{
chainId: '1',
resourceLock: {
lockId: '0x1234',
allocatorAddress: process.env.ALLOCATOR_ADDRESS!, // Add non-null assertion
},
},
{
chainId: '2',
resourceLock: {
lockId: '0x5678',
allocatorAddress: 'different_address',
},
},
],
},
},
};
} else {
// Subsequent requests - getCompactDetails
return {
allocator: {
supportedChains: {
items: [{ allocatorId: '1' }],
},
},
accountDeltas: {
items: [],
},
account: {
resourceLocks: {
items: [
{
withdrawalStatus: 0,
balance: '1000000000000000000000',
},
],
},
claims: {
items: [],
},
},
};
}
};

try {
const response = await server.inject({
method: 'GET',
url: '/balances',
headers: {
'x-session-id': sessionId,
},
});

expect(response.statusCode).toBe(200);
const body = JSON.parse(response.payload);

expect(body).toHaveProperty('balances');
expect(Array.isArray(body.balances)).toBe(true);
expect(body.balances.length).toBe(1); // Only our allocator's locks

const balance = body.balances[0];
expect(balance).toHaveProperty('chainId', '1');
expect(balance).toHaveProperty('lockId', '0x1234');
expect(balance).toHaveProperty('allocatableBalance');
expect(balance).toHaveProperty('allocatedBalance');
expect(balance).toHaveProperty('balanceAvailableToAllocate');
expect(balance).toHaveProperty('withdrawalStatus', 0);
} finally {
// Restore original function
graphqlClient.request = originalRequest;
}
});

it('should return 401 without session', async () => {
const response = await server.inject({
method: 'GET',
url: '/balances',
});

expect(response.statusCode).toBe(401);
});

it('should handle case when no resource locks exist', async () => {
// Store original function
const originalRequest = graphqlClient.request;

// Mock GraphQL response with no locks
graphqlClient.request = async <
V extends Variables = Variables,
T = AllResourceLocksResponse,
>(
_documentOrOptions: RequestDocument | RequestOptions<V, T>,
..._variablesAndRequestHeaders: unknown[]
): Promise<AllResourceLocksResponse> => ({
account: {
resourceLocks: {
items: [],
},
},
});

try {
const response = await server.inject({
method: 'GET',
url: '/balances',
headers: {
'x-session-id': sessionId,
},
});

expect(response.statusCode).toBe(200);
const body = JSON.parse(response.payload);

expect(body).toHaveProperty('balances');
expect(Array.isArray(body.balances)).toBe(true);
expect(body.balances.length).toBe(0);
} finally {
// Restore original function
graphqlClient.request = originalRequest;
}
});
});
});
});
Loading

0 comments on commit fbe6f63

Please sign in to comment.