Skip to content

Commit

Permalink
feat(records): add records api to arns remote cache
Browse files Browse the repository at this point in the history
  • Loading branch information
atticusofsparta committed Feb 26, 2024
1 parent e92da07 commit 1b7f54f
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 27 deletions.
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,24 @@ import { ArIO } from '@ar-io/sdk';

const arIO = new ArIO({});
const address = 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ';
const domain = 'ardrive';
const contractTxIds = [
'I-cxQhfh0Zb9UqQNizC9PiLC41KpUeA9hjiVV02rQRw',
'DGWp8_6c1YywKgCgBFhncMglciQyCdfX1swil4qjNSc',
];
// testnet
const testnetBalance = await arIO.testnet.getBalance({ address });
const testnetGateway = await arIO.testnet.getGateway({ address });
const testnetRecord = await arIO.testnet.getRecord({ domain });
const testnetRecords = await arIO.testnet.getRecords({ contractTxIds });
const allTestnetRecords = await arIO.testnet.getRecords({});

// mainnet
const balance = await arIO.mainnet.getBalance({ address });
const gateway = await arIO.mainnet.getGateway({ address });
const record = await arIO.mainnet.getRecord({ domain });
const records = await arIO.mainnet.getRecords({ contractTxIds });
const allRecords = await arIO.mainnet.getRecords({});
```

## Usage
Expand Down Expand Up @@ -94,12 +106,14 @@ Types are exported from `./lib/types/[node/web]/index.d.ts` and should be automa

The contract that the following methods retrieve data from are determined by the `testnet` or `devnet` clients - see examples above for implementation details.

| Method Name | Description |
| ------------------------- | ----------------------------------------------- |
| `getBalance({ address })` | Retrieves the balance of the specified address. |
| `getBalances()` | Retrieves all balances on the ArIO contract. |
| `getGateway({ address })` | Retrieves the specified gateway by address. |
| `getGateways()` | Retrieves all gateways. |
| Method Name | Description |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `getBalance({ address })` | Retrieves the balance of the specified address. |
| `getBalances()` | Retrieves all balances on the ArIO contract. |
| `getGateway({ address })` | Retrieves the specified gateway by address. |
| `getGateways()` | Retrieves all gateways. |
| `getRecord({ domain })` | Retrieves a specified ArNS record by the domain name. |
| `getRecords({ contractTxIds })` | Retrieves all records with an optional `contractTxIds` filter to only get records associated with specified ANT contracts. |

## Developers

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"dependencies": {
"arweave": "^1.14.4",
"axios": "1.4.0",
"lodash": "^4.17.21",
"warp-contracts": "^1.4.34",
"winston": "^3.11.0"
}
Expand Down
86 changes: 66 additions & 20 deletions src/common/caches/arns-remote-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { chunk } from 'lodash';

import { ARNS_TESTNET_REGISTRY_TX } from '../../constants.js';
import {
ArIOContract,
ArNSNameData,
ArNSStateResponse,
ContractCache,
Gateway,
Expand All @@ -26,6 +29,14 @@ import { NotFound } from '../error.js';
import { AxiosHTTPService } from '../http.js';
import { DefaultLogger } from '../logger.js';

const validateContractTxId = (contractTxId: string) => {
if (!contractTxId) {
throw new Error(
'Contract TxId not set, set one before calling this function.',
);
}
};

export class ArNSRemoteCache implements ContractCache, ArIOContract {
private contractTxId: string;
private logger: DefaultLogger;
Expand Down Expand Up @@ -55,11 +66,8 @@ export class ArNSRemoteCache implements ContractCache, ArIOContract {
}

async getGateway({ address }: { address: string }) {
if (!this.contractTxId) {
throw new Error(
'Contract TxId not set, set one before calling this function.',
);
}
validateContractTxId(this.contractTxId);

this.logger.debug(`Fetching gateway ${address}`);
const gateway = await this.getGateways().then((gateways) => {
if (gateways[address] === undefined) {
Expand All @@ -71,11 +79,8 @@ export class ArNSRemoteCache implements ContractCache, ArIOContract {
}

async getGateways() {
if (!this.contractTxId) {
throw new Error(
'Contract TxId not set, set one before calling this function.',
);
}
validateContractTxId(this.contractTxId);

this.logger.debug(`Fetching gateways`);
const { result } = await this.http.get<
ArNSStateResponse<'result', Record<string, Gateway>>
Expand All @@ -86,11 +91,8 @@ export class ArNSRemoteCache implements ContractCache, ArIOContract {
}

async getBalance({ address }: { address: string }) {
if (!this.contractTxId) {
throw new Error(
'Contract TxId not set, set one before calling this function.',
);
}
validateContractTxId(this.contractTxId);

this.logger.debug(`Fetching balance for ${address}`);
const { result } = await this.http
.get<ArNSStateResponse<'result', number>>({
Expand All @@ -106,11 +108,8 @@ export class ArNSRemoteCache implements ContractCache, ArIOContract {
}

async getBalances() {
if (!this.contractTxId) {
throw new Error(
'Contract TxId not set, set one before calling this function.',
);
}
validateContractTxId(this.contractTxId);

this.logger.debug(`Fetching balances`);
const { result } = await this.http.get<
ArNSStateResponse<'result', Record<string, number>>
Expand All @@ -119,4 +118,51 @@ export class ArNSRemoteCache implements ContractCache, ArIOContract {
});
return result;
}

async getRecord({ domain }: { domain: string }): Promise<ArNSNameData> {
validateContractTxId(this.contractTxId);

this.logger.debug(`Fetching record for ${domain}`);
const { record } = await this.http.get<
ArNSStateResponse<'record', ArNSNameData>
>({
endpoint: `/contract/${this.contractTxId.toString()}/records/${domain}`,
});
return record;
}

async getRecords({
contractTxIds,
}: {
contractTxIds?: string[];
}): Promise<Record<string, ArNSNameData>> {
validateContractTxId(this.contractTxId);

const contractTxIdsSet = new Set(contractTxIds);
const batches = chunk([...contractTxIdsSet]);

this.logger.debug(`Fetching records for ${contractTxIdsSet.size} ANT's`);
const records = await Promise.all(
batches.map((batch: string[]) =>
this.http.get<
ArNSStateResponse<'records', Record<string, ArNSNameData>>
>({
endpoint: `/contract/${this.contractTxId.toString()}/records${new URLSearchParams(batch.map((id) => ['contractTxId', id.toString()])).toString()}`,
}),
),
).then(
(
responses: ArNSStateResponse<'records', Record<string, ArNSNameData>>[],
) => {
const recordsObj = responses.reduce(
(acc: Record<string, ArNSNameData>, response) => {
return { ...acc, ...response.records };
},
{},
);
return recordsObj;
},
);
return records;
}
}
8 changes: 7 additions & 1 deletion src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Gateway } from './contract-state.js';
import { ArNSNameData, Gateway } from './contract-state.js';

