Skip to content

Commit

Permalink
Boxes: Add support for Boxes (#604)
Browse files Browse the repository at this point in the history
  • Loading branch information
algochoi authored Nov 2, 2022
1 parent abab2b9 commit b780daa
Show file tree
Hide file tree
Showing 22 changed files with 1,235 additions and 65 deletions.
42 changes: 42 additions & 0 deletions src/boxStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { EncodedBoxReference } from './types';
import { BoxReference } from './types/transactions/base';

function translateBoxReference(
reference: BoxReference,
foreignApps: number[],
appIndex: number
): EncodedBoxReference {
const referenceId = reference.appIndex;
const referenceName = reference.name;
const isOwnReference = referenceId === 0 || referenceId === appIndex;
let index = 0;

if (foreignApps != null) {
// Foreign apps start from index 1; index 0 is its own app ID.
index = foreignApps.indexOf(referenceId) + 1;
}
// Check if the app referenced is itself after checking the foreign apps array.
// If index is zero, then the app ID was not found in the foreign apps array
// or the foreign apps array was null.
if (index === 0 && !isOwnReference) {
// Error if the app is trying to reference a foreign app that was not in
// its own foreign apps array.
throw new Error(`Box ref with appId ${referenceId} not in foreign-apps`);
}
return { i: index, n: referenceName };
}

/**
* translateBoxReferences translates an array of BoxReferences with app IDs
* into an array of EncodedBoxReferences with foreign indices.
*/
export function translateBoxReferences(
references: BoxReference[] | undefined,
foreignApps: number[],
appIndex: number
): EncodedBoxReference[] {
if (references == null) return [];
return references.map((bx) =>
translateBoxReference(bx, foreignApps, appIndex)
);
}
23 changes: 15 additions & 8 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export default class HTTPClient {
private static prepareResponse(
res: BaseHTTPClientResponse,
format: 'application/msgpack' | 'application/json',
parseBody: boolean,
jsonOptions: utils.JSONOptions = {}
): HTTPClientResponse {
let { body } = res;
Expand All @@ -180,7 +181,7 @@ export default class HTTPClient {
text = (body && Buffer.from(body).toString()) || '';
}

if (format === 'application/json') {
if (parseBody && format === 'application/json') {
body = HTTPClient.parseJSON(text, res.status, jsonOptions);
}

Expand All @@ -203,7 +204,8 @@ export default class HTTPClient {
// eslint-disable-next-line no-param-reassign
err.response = HTTPClient.prepareResponse(
err.response,
'application/json'
'application/json',
true
);
// eslint-disable-next-line no-param-reassign
err.status = err.response.status;
Expand All @@ -218,13 +220,16 @@ export default class HTTPClient {
* @param requestHeaders - An object containing additional request headers to use.
* @param jsonOptions - Options object to use to decode JSON responses. See
* utils.parseJSON for the options available.
* @param parseBody - An optional boolean indicating whether the response body should be parsed
* or not.
* @returns Response object.
*/
async get(
relativePath: string,
query?: Query<any>,
requestHeaders: Record<string, string> = {},
jsonOptions: utils.JSONOptions = {}
jsonOptions: utils.JSONOptions = {},
parseBody: boolean = true
): Promise<HTTPClientResponse> {
const format = getAcceptFormat(query);
const fullHeaders = { ...requestHeaders, accept: format };
Expand All @@ -236,7 +241,7 @@ export default class HTTPClient {
fullHeaders
);

return HTTPClient.prepareResponse(res, format, jsonOptions);
return HTTPClient.prepareResponse(res, format, parseBody, jsonOptions);
} catch (err) {
throw HTTPClient.prepareResponseError(err);
}
Expand All @@ -251,7 +256,8 @@ export default class HTTPClient {
relativePath: string,
data: any,
requestHeaders: Record<string, string> = {},
query?: Query<any>
query?: Query<any>,
parseBody: boolean = true
): Promise<HTTPClientResponse> {
const fullHeaders = {
'content-type': 'application/json',
Expand All @@ -266,7 +272,7 @@ export default class HTTPClient {
fullHeaders
);

return HTTPClient.prepareResponse(res, 'application/json');
return HTTPClient.prepareResponse(res, 'application/json', parseBody);
} catch (err) {
throw HTTPClient.prepareResponseError(err);
}
Expand All @@ -280,7 +286,8 @@ export default class HTTPClient {
async delete(
relativePath: string,
data: any,
requestHeaders: Record<string, string> = {}
requestHeaders: Record<string, string> = {},
parseBody: boolean = true
) {
const fullHeaders = {
'content-type': 'application/json',
Expand All @@ -294,6 +301,6 @@ export default class HTTPClient {
fullHeaders
);

return HTTPClient.prepareResponse(res, 'application/json');
return HTTPClient.prepareResponse(res, 'application/json', parseBody);
}
}
44 changes: 44 additions & 0 deletions src/client/v2/algod/algod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Genesis from './genesis';
import GetAssetByID from './getAssetByID';
import GetApplicationByID from './getApplicationByID';
import GetBlockHash from './getBlockHash';
import GetApplicationBoxByName from './getApplicationBoxByName';
import GetApplicationBoxes from './getApplicationBoxes';
import HealthCheck from './healthCheck';
import PendingTransactionInformation from './pendingTransactionInformation';
import PendingTransactions from './pendingTransactions';
Expand Down Expand Up @@ -449,6 +451,48 @@ export default class AlgodClient extends ServiceClient {
return new GetApplicationByID(this.c, this.intDecoding, index);
}

/**
* Given an application ID and the box name (key), return the value stored in the box.
*
* #### Example
* ```typescript
* const index = 60553466;
* const boxName = Buffer.from("foo");
* const boxResponse = await algodClient.getApplicationBoxByName(index, boxName).do();
* const boxValue = boxResponse.value;
* ```
*
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox)
* @param index - The application ID to look up.
* @category GET
*/
getApplicationBoxByName(index: number, boxName: Uint8Array) {
return new GetApplicationBoxByName(
this.c,
this.intDecoding,
index,
boxName
);
}

/**
* Given an application ID, return all the box names associated with the app.
*
* #### Example
* ```typescript
* const index = 60553466;
* const boxesResponse = await algodClient.getApplicationBoxes(index).max(3).do();
* const boxNames = boxesResponse.boxes.map(box => box.name);
* ```
*
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes)
* @param index - The application ID to look up.
* @category GET
*/
getApplicationBoxes(index: number) {
return new GetApplicationBoxes(this.c, this.intDecoding, index);
}

/**
* Returns the entire genesis file.
*
Expand Down
49 changes: 49 additions & 0 deletions src/client/v2/algod/getApplicationBoxByName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import JSONRequest from '../jsonrequest';
import HTTPClient from '../../client';
import IntDecoding from '../../../types/intDecoding';
import { Box } from './models/types';

/**
* Given an application ID and the box name (key), return the value stored in the box.
*
* #### Example
* ```typescript
* const index = 60553466;
* const boxName = Buffer.from("foo");
* const boxResponse = await algodClient.getApplicationBoxByName(index, boxName).do();
* const boxValue = boxResponse.value;
* ```
*
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idbox)
* @param index - The application ID to look up.
* @category GET
*/
export default class GetApplicationBoxByName extends JSONRequest<
Box,
Record<string, any>
> {
constructor(
c: HTTPClient,
intDecoding: IntDecoding,
private index: number,
name: Uint8Array
) {
super(c, intDecoding);
this.index = index;
// Encode name in base64 format and append the encoding prefix.
const encodedName = Buffer.from(name).toString('base64');
this.query.name = encodeURI(`b64:${encodedName}`);
}

/**
* @returns `/v2/applications/${index}/box`
*/
path() {
return `/v2/applications/${this.index}/box`;
}

// eslint-disable-next-line class-methods-use-this
prepare(body: Record<string, any>): Box {
return Box.from_obj_for_encoding(body);
}
}
61 changes: 61 additions & 0 deletions src/client/v2/algod/getApplicationBoxes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import JSONRequest from '../jsonrequest';
import HTTPClient from '../../client';
import IntDecoding from '../../../types/intDecoding';
import { BoxesResponse } from './models/types';

/**
* Given an application ID, return all the box names associated with the app.
*
* #### Example
* ```typescript
* const index = 60553466;
* const boxesResponse = await algodClient.getApplicationBoxes(index).max(3).do();
* const boxNames = boxesResponse.boxes.map(box => box.name);
* ```
*
* [Response data schema details](https://developer.algorand.org/docs/rest-apis/algod/v2/#get-v2applicationsapplication-idboxes)
* @param index - The application ID to look up.
* @category GET
*/
export default class GetApplicationBoxes extends JSONRequest<
BoxesResponse,
Record<string, any>
> {
constructor(c: HTTPClient, intDecoding: IntDecoding, private index: number) {
super(c, intDecoding);
this.index = index;
this.query.max = 0;
}

/**
* @returns `/v2/applications/${index}/boxes`
*/
path() {
return `/v2/applications/${this.index}/boxes`;
}

/**
* Limit results for pagination.
*
* #### Example
* ```typescript
* const maxResults = 20;
* const boxesResult = await algodClient
* .GetApplicationBoxes(1234)
* .limit(maxResults)
* .do();
* ```
*
* @param limit - maximum number of results to return.
* @category query
*/
max(max: number) {
this.query.max = max;
return this;
}

// eslint-disable-next-line class-methods-use-this
prepare(body: Record<string, any>): BoxesResponse {
return BoxesResponse.from_obj_for_encoding(body);
}
}
Loading

0 comments on commit b780daa

Please sign in to comment.