Skip to content

Commit b780daa

Browse files
authored
Boxes: Add support for Boxes (#604)
1 parent abab2b9 commit b780daa

22 files changed

+1235
-65
lines changed

src/boxStorage.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { EncodedBoxReference } from './types';
2+
import { BoxReference } from './types/transactions/base';
3+
4+
function translateBoxReference(
5+
reference: BoxReference,
6+
foreignApps: number[],
7+
appIndex: number
8+
): EncodedBoxReference {
9+
const referenceId = reference.appIndex;
10+
const referenceName = reference.name;
11+
const isOwnReference = referenceId === 0 || referenceId === appIndex;
12+
let index = 0;
13+
14+
if (foreignApps != null) {
15+
// Foreign apps start from index 1; index 0 is its own app ID.
16+
index = foreignApps.indexOf(referenceId) + 1;
17+
}
18+
// Check if the app referenced is itself after checking the foreign apps array.
19+
// If index is zero, then the app ID was not found in the foreign apps array
20+
// or the foreign apps array was null.
21+
if (index === 0 && !isOwnReference) {
22+
// Error if the app is trying to reference a foreign app that was not in
23+
// its own foreign apps array.
24+
throw new Error(`Box ref with appId ${referenceId} not in foreign-apps`);
25+
}
26+
return { i: index, n: referenceName };
27+
}
28+
29+
/**
30+
* translateBoxReferences translates an array of BoxReferences with app IDs
31+
* into an array of EncodedBoxReferences with foreign indices.
32+
*/
33+
export function translateBoxReferences(
34+
references: BoxReference[] | undefined,
35+
foreignApps: number[],
36+
appIndex: number
37+
): EncodedBoxReference[] {
38+
if (references == null) return [];
39+
return references.map((bx) =>
40+
translateBoxReference(bx, foreignApps, appIndex)
41+
);
42+
}

src/client/client.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ export default class HTTPClient {
171171
private static prepareResponse(
172172
res: BaseHTTPClientResponse,
173173
format: 'application/msgpack' | 'application/json',
174+
parseBody: boolean,
174175
jsonOptions: utils.JSONOptions = {}
175176
): HTTPClientResponse {
176177
let { body } = res;
@@ -180,7 +181,7 @@ export default class HTTPClient {
180181
text = (body && Buffer.from(body).toString()) || '';
181182
}
182183

183-
if (format === 'application/json') {
184+
if (parseBody && format === 'application/json') {
184185
body = HTTPClient.parseJSON(text, res.status, jsonOptions);
185186
}
186187

@@ -203,7 +204,8 @@ export default class HTTPClient {
203204
// eslint-disable-next-line no-param-reassign
204205
err.response = HTTPClient.prepareResponse(
205206
err.response,
206-
'application/json'
207+
'application/json',
208+
true
207209
);
208210
// eslint-disable-next-line no-param-reassign
209211
err.status = err.response.status;
@@ -218,13 +220,16 @@ export default class HTTPClient {
218220
* @param requestHeaders - An object containing additional request headers to use.
219221
* @param jsonOptions - Options object to use to decode JSON responses. See
220222
* utils.parseJSON for the options available.
223+
* @param parseBody - An optional boolean indicating whether the response body should be parsed
224+
* or not.
221225
* @returns Response object.
222226
*/
223227
async get(
224228
relativePath: string,
225229
query?: Query<any>,
226230
requestHeaders: Record<string, string> = {},
227-
jsonOptions: utils.JSONOptions = {}
231+
jsonOptions: utils.JSONOptions = {},
232+
parseBody: boolean = true
228233
): Promise<HTTPClientResponse> {
229234
const format = getAcceptFormat(query);
230235
const fullHeaders = { ...requestHeaders, accept: format };
@@ -236,7 +241,7 @@ export default class HTTPClient {
236241
fullHeaders
237242
);
238243

239-
return HTTPClient.prepareResponse(res, format, jsonOptions);
244+
return HTTPClient.prepareResponse(res, format, parseBody, jsonOptions);
240245
} catch (err) {
241246
throw HTTPClient.prepareResponseError(err);
242247
}
@@ -251,7 +256,8 @@ export default class HTTPClient {
251256
relativePath: string,
252257
data: any,
253258
requestHeaders: Record<string, string> = {},
254-
query?: Query<any>
259+
query?: Query<any>,
260+
parseBody: boolean = true
255261
): Promise<HTTPClientResponse> {
256262
const fullHeaders = {
257263
'content-type': 'application/json',
@@ -266,7 +272,7 @@ export default class HTTPClient {
266272
fullHeaders
267273
);
268274

269-
return HTTPClient.prepareResponse(res, 'application/json');
275+
return HTTPClient.prepareResponse(res, 'application/json', parseBody);
270276
} catch (err) {
271277
throw HTTPClient.prepareResponseError(err);
272278
}
@@ -280,7 +286,8 @@ export default class HTTPClient {
280286
async delete(
281287
relativePath: string,
282288
data: any,
283-
requestHeaders: Record<string, string> = {}
289+
requestHeaders: Record<string, string> = {},
290+
parseBody: boolean = true
284291
) {
285292
const fullHeaders = {
286293
'content-type': 'application/json',
@@ -294,6 +301,6 @@ export default class HTTPClient {
294301
fullHeaders
295302
);
296303

297-
return HTTPClient.prepareResponse(res, 'application/json');
304+
return HTTPClient.prepareResponse(res, 'application/json', parseBody);
298305
}
299306
}

src/client/v2/algod/algod.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Genesis from './genesis';
1010
import GetAssetByID from './getAssetByID';
1111
import GetApplicationByID from './getApplicationByID';
1212
import GetBlockHash from './getBlockHash';
13+
import GetApplicationBoxByName from './getApplicationBoxByName';
14+
import GetApplicationBoxes from './getApplicationBoxes';
1315
import HealthCheck from './healthCheck';
1416
import PendingTransactionInformation from './pendingTransactionInformation';
1517
import PendingTransactions from './pendingTransactions';
@@ -449,6 +451,48 @@ export default class AlgodClient extends ServiceClient {
449451
return new GetApplicationByID(this.c, this.intDecoding, index);
450452
}
451453

454+
/**
455+
* Given an application ID and the box name (key), return the value stored in the box.
456+
*
457+
* #### Example
458+
* ```typescript
459+
* const index = 60553466;
460+
* const boxName = Buffer.from("foo");
461+
* const boxResponse = await algodClient.getApplicationBoxByName(index, boxName).do();
462+
* const boxValue = boxResponse.value;
463+
* ```
464+
*
465+
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox)
466+
* @param index - The application ID to look up.
467+
* @category GET
468+
*/
469+
getApplicationBoxByName(index: number, boxName: Uint8Array) {
470+
return new GetApplicationBoxByName(
471+
this.c,
472+
this.intDecoding,
473+
index,
474+
boxName
475+
);
476+
}
477+
478+
/**
479+
* Given an application ID, return all the box names associated with the app.
480+
*
481+
* #### Example
482+
* ```typescript
483+
* const index = 60553466;
484+
* const boxesResponse = await algodClient.getApplicationBoxes(index).max(3).do();
485+
* const boxNames = boxesResponse.boxes.map(box => box.name);
486+
* ```
487+
*
488+
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes)
489+
* @param index - The application ID to look up.
490+
* @category GET
491+
*/
492+
getApplicationBoxes(index: number) {
493+
return new GetApplicationBoxes(this.c, this.intDecoding, index);
494+
}
495+
452496
/**
453497
* Returns the entire genesis file.
454498
*
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import JSONRequest from '../jsonrequest';
2+
import HTTPClient from '../../client';
3+
import IntDecoding from '../../../types/intDecoding';
4+
import { Box } from './models/types';
5+
6+
/**
7+
* Given an application ID and the box name (key), return the value stored in the box.
8+
*
9+
* #### Example
10+
* ```typescript
11+
* const index = 60553466;
12+
* const boxName = Buffer.from("foo");
13+
* const boxResponse = await algodClient.getApplicationBoxByName(index, boxName).do();
14+
* const boxValue = boxResponse.value;
15+
* ```
16+
*
17+
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox)
18+
* @param index - The application ID to look up.
19+
* @category GET
20+
*/
21+
export default class GetApplicationBoxByName extends JSONRequest<
22+
Box,
23+
Record<string, any>
24+
> {
25+
constructor(
26+
c: HTTPClient,
27+
intDecoding: IntDecoding,
28+
private index: number,
29+
name: Uint8Array
30+
) {
31+
super(c, intDecoding);
32+
this.index = index;
33+
// Encode name in base64 format and append the encoding prefix.
34+
const encodedName = Buffer.from(name).toString('base64');
35+
this.query.name = encodeURI(`b64:${encodedName}`);
36+
}
37+
38+
/**
39+
* @returns `/v2/applications/${index}/box`
40+
*/
41+
path() {
42+
return `/v2/applications/${this.index}/box`;
43+
}
44+
45+
// eslint-disable-next-line class-methods-use-this
46+
prepare(body: Record<string, any>): Box {
47+
return Box.from_obj_for_encoding(body);
48+
}
49+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import JSONRequest from '../jsonrequest';
2+
import HTTPClient from '../../client';
3+
import IntDecoding from '../../../types/intDecoding';
4+
import { BoxesResponse } from './models/types';
5+
6+
/**
7+
* Given an application ID, return all the box names associated with the app.
8+
*
9+
* #### Example
10+
* ```typescript
11+
* const index = 60553466;
12+
* const boxesResponse = await algodClient.getApplicationBoxes(index).max(3).do();
13+
* const boxNames = boxesResponse.boxes.map(box => box.name);
14+
* ```
15+
*
16+
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes)
17+
* @param index - The application ID to look up.
18+
* @category GET
19+
*/
20+
export default class GetApplicationBoxes extends JSONRequest<
21+
BoxesResponse,
22+
Record<string, any>
23+
> {
24+
constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) {
25+
super(c, intDecoding);
26+
this.index = index;
27+
this.query.max = 0;
28+
}
29+
30+
/**
31+
* @returns `/v2/applications/${index}/boxes`
32+
*/
33+
path() {
34+
return `/v2/applications/${this.index}/boxes`;
35+
}
36+
37+
/**
38+
* Limit results for pagination.
39+
*
40+
* #### Example
41+
* ```typescript
42+
* const maxResults = 20;
43+
* const boxesResult = await algodClient
44+
* .GetApplicationBoxes(1234)
45+
* .limit(maxResults)
46+
* .do();
47+
* ```
48+
*
49+
* @param limit - maximum number of results to return.
50+
* @category query
51+
*/
52+
max(max: number) {
53+
this.query.max = max;
54+
return this;
55+
}
56+
57+
// eslint-disable-next-line class-methods-use-this
58+
prepare(body: Record<string, any>): BoxesResponse {
59+
return BoxesResponse.from_obj_for_encoding(body);
60+
}
61+
}

0 commit comments

Comments
 (0)