export interface ContractCache {
/**
Expand All @@ -28,6 +28,12 @@ export interface ArIOContract {
getGateways(): Promise<Record<WalletAddress, Gateway>>;
getBalance({ address }: { address: WalletAddress }): Promise<number>;
getBalances(): Promise<Record<WalletAddress, number>>;
getRecord({ domain }: { domain: string }): Promise<ArNSNameData>;
getRecords({
contractTxIds,
}: {
contractTxIds?: string[];
}): Promise<Record<string, ArNSNameData>>;
}

/* eslint-disable @typescript-eslint/no-explicit-any */
Expand Down
42 changes: 42 additions & 0 deletions tests/arns-remote-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NotFound } from '../src/common/error.js';
describe('ArNSRemoteCache', () => {
const remoteCacheProvider = new ArNSRemoteCache({});

// gateway tests
it('should be able to fetch gateways', async () => {
const gateways = await remoteCacheProvider.getGateways();
expect(gateways).toBeDefined();
Expand All @@ -18,6 +19,7 @@ describe('ArNSRemoteCache', () => {
expect(error).toBeInstanceOf(NotFound);
});

// balance tests
it('should fetch a balance', async () => {
const balance = await remoteCacheProvider.getBalance({
address: 'some-address',
Expand All @@ -29,4 +31,44 @@ describe('ArNSRemoteCache', () => {
const balances = await remoteCacheProvider.getBalances();
expect(balances).toBeDefined();
});

// records tests
it('should fetch a record', async () => {
const record = await remoteCacheProvider.getRecord({
domain: 'ar-io',
});
expect(record).toBeDefined();
});

it('should throw NotFound error on non existent record', async () => {
const error = await remoteCacheProvider
.getRecord({
domain: 'some-domain',
})
.catch((e) => e);
expect(error).toBeInstanceOf(NotFound);
});

it('should fetch records for list of contractIDs', async () => {
const allRecords = await remoteCacheProvider
.getRecords({})
.then((res) => Object.entries(res).slice(200))
.catch((e) => e); // deliberately attempting to get more than URL params can handle to test batching but limiting to 200 to not strain service

const contractTxIds = allRecords.map(([, record]) => record.contractTxId); // deliberately attempting to get more than URL params can handle to test batching but limiting to 200 to not strain service

const expectedRecords = allRecords
.map(([domain]) => domain)
.sort((a: string, b: string) => a.localeCompare(b));
const records = await remoteCacheProvider.getRecords({
contractTxIds: [...contractTxIds, ...contractTxIds], // mapping twice to test duplicates
});

const actualRecords = Object.keys(records).sort((a: string, b: string) =>
a.localeCompare(b),
);

expect(records).toBeDefined();
expect(actualRecords).toEqual(expectedRecords);
});
});

0 comments on commit 1b7f54f

Please sign in to comment.