diff --git a/.github/ISSUE_TEMPLATE/bug_report_template.yml b/.github/ISSUE_TEMPLATE/bug_report_template.yml index ad0feb4..0c2a261 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_template.yml +++ b/.github/ISSUE_TEMPLATE/bug_report_template.yml @@ -26,6 +26,7 @@ body: label: Version description: What version of the software are you running? options: + - 1.1.0 - 1.0.1 - 1.0.0 validations: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0df8080..80e71d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +# [1.1.0](https://github.com/NEARBuilders/near-social-js/compare/v1.0.1...v1.1.0) (2024-08-19) + + +### Features + +* wallet support for frontend apps and API server methods ([#43](https://github.com/NEARBuilders/near-social-js/issues/43)) ([83bb3a6](https://github.com/NEARBuilders/near-social-js/commit/83bb3a6c4610a28bb4599a5be1ce2278a82ca989)), closes [#35](https://github.com/NEARBuilders/near-social-js/issues/35) [#35](https://github.com/NEARBuilders/near-social-js/issues/35) [#38](https://github.com/NEARBuilders/near-social-js/issues/38) [#38](https://github.com/NEARBuilders/near-social-js/issues/38) [#40](https://github.com/NEARBuilders/near-social-js/issues/40) [#40](https://github.com/NEARBuilders/near-social-js/issues/40) [#33](https://github.com/NEARBuilders/near-social-js/issues/33) [#33](https://github.com/NEARBuilders/near-social-js/issues/33) [#42](https://github.com/NEARBuilders/near-social-js/issues/42) [#42](https://github.com/NEARBuilders/near-social-js/issues/42) + +# [1.1.0-beta.5](https://github.com/NEARBuilders/near-social-js/compare/v1.1.0-beta.4...v1.1.0-beta.5) (2024-08-05) + + +### Features + +* aded transformActions utility and exposed all utils ([#42](https://github.com/NEARBuilders/near-social-js/issues/42)) ([bfc9072](https://github.com/NEARBuilders/near-social-js/commit/bfc90720035d9e686469e95bf25ef0cc17c79c4b)) + +# [1.1.0-beta.4](https://github.com/NEARBuilders/near-social-js/compare/v1.1.0-beta.3...v1.1.0-beta.4) (2024-08-01) + + +### Features + +* adding api server support for read methods ([#33](https://github.com/NEARBuilders/near-social-js/issues/33)) ([dc53b30](https://github.com/NEARBuilders/near-social-js/commit/dc53b30af3ade611a4e8d29316dfc56f77dc8791)) + +# [1.1.0-beta.3](https://github.com/NEARBuilders/near-social-js/compare/v1.1.0-beta.2...v1.1.0-beta.3) (2024-07-30) + + +### Bug Fixes + +* required deposit fix when available storage is higher than needed ([#40](https://github.com/NEARBuilders/near-social-js/issues/40)) ([99f9e8d](https://github.com/NEARBuilders/near-social-js/commit/99f9e8de55d5410ff34f553348d978fe075e6fbf)) + +# [1.1.0-beta.2](https://github.com/NEARBuilders/near-social-js/compare/v1.1.0-beta.1...v1.1.0-beta.2) (2024-07-25) + + +### Features + +* remove near-api-js account object dependency from change functions ([#38](https://github.com/NEARBuilders/near-social-js/issues/38)) ([134e463](https://github.com/NEARBuilders/near-social-js/commit/134e4638341febdaf945fe8412479172421332d3)) + +# [1.1.0-beta.1](https://github.com/NEARBuilders/near-social-js/compare/v1.0.1...v1.1.0-beta.1) (2024-07-24) + + +### Features + +* **wip:** removed signer from viewMethods by dropping NAJ account.ViewFunction ([#35](https://github.com/NEARBuilders/near-social-js/issues/35)) ([a57edc4](https://github.com/NEARBuilders/near-social-js/commit/a57edc42bee279abc1f3925acd130cba29cd239a)) + ## [1.0.1](https://github.com/NEARBuilders/near-social-js/compare/v1.0.0...v1.0.1) (2024-07-05) diff --git a/README.md b/README.md index 1e5c53b..f16df49 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ GitHub Pre-release - GitHub Pre-release Date - Published At + GitHub Pre-release Date - Published At

diff --git a/docs/advanced/fetching-indexed-data.mdx b/docs/advanced/fetching-indexed-data.mdx new file mode 100644 index 0000000..a9fc305 --- /dev/null +++ b/docs/advanced/fetching-indexed-data.mdx @@ -0,0 +1,160 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import TOCInline from '@theme/TOCInline'; + +# Fetching Indexed Data + +## Overview + +The `index` function in the Social SDK allows you to retrieve indexed values based on specified criteria from the Social API server. This function is crucial for efficient lookups of social interactions or custom indexed data, supporting various filtering, ordering, and pagination options. + +:::note + +The `index` function is only available through the API server and does not have an RPC version. + +::: + +## Function Signature + +```typescript +public async index({ + action, + key, + accountId, + order, + limit, + from, +}: IIndexOptions): Promise> +``` + +### Parameters + +- `action`: The index_type from the standard (e.g., 'like' in the path 'index/like'). +- `key`: Can be either a string or an object: + - If string: The inner indexed value from the standard. + - If object: Can include properties like type, path, and blockHeight. +- `accountId` (optional): A string or array of account IDs to filter values. +- `order` (optional): The order of results. Either 'asc' or 'desc'. Default is 'asc'. +- `limit` (optional): The number of values to return. Default is 100. +- `from` (optional): The starting point for fetching results. Defaults to 0 or Max depending on order. + +### Return Value + +A promise that resolves to an array of matched indexed values, ordered by blockHeight. + +## Usage Examples + +### Basic Usage + + + + + ```js + const { Social } = require('@builddao/near-social-js'); + + const social = new Social(); + const result = await social.index({ + action: 'like', + key: 'post-123', + }); + + console.log(result); + ``` + + + + + ```js + var social = new NEARSocialSDK(); + + social.index({ + action: 'like', + key: 'post-123', + }).then((result) => { + console.log(result); + }); + ``` + + + + + ```typescript + import { Social } from '@builddao/near-social-js'; + + const social = new Social(); + const result = await social.index({ + action: 'like', + key: 'post-123', + }); + + console.log(result); + ``` + + + + +### Advanced Usage + +You can use additional options to customize the behavior of the `index` function: + +```typescript +const result = await social.index({ + action: 'follow', + key: 'alice.near', + accountId: ['bob.near', 'charlie.near'], + order: 'desc', + limit: '50', + from: 100, +}); +``` + +This example retrieves the last 50 'follow' actions for 'alice.near', starting from the 100th most recent entry, and only includes actions by 'bob.near' and 'charlie.near'. + +## Use Cases + +The `index` function is particularly useful for: + +1. Fetching all 'like' actions for a specific post: + ```typescript + const likes = await social.index({ + action: 'like', + key: { + type: 'social', + path: 'efiz.near/post/main', + blockHeight: 124692995,//blockHeight of the post + }, + }); + ``` + +2. Retrieving recent 'follow' actions for a user: + ```typescript + const result = await social.index({ + action: 'graph', + key: 'follow', + order: 'desc', + accountId: 'alice.near', + limit: '10', + }); + ``` + +3. Querying custom indexed data based on application-specific schemas: + ```typescript + const customData = await social.index({ + action: 'custom-action', + key: 'app-specific-key', + }); + ``` + +By leveraging the `index` function, you can build efficient and scalable features in your NEAR Social applications, such as activity feeds, trending content algorithms, or custom data aggregations. + +:::tip + +Combine the `index` function with `get` and `keys` for comprehensive data retrieval strategies in your application. + +::: diff --git a/docs/advanced/granting-write-permission.mdx b/docs/advanced/granting-write-permission.mdx index a8b1a4f..fe0dbd7 100644 --- a/docs/advanced/granting-write-permission.mdx +++ b/docs/advanced/granting-write-permission.mdx @@ -32,20 +32,17 @@ Accounts can grant write permission to other accounts for a set of keys. ```js const { Social } = require('@builddao/near-social-js'); - const grantee = await nearConnection.account('alice.near'); - const granter = await nearConnection.account('bob.near'); - const accessKeys = await granter.getAccessKeys(); const social = new Social(); const transaction = await social.grantWritePermission({ - blockHash: accessKeys[0].block_hash, - granteeAccountId: grantee.accountId, + account: { + accountID: 'bob.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, + granteeAccountId: 'alice.near', keys: [ 'alice.near/profile/name', 'alice.near/profile/image/url', ], - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer: granter, }); // ...sign the returned transaction and post to the network @@ -56,41 +53,22 @@ Accounts can grant write permission to other accounts for a set of keys. ```js - var accessKeys; - var grantee; - var granter; - var social; - - nearConnection.account('alice.near') - .then((_granter) => { - granter = _granter; - - return nearConnection.account('bob.near'); - }) - .then((_grantee) => { - grantee = _grantee; - - return granter.getAccessKeys(); - }) - .then((_accessKeys) => { - accessKeys = _accessKeys; - social = new NEARSocialSDK(); - - return social.grantWritePermission({ - blockHash: accessKeys[0].block_hash, - granteeAccountId: grantee.accountId, - keys: [ - 'alice.near/profile/name', - 'alice.near/profile/image/url', - ], - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer: granter, - }); - }) - .then((transaction) => { - // ...sign the returned transaction and post to the network - }); + var social = new NEARSocialSDK(); + + social.grantWritePermission({ + account: { + accountID: 'bob.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, + granteeAccountId: 'alice.near', + keys: [ + 'alice.near/profile/name', + 'alice.near/profile/image/url' + ] + }) + .then((transaction) => { + // ...sign the returned transaction and post to the network + }); ``` @@ -99,20 +77,17 @@ Accounts can grant write permission to other accounts for a set of keys. ```typescript import { Social } from '@builddao/near-social-js'; - const grantee = await nearConnection.account('alice.near'); - const granter = await nearConnection.account('bob.near'); - const accessKeys = await granter.getAccessKeys(); const social = new Social(); const transaction = await social.grantWritePermission({ - blockHash: accessKeys[0].block_hash, - granteeAccountId: grantee.accountId, + account: { + accountID: 'bob.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, + granteeAccountId: 'alice.near', keys: [ 'alice.near/profile/name', 'alice.near/profile/image/url', ], - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer: granter, }); // ...sign the returned transaction and post to the network @@ -124,12 +99,12 @@ Accounts can grant write permission to other accounts for a set of keys. :::caution -If the grantee account ID or the account ID in each key is not a valid account ID then a [`InvalidAccountIdError`](../../api-reference/errors#invalidaccountiderror) is thrown. +If the grantee account ID or the account ID in each key is not a valid account ID then a [`InvalidAccountIdError`](../api-reference/errors#invalidaccountiderror) is thrown. ::: :::caution -If a key does is not owned by the granter, then a [`KeyNotAllowedError`](../../api-reference/errors#keynotallowederror) is thrown. +If a key does is not owned by the granter, then a [`KeyNotAllowedError`](../api-reference/errors#keynotallowederror) is thrown. ::: diff --git a/docs/advanced/index.md b/docs/advanced/index.md index 4d859c3..b56167f 100644 --- a/docs/advanced/index.md +++ b/docs/advanced/index.md @@ -1,6 +1,7 @@ # Advanced * [Reading Data](advanced/reading-data) +* [Fetching Indexed Data](advanced/fetching-indexed-data) * [Storing Data](advanced/storing-data) * [Granting Write Permission](advanced/granting-write-permission) * [Storage Deposit/Withdrawal](advanced/storage-deposit-withdraw) diff --git a/docs/advanced/reading-data.mdx b/docs/advanced/reading-data.mdx index 6c87eb7..d39c75b 100644 --- a/docs/advanced/reading-data.mdx +++ b/docs/advanced/reading-data.mdx @@ -4,14 +4,18 @@ import TOCInline from '@theme/TOCInline'; # Reading Data +The Social SDK provides two main functions for reading data: `get` and `keys`. Both functions now support API server requests for improved performance on mainnet, as well as direct contract calls. + +# Get Function + ## Overview -Reading data is referenced by a set of key paths, where each path corresponds to the object to return. +The get function in the Social SDK allows you to read data referenced by a set of key paths, where each path corresponds to the object to return. This function now supports both direct contract calls and API server requests for improved performance on mainnet. :::note @@ -56,7 +60,6 @@ Getting specific values, like the example from above, you can use a set of keys: 'alice.near/profile/image/url', 'bob.near/profile/name', ], - signer, // an initialized near-api-js account }); console.log(result); @@ -95,7 +98,6 @@ Getting specific values, like the example from above, you can use a set of keys: 'alice.near/profile/image/url', 'bob.near/profile/name', ], - signer, // an initialized near-api-js account }).then((result) => { console.log(result); /* @@ -135,7 +137,6 @@ Getting specific values, like the example from above, you can use a set of keys: 'alice.near/profile/image/url', 'bob.near/profile/name', ], - signer, // an initialized near-api-js account }); console.log(result); @@ -186,7 +187,6 @@ The [`get`](../api-reference/social#getoptions) function also supports wildcard keys: [ 'alice.near/profile/**', ], - signer, // an initialized near-api-js account }); console.log(result); @@ -214,7 +214,6 @@ The [`get`](../api-reference/social#getoptions) function also supports wildcard keys: [ 'alice.near/profile/**', ], - signer, // an initialized near-api-js account }).then((result) => { console.log(result); /* @@ -243,7 +242,6 @@ The [`get`](../api-reference/social#getoptions) function also supports wildcard keys: [ 'alice.near/profile/**' ], - signer, // an initialized near-api-js account }); console.log(result); @@ -263,3 +261,136 @@ The [`get`](../api-reference/social#getoptions) function also supports wildcard + +## Using API Server (Default) +By default, the get function uses the API server for improved performance on mainnet. You don't need to specify any additional parameters for this behavior. +This is only for mainnet. + + +## Using Direct Contract Calls +If you want to make direct contract calls instead of using the API server, you can set useApiServer to false: +```typescript +const result = await social.get({ + keys: ['alice.near/profile/**'], + useApiServer: false, +}); +``` +Note that when using direct contract calls, you can use the withNodeId option, which is not available when using the API server. + + +# Keys Function + +## Overview + +The `keys` function allows you to retrieve a list of keys that match specified criteria. This is useful for querying the data structure without necessarily reading all the associated values. + +## Usage Examples + +### Basic Usage + + + + + ```js + const { Social } = require('@builddao/near-social-js'); + + const social = new Social(); + const result = await social.keys({ + keys: [ + 'alice.near/profile/**', + 'bob.near/post/**', + ], + }); + + console.log(result); + ``` + + + + + ```js + var social = new NEARSocialSDK(); + + social.keys({ + keys: [ + 'alice.near/profile/**', + 'bob.near/post/**', + ], + }).then((result) => { + console.log(result); + }); + ``` + + + + + ```typescript + import { Social } from '@builddao/near-social-js'; + + const social = new Social(); + const result = await social.keys({ + keys: [ + 'alice.near/profile/**', + 'bob.near/post/**', + ], + }); + + console.log(result); + ``` + + + + +### Using API Server (Default) + +By default, the `keys` function uses the API server for improved performance on mainnet. You don't need to specify any additional parameters for this behavior. + +### Using Direct Contract Calls + +If you want to make direct contract calls instead of using the API server, you can set `useApiServer` to `false`: + +```typescript +const result = await social.keys({ + keys: ['alice.near/profile/**'], + useApiServer: false, +}); +``` + +### Additional Options + +You can use additional options to customize the behavior of the `keys` function: + +```typescript +const result = await social.keys({ + keys: ['alice.near/profile/**'], + blockHeight: 12345678, + returnDeleted: true, + returnType: 'History', + valuesOnly: true, +}); +``` + +This example retrieves keys at a specific block height, includes deleted keys, returns historical data, and only includes values in the response. + +:::note + +The `returnType` option with value "History" is only supported when using the API server (`useApiServer: true`). + +::: + +## Use Cases + +The `keys` function is particularly useful for: + +1. Discovering available data structures without fetching all the associated values. +2. Checking for the existence of certain keys or patterns in the data. +3. Retrieving metadata about keys, such as when they were last updated or deleted. +4. Efficiently querying large data sets by first fetching keys and then selectively retrieving values. + +By combining the `get` and `keys` functions, you can build powerful and efficient data retrieval strategies for your NEAR Social applications. diff --git a/docs/advanced/storage-deposit-withdraw.mdx b/docs/advanced/storage-deposit-withdraw.mdx index 791f880..70e70f3 100644 --- a/docs/advanced/storage-deposit-withdraw.mdx +++ b/docs/advanced/storage-deposit-withdraw.mdx @@ -5,8 +5,8 @@ import TOCInline from '@theme/TOCInline'; # Depositing NEAR for Storage ## Overview @@ -16,167 +16,132 @@ When depositing NEAR to the social DB contract, you can cover the storage cost f The signer account MUST have sufficient NEAR balance to cover the deposit. - + defaultValue="javascript-via-package-manager" + values={[ + { label: 'JavaScript (via package manager)', value: 'javascript-via-package-manager' }, + { label: 'JavaScript (via CDN)', value: 'javascript-via-cdn' }, + { label: 'TypeScript', value: 'typescript' }, + ]}> - ```js -const { Social } = require('@builddao/near-social-js'); - -const signer = await nearConnection.account('alice.near'); -const accessKeys = await signer.getAccessKeys(); -const social = new Social(); -const transaction = await social.storageDeposit({ - blockHash: accessKeys[0].block_hash, - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer, - registration_only: true, // set to true if you want to pay only the bare minimum deposit - account_id: 'bob.near', // optional, defaults to the signer account - deposit: '10000000000000000000000', // the amount to deposit in yoctoNEAR -}); -// ...sign the returned transaction and post to the network -``` - - -```js -var accessKeys; -var signer; -var social; - -nearConnection.account('alice.near') - .then((_signer) => { - signer = _signer; - - return signer.getAccessKeys(); - }) - .then((_accessKeys) => { - accessKeys = _accessKeys; - social = new NEARSocialSDK(); - - return social.storageDeposit({ - blockHash: accessKeys[0].block_hash, - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer, + ```js + const { Social } = require('@builddao/near-social-js'); + + const social = new Social(); + const transaction = await social.storageDeposit({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, registration_only: true, // set to true if you want to pay only the bare minimum deposit account_id: 'bob.near', // optional, defaults to the signer account deposit: '10000000000000000000000', // the amount to deposit in yoctoNEAR }); - }) - .then((transaction) => { // ...sign the returned transaction and post to the network - }); -``` + ``` + + + + + ```js + var social = new NEARSocialSDK(); + + social.storageDeposit({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, + account_id: 'bob.near', // optional, defaults to the signer account + deposit: '10000000000000000000000', // the amount to deposit in yoctoNEAR + registration_only: true // set to true if you want to pay only the bare minimum deposit + }) + .then((transaction) => { + // ...sign the returned transaction and post to the network + }); + ``` -```typescript -import { Social } from '@builddao/near-social-js'; - -const signer = await nearConnection.account('alice.near'); -const accessKeys = await signer.getAccessKeys(); -const social = new Social(); -const transaction = await social.storageDeposit({ - blockHash: accessKeys[0].block_hash, - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer, - registration_only: true, // set to true if you want to pay only the bare minimum deposit - account_id: 'bob.near', // optional, defaults to the signer account - deposit: '10000000000000000000000', // the amount to deposit in yoctoNEAR -}); -// ...sign the returned transaction and post to the network -``` + ```typescript + import { Social } from '@builddao/near-social-js'; + + const social = new Social(); + const transaction = await social.storageDeposit({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, + account_id: 'bob.near', // optional, defaults to the signer account + deposit: '10000000000000000000000', // the amount to deposit in yoctoNEAR + registration_only: true, // set to true if you want to pay only the bare minimum deposit + }); + // ...sign the returned transaction and post to the network + ``` # Withdrawing NEAR from Storage - - ## Overview + When withdrawing NEAR from the social DB contract, you can specify the amount to withdraw. If no amount is specified, all available NEAR is withdrawn. The signer account MUST have sufficient NEAR balance available for withdrawal. - - -```js -const { Social } = require('@builddao/near-social-js'); - -const signer = await nearConnection.account('alice.near'); -const accessKeys = await signer.getAccessKeys(); -const social = new Social(); -const transaction = await social.storageWithdraw({ - blockHash: accessKeys[0].block_hash, - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer, - amount: '5000000000000000000000', // the amount to withdraw in yoctoNEAR, optional, defaults to all available balance -}); -// ...sign the returned transaction and post to the network -``` - - -```js -var accessKeys; -var signer; -var social; - -nearConnection.account('alice.near') - .then((_signer) => { - signer = _signer; - - return signer.getAccessKeys(); - }) - .then((_accessKeys) => { - accessKeys = _accessKeys; - social = new NEARSocialSDK(); - - return social.storageWithdraw({ - blockHash: accessKeys[0].block_hash, - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer, + defaultValue="javascript-via-package-manager" + values={[ + { label: 'JavaScript (via package manager)', value: 'javascript-via-package-manager' }, + { label: 'JavaScript (via CDN)', value: 'javascript-via-cdn' }, + { label: 'TypeScript', value: 'typescript' }, + ]}> + + + ```js + const { Social } = require('@builddao/near-social-js'); + + const social = new Social(); + const transaction = await social.storageWithdraw({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, amount: '5000000000000000000000', // the amount to withdraw in yoctoNEAR, optional, defaults to all available balance }); - }) - .then((transaction) => { // ...sign the returned transaction and post to the network - }); -``` + ``` + + + ```js + var social = new NEARSocialSDK(); + + social.storageWithdraw({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, + amount: '5000000000000000000000' // the amount to withdraw in yoctoNEAR, optional, defaults to all available balance + }) + .then((transaction) => { + // ...sign the returned transaction and post to the network + }); + ``` + + -```typescript -import { Social } from '@builddao/near-social-js'; - -const signer = await nearConnection.account('alice.near'); -const accessKeys = await signer.getAccessKeys(); -const social = new Social(); -const transaction = await social.storageWithdraw({ - blockHash: accessKeys[0].block_hash, - nonce: BigInt(accessKeys[0].nonce + 1), // the nonce to be used for the transaction, must be greater than the access key nonce - publicKey: accessKeys[0].public_key, - signer, - amount: '5000000000000000000000', // the amount to withdraw in yoctoNEAR, optional, defaults to all available balance -}); -// ...sign the returned transaction and post to the network -``` + ```typescript + import { Social } from '@builddao/near-social-js'; + + const social = new Social(); + const transaction = await social.storageWithdraw({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, + amount: '5000000000000000000000', // the amount to withdraw in yoctoNEAR, optional, defaults to all available balance + }); + // ...sign the returned transaction and post to the network + ``` diff --git a/docs/advanced/storing-data.mdx b/docs/advanced/storing-data.mdx index 078da87..2361df3 100644 --- a/docs/advanced/storing-data.mdx +++ b/docs/advanced/storing-data.mdx @@ -39,19 +39,19 @@ The signer account automatically has write permissions for their own account. ```js const { Social } = require('@builddao/near-social-js'); - const signer = await nearConnection.account('alice.near'); - const accessKeys = await signer.getAccessKeys(); const social = new Social(); const transaction = await social.set({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, data: { ['alice.near']: { profile: { - name: 'Alice' - } - } + name: 'Alice', + }, + }, }, - publicKey: accessKeys[0].public_key, - signer, }); // ...sign the returned transaction and post to the network @@ -61,35 +61,24 @@ The signer account automatically has write permissions for their own account. ```js - var accessKeys; - var signer; - var social; - - nearConnection.account('alice.near') - .then((_signer) => { - signer = _signer; - - return signer.getAccessKeys(); - }) - .then((_accessKeys) => { - accessKeys = _accessKeys; - social = new NEARSocialSDK(); - - return social.set({ - data: { - ['alice.near']: { - profile: { - name: 'Alice' - } - } - }, - publicKey: accessKeys[0].public_key, - signer, - }); - }) - .then((transaction) => { - // ...sign the returned transaction and post to the network - }); + var social = new NEARSocialSDK(); + + social.set({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, + data: { + ['alice.near']: { + profile: { + name: 'Alice' + } + } + } + }) + .then((transaction) => { + // ...sign the returned transaction and post to the network + }); ``` @@ -98,19 +87,19 @@ The signer account automatically has write permissions for their own account. ```typescript import { Social } from '@builddao/near-social-js'; - const signer = await nearConnection.account('alice.near'); - const accessKeys = await signer.getAccessKeys(); const social = new Social(); const transaction = await social.set({ + account: { + accountID: 'alice.near', + publicKey: 'ed25519:H9k5eiU4xXS3M4z8HzKJSLaZdqGdGwBG49o7orNC4eZW', + }, data: { ['alice.near']: { profile: { - name: 'Alice' - } - } + name: 'Alice', + }, + }, }, - publicKey: accessKeys[0].public_key, - signer, }); // ...sign the returned transaction and post to the network diff --git a/docs/api-reference/errors.md b/docs/api-reference/errors.md index 435abab..dd5b01a 100644 --- a/docs/api-reference/errors.md +++ b/docs/api-reference/errors.md @@ -2,10 +2,11 @@ ## Summary -| Code | Name | Summary | -|------|---------------------------------------------------|--------------------------------| -| 2000 | [`InvalidAccountIdError`](#invalidaccountiderror) | When an account ID is invalid. | -| 3000 | [`KeyNotAllowedError`](#keynotallowederror) | When a key is not allowed. | +| Code | Name | Summary | +|------|---------------------------------------------------|-----------------------------------| +| 2000 | [`InvalidAccountIdError`](#invalidaccountiderror) | When an account ID is invalid. | +| 3000 | [`KeyNotAllowedError`](#keynotallowederror) | When a key is not allowed. | +| 4000 | [`UnknownNetworkError`](#unknownnetworkerror) | When the network ID is not known. | ## `InvalidAccountIdError` @@ -30,3 +31,15 @@ This error is thrown when a key is not allowed, usually when an account has not | code | `number` | 3000 | A canonical code for this error. | | key | `string` | - | The key that is not allowed. | | message | `string` | - | A human readable message. | + +## `UnknownNetworkError` + +This error is thrown when initializing the SDK and the network ID supplied, is not known. + +#### Properties + +| Name | Type | Value | Description | +|-----------|----------|-------|----------------------------------| +| code | `number` | 4000 | A canonical code for this error. | +| message | `string` | - | A human readable message. | +| networkID | `string` | - | The network ID that is invalid. | diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index f453095..2f8363c 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -3,3 +3,4 @@ * [`Social`](api-reference/social) - The main client used to interact with the social contract. * [`Types`](api-reference/types) - Various types used within the SDK. * [`Errors`](api-reference/errors) - Various error responses that can be thrown. +* [`Networks`](api-reference/networks) - The default RPC networks used by their network ID. diff --git a/docs/api-reference/networks.md b/docs/api-reference/networks.md new file mode 100644 index 0000000..2dd65e1 --- /dev/null +++ b/docs/api-reference/networks.md @@ -0,0 +1,10 @@ +# Networks + +Below are the URLs of the default RPC networks that are used, referenced by the network ID. + +| Network ID | RPC URL | +|------------|--------------------------------------------------------------| +| `betanet` | [https://rpc.betanet.near.org](https://rpc.betanet.near.org) | +| `localnet` | [http://localhost:3030](http://localhost:3030) | +| `mainnet` | [https://rpc.mainnet.near.org](https://rpc.mainnet.near.org) | +| `testnet` | [https://rpc.testnet.near.org](https://rpc.testnet.near.org) | diff --git a/docs/api-reference/social.mdx b/docs/api-reference/social.mdx index 8425ecc..23286f8 100644 --- a/docs/api-reference/social.mdx +++ b/docs/api-reference/social.mdx @@ -15,9 +15,9 @@ import TOCInline from '@theme/TOCInline'; #### Parameters -| Name | Type | Required | Default | Description | -|---------|------------------------------------------------|----------|---------|----------------------------------| -| options | [`INewSocialOptions`](types#inewsocialoptions) | no | - | Options that initialize the SDK. | +| Name | Type | Required | Default | Description | +|---------|------------------------------------------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------| +| options | [`INewSocialOptions`](types#inewsocialoptions) | no | - | Options that initialize the SDK such as the social contract account ID and the network ID or custom RPC provider details. | #### Returns @@ -39,9 +39,9 @@ import TOCInline from '@theme/TOCInline'; #### Parameters -| Name | Type | Required | Default | Description | -|---------|------------------------------------|----------|---------|------------------------------------------------------------| -| options | [`IGetOptions`](types#igetoptions) | yes | - | Options that include the signer account and a set of keys. | +| Name | Type | Required | Default | Description | +|---------|------------------------------------|----------|---------|-----------------------------------------------------| +| options | [`IGetOptions`](types#igetoptions) | yes | - | Options that include the set of keys. | #### Returns @@ -49,6 +49,42 @@ import TOCInline from '@theme/TOCInline'; |-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| | `Promise` | A promise that resolves to an object with the top-level properties referencing the keys and the nested properties/objects referencing the corresponding path. | +### `keys(options)` +> Retrieves a list of keys that match specified criteria. +> This function is useful for querying the data structure without necessarily reading all the associated values. +> For example: +> * `alice.near/profile/**` - will match all keys under the profile of account `alice.near`. +> * `*/post/*` - will match all post keys for all accounts. +> * `bob.near/widget/***` - will match all widget keys and their nested keys for account `bob.near`. + +#### Parameters +| Name | Type | Required | Default | Description | +|---------|--------------------------------------|----------|---------|-----------------------------------------------------| +| options | [`IKeysOptions`](types#ikeysoptions) | yes | - | Options that specify the criteria for key retrieval. | + +#### Returns +| Type | Description | +|-------------------|---------------------------------------------------------------------------------------------------------------| +| `Promise` | A promise that resolves to an object containing the matching keys and their metadata, ordered by block height. | + +### `index(options)` +> Retrieves indexed values based on specified criteria from the Social API server. +> This function is crucial for efficient lookups of social interactions or custom indexed data. +> For example: +> * Fetching all 'like' actions for a specific post. +> * Retrieving recent 'follow' actions for a user. +> * Querying custom indexed data based on application-specific schemas. + +#### Parameters +| Name | Type | Required | Default | Description | +|---------|----------------------------------------|----------|---------|-----------------------------------------------------| +| options | [`IIndexOptions`](types#iindexoptions) | yes | - | Options that specify the criteria for index retrieval. | + +#### Returns +| Type | Description | +|-------------------|---------------------------------------------------------------------------------------------------------------| +| `Promise` | A promise that resolves to an array of matched indexed values, ordered by block height. | + ### `getVersion()` > Gets the current version of the social contract. @@ -78,7 +114,6 @@ import TOCInline from '@theme/TOCInline'; ### `isWritePermissionGranted(options)` > Checks if an account, specified in `options.granteeAccountId`, has been granted write access for a key. -> If the signer and the supplied `options.granteeAccountId` match, true will be returned. #### Parameters @@ -112,9 +147,9 @@ Accounts that match the signer's account ID automatically have write permission #### Returns -| Type | Description | -|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| -| [`Promise`](https://near.github.io/near-api-js/classes/near_api_js.transaction.Transaction.html) | A promise that resolves to an unsigned trasnaction that contiains the function call to update the specified data. | +| Type | Description | +|---------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| +| [`Promise`](https://near.github.io/near-api-js/classes/near_api_js.transaction.Transaction.html) | A promise that resolves to an unsigned transaction that contains the function call to update the specified data. | ### `storageDeposit(options)` @@ -130,14 +165,14 @@ The signer account must have sufficient NEAR balance to cover the deposit. #### Parameters -| Name | Type | Required | Default | Description | -|-----------------|--------------------------------------------|----------|---------|-----------------------------------------------------------------------------| -| options | [`IStorageDepositOptions`](types#istorage-deposit-options) | yes | - | Options that include the deposit details, the signer's account and key details. | +| Name | Type | Required | Default | Description | +|---------|------------------------------------------------------------|----------|---------|---------------------------------------------------------------------------------| +| options | [`IStorageDepositOptions`](types#istorage-deposit-options) | yes | - | Options that include the deposit details, the signer's account and key details. | #### Returns -| Type | Description | -|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| +| Type | Description | +|---------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------| | [`Promise`](https://near.github.io/near-api-js/classes/near_api_js.transaction.Transaction.html) | A promise that resolves to an unsigned transaction that contains the function call to deposit the specified amount. | @@ -154,12 +189,12 @@ The signer account must have sufficient NEAR balance available for withdrawal. #### Parameters -| Name | Type | Required | Default | Description | -|---------|--------------------------------------------|----------|---------|-----------------------------------------------------------------------------| +| Name | Type | Required | Default | Description | +|---------|--------------------------------------------------------------|----------|---------|------------------------------------------------------------------------------------| | options | [`IStorageWithdrawOptions`](types#istorage-withdraw-options) | yes | - | Options that include the withdrawal details, the signer's account and key details. | #### Returns -| Type | Description | -|---------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| +| Type | Description | +|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------| | [`Promise`](https://near.github.io/near-api-js/classes/near_api_js.transaction.Transaction.html) | A promise that resolves to an unsigned transaction that contains the function call to withdraw the specified amount. | diff --git a/docs/api-reference/types.mdx b/docs/api-reference/types.mdx index b3f47df..5a5fc4f 100644 --- a/docs/api-reference/types.mdx +++ b/docs/api-reference/types.mdx @@ -7,26 +7,54 @@ import TOCInline from '@theme/TOCInline'; toc={toc} /> +### `IAccount` + +| Name | Type | Required | Default | Description | +|-----------|--------------------------------------------------------------------------------------------------------|----------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| accountID | `string` | yes | - | The account ID of the account used to sign the transaction. | +| publicKey | [`PublicKey`](https://near.github.io/near-api-js/classes/near_api_js.utils.PublicKey.html) \| `string` | yes | - | The public key of the access key that will be used to sign the transaction. The key must have write access for the account. For details on managing keys see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account#access-keys). | + ### `IGetOptions` | Name | Type | Required | Default | Description | |-----------------|------------------------------------------------------------------------------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| | keys | `string[]` | yes | - | A set of key patterns to return. Each key is in the form of a pattern to return data from. For example `alice.near/profile/**` or `alice.near/profile/name`. | -| signer | [`Account`](https://near.github.io/near-api-js/classes/near_api_js.account.Account.html) | yes | - | An account that will read the data. For details on getting the account object see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account). | +| useApiServer | `boolean` | no | `true` | If true, the data will be fetched from api server instead of network rpc node. | | | returnDeleted | `boolean` | no | `false` | If true, will include deleted keys with the value `null`. | | withBlockHeight | `boolean` | no | `false` | If true, for every value and a node will add the block height of the data with the key `:block`. | | withNodeId | `boolean` | no | `false` | If true, for every node will add the node index with the key `:node`. | +### `IKeysOptions` + +| Name | Type | Required | Default | Description | +|---------------|------------|----------|---------|-------------------------------------------------------------------------------------------------------| +| keys | `string[]` | yes | - | A set of key patterns to return. Each key is in the form of a pattern to return data from. | +| blockHeight | `bigint` | no | - | The block height to query from. | +| returnDeleted | `boolean` | no | `false` | If true, will include deleted keys with the value `null`. | +| returnType | `boolean` | no | `false` | If true, will return the type of the value (e.g., 'string', 'object') along with the value. | +| valuesOnly | `boolean` | no | `false` | If true, returns only values without keys. | +| useApiServer | `boolean` | no | `true` | If true, the data will be fetched from the API server instead of the network RPC node. | + +### `IIndexOptions` + +| Name | Type | Required | Default | Description | +|-----------|----------------------------------------------------------------|----------|---------|------------------------------------------------------------------------------------------------------------| +| action | `string` | yes | - | The index_type from the standard (e.g., 'like', 'follow'). | +| key | `string \| { type?: string; path?: string; blockHeight?: number }` | yes | - | The inner indexed value. Can be a string or an object with optional type, path, and blockHeight properties. | +| accountId | `string \| string[]` | no | - | A string or array of account IDs to filter values. | +| order | `'asc' \| 'desc'` | no | `'asc'` | The order of results. Either 'asc' or 'desc'. | +| limit | `number` | no | `100` | The number of values to return. | +| from | `number \| string` | no | `0` | The starting point for fetching results. Can be a number or string, depending on the context. | + ### `IGrantWritePermissionOptions` -| Name | Type | Required | Default | Description | -|------------------|--------------------------------------------------------------------------------------------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| blockHash | `string` | yes | - | The block hash that is returned from [`getAccessKeys()`](https://near.github.io/near-api-js/classes/near_api_js.account.Account.html#getAccessKeys). See the [`AccessKeyView`](https://near.github.io/near-api-js/interfaces/near_api_js.providers_provider.AccessKeyView.html) object for more information. | -| granteeAccountId | `string` | yes | - | The account ID to give write permission to. | -| keys | `string[]` | yes | - | A set of keys to give the grantee write permission. For example `alice.near/profile/**` or `alice.near/profile/name`. | -| nonce | `bigint` | yes | - | The access key's nonce. See the [`AccessKeyView`](https://near.github.io/near-api-js/interfaces/near_api_js.providers_provider.AccessKeyView.html) object for more information. | -| publicKey | [`PublicKey`](https://near.github.io/near-api-js/classes/near_api_js.utils.PublicKey.html) \| `string` | yes | - | The signer's public key for the access key that will be used to write to the contract. The key must have write access for the account. For details on managing keys see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account#access-keys). | -| signer | [`Account`](https://near.github.io/near-api-js/classes/near_api_js.account.Account.html) | yes | - | An account that will read the data. For details on getting the account object see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account). | +| Name | Type | Required | Default | Description | +|------------------|-------------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| account | [`IAccount`](#iaccount) | yes | - | An object that contains the account ID and the public key that will be used to sign the transaction. | +| blockHash | `string` | yes | - | The block hash that is returned from [`getAccessKeys()`](https://near.github.io/near-api-js/classes/near_api_js.account.Account.html#getAccessKeys). See the [`AccessKeyView`](https://near.github.io/near-api-js/interfaces/near_api_js.providers_provider.AccessKeyView.html) object for more information. | +| granteeAccountId | `string` | yes | - | The account ID to give write permission to. | +| keys | `string[]` | yes | - | A set of keys to give the grantee write permission. For example `alice.near/profile/**` or `alice.near/profile/name`. | +| nonce | `bigint` | yes | - | The access key's nonce. See the [`AccessKeyView`](https://near.github.io/near-api-js/interfaces/near_api_js.providers_provider.AccessKeyView.html) object for more information. | ### `IIsWritePermissionGrantedOptions` @@ -34,41 +62,47 @@ import TOCInline from '@theme/TOCInline'; |------------------|------------------------------------------------------------------------------------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------| | granteeAccountId | `string` | yes | - | The account ID to check if it has write permissions for a key. | | key | `string` | yes | - | The key to check if the grantee has write permission. | -| signer | [`Account`](https://near.github.io/near-api-js/classes/near_api_js.account.Account.html) | yes | - | An account that will read the data. For details on getting the account object see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account). | ### `INewSocialOptions` -| Name | Type | Required | Default | Description | -|------------|----------|----------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| -| contractId | `string` | no | `social.near` | The account ID of the social contract. This defaults to the mainnet social contract account ID: [`social.near`](https://nearblocks.io/address/social.near). | +| Name | Type | Required | Default | Description | +|------------|-------------------------------------------|----------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| contractId | `string` | no | `social.near` | The account ID of the social contract. This defaults to the mainnet social contract account ID: [`social.near`](https://nearblocks.io/address/social.near). | +| network | `string` \| [`IRPCOptions`](#irpcoptions) | no | `mainnet` | This can be a network ID string (`betanet`, `localnet`, `mainnet` or `testnet`) which will use a public RPC provider, or you can use a custom RPC URL with an API key. | + +### `IRPCOptions` + +| Name | Type | Required | Default | Description | +|--------|----------|----------|---------|----------------------------------------------------------------------------------| +| apiKey | `string` | no | - | An API key that is used in the `X-Api-Key` header for all RPC provider requests. | +| url | `string` | yes | - | The RPC provider URL. | ### `ISetOptions` -| Name | Type | Required | Default | Description | -|---------------------|--------------------------------------------------------------------------------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| blockHash | `string` | no | - | A current block hash (within 24 hours). Defaults to the latest block hash. | -| data | `object` | yes | - | Each top-level property must be the account ID. The nested objects/properties will be written to the contract, however, if any nested properties are `null` values, this signals a deletion of that key. | -| nonce | `bigint` | no | - | The access key's nonce. See the [`AccessKeyView`](https://near.github.io/near-api-js/interfaces/near_api_js.providers_provider.AccessKeyView.html) object for more information. Defaults to the current access key nonce incremented by 1. | -| publicKey | [`PublicKey`](https://near.github.io/near-api-js/classes/near_api_js.utils.PublicKey.html) \| `string` | yes | - | The signer's public key for the access key that will be used to write to the contract. The key must have write access for the account. For details on managing keys see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account#access-keys). | -| refundUnusedDeposit | `boolean` | no | `false` | If true, will refund any left over deposit to the signer's account. | -| signer | [`Account`](https://near.github.io/near-api-js/classes/near_api_js.account.Account.html) | yes | - | An account that will read the data. For details on getting the account object see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account). | +| Name | Type | Required | Default | Description | +|---------------------|-------------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| account | [`IAccount`](#iaccount) | yes | - | An object that contains the account ID and the public key that will be used to sign the transaction. | +| blockHash | `string` | no | - | A current block hash (within 24 hours). Defaults to the latest block hash. | +| data | `object` | yes | - | Each top-level property must be the account ID. The nested objects/properties will be written to the contract, however, if any nested properties are `null` values, this signals a deletion of that key. | +| nonce | `bigint` | no | - | The access key's nonce. See the [`AccessKeyView`](https://near.github.io/near-api-js/interfaces/near_api_js.providers_provider.AccessKeyView.html) object for more information. Defaults to the current access key nonce incremented by 1. | +| refundUnusedDeposit | `boolean` | no | `false` | If true, will refund any left over deposit to the signer's account. | ### `IStorageDepositOptions` -| Name | Type | Required | Default | Description | -|---------------------|--------------------------------------------------------------------------------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| account_id | `string` | no | - | The account ID for which to deposit storage. Defaults to the signer account if not provided. -| registration_only| `boolean` | no | `false` | If true, deposits the minimum amount required for account registration without additional storage.| -| deposit | `string` | yes | - | The amount of NEAR to deposit for storage, in yoctoNEAR. | | blockHash | `string` | no | - | A current block hash (within 24 hours). Defaults to the latest block hash. | -| nonce | `bigint` | no | - | The access key's nonce. Defaults to the current access key nonce incremented by 1. | -| publicKey | [`PublicKey`](https://near.github.io/near-api-js/classes/near_api_js.utils.PublicKey.html) \| `string` | yes | - | The signer's public key for the access key that will be used to write to the contract. | -| signer | [`Account`](https://near.github.io/near-api-js/classes/near_api_js.account.Account.html) | yes | - | An account that will read the data. For details on getting the account object see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account). | + +| Name | Type | Required | Default | Description | +|-------------------|--------------------------------|----------|---------|------------------------------------------------------------------------------------------------------| +| account | [`IAccount`](#iaccount) | yes | - | An object that contains the account ID and the public key that will be used to sign the transaction. | +| account_id | `string` | no | - | The account ID for which to deposit storage. Defaults to the signer account if not provided. | +| registration_only | `boolean` | no | `false` | If true, deposits the minimum amount required for account registration without additional storage. | +| deposit | `string` | yes | - | The amount of NEAR to deposit for storage, in yoctoNEAR. | +| blockHash | `string` | no | - | A current block hash (within 24 hours). Defaults to the latest block hash. | +| nonce | `bigint` | no | - | The access key's nonce. Defaults to the current access key nonce incremented by 1. | ### `IStorageWithdrawOptions` -| Name | Type | Required | Default | Description | -|------------------|--------------------------------------------------------------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------------| -| amount | `string` | no | - | The amount of NEAR to withdraw, in yoctoNEAR. If not specified, withdraws all available NEAR.| -| blockHash | `string` | no | - | A current block hash (within 24 hours). Defaults to the latest block hash. | -| nonce | `bigint` | no | - | The access key's nonce. Defaults to the current access key nonce incremented by 1. | -| publicKey | [`PublicKey`](https://near.github.io/near-api-js/classes/near_api_js.utils.PublicKey.html) \| `string` | yes | - | The signer's public key for the access key that will be used to write to the contract. | -| signer | [`Account`](https://near.github.io/near-api-js/classes/near_api_js.account.Account.html) | yes | - | An account that will read the data. For details on getting the account object see the NEAR API [docs](https://docs.near.org/tools/near-api-js/account). | +| Name | Type | Required | Default | Description | +|-----------|-------------------------|----------|---------|------------------------------------------------------------------------------------------------------| +| account | [`IAccount`](#iaccount) | yes | - | An object that contains the account ID and the public key that will be used to sign the transaction. | +| amount | `string` | no | - | The amount of NEAR to withdraw, in yoctoNEAR. If not specified, withdraws all available NEAR. | +| blockHash | `string` | no | - | A current block hash (within 24 hours). Defaults to the latest block hash. | +| nonce | `bigint` | no | - | The access key's nonce. Defaults to the current access key nonce incremented by 1. | diff --git a/docs/scripts/sidebars.js b/docs/scripts/sidebars.js index 8b68478..7d7f665 100644 --- a/docs/scripts/sidebars.js +++ b/docs/scripts/sidebars.js @@ -16,6 +16,7 @@ const sidebars = { { items: [ 'advanced/reading-data', + 'advanced/fetching-indexed-data', 'advanced/storing-data', 'advanced/granting-write-permission', 'advanced/storage-deposit-withdraw', @@ -32,6 +33,7 @@ const sidebars = { 'api-reference/social', 'api-reference/types', 'api-reference/errors', + 'api-reference/networks', ], label: 'API Reference', link: { diff --git a/docs/usage/getting-started.mdx b/docs/usage/getting-started.mdx index da2884a..4e63540 100644 --- a/docs/usage/getting-started.mdx +++ b/docs/usage/getting-started.mdx @@ -72,13 +72,13 @@ To initialize the SDK, simply instantiate the [`Social`](../api-reference/social :::note -By default, the Social SDK is initialized for mainnet's social contract at account ID: [`social.near`](https://nearblocks.io/address/social.near). +By default, the Social SDK is initialized for the mainnet social contract ([`social.near`](https://nearblocks.io/address/social.near)) and the mainnet RPC provider (https://rpc.mainnet.near.org). ::: :::note -See [`INewSocialOptions`](../../api-reference/types#inewsocialoptions) for more information on what options are supported. +See [`INewSocialOptions`](../api-reference/types#inewsocialoptions) for more information on what options are supported. ::: @@ -86,6 +86,50 @@ See [`INewSocialOptions`](../../api-reference/types#inewsocialoptions) for more You can initialize the SDK with a different social contract account by specifying it in the options: + + + + ```js + const { Social } = require('@builddao/near-social-js'); + + const social = new Social({ + contractId: 'mysocialcontract.near', + }); + ``` + + + + + ```js + var social = new NEARSocialSDK({ + contractId: 'mysocialcontract.near', + }); + ``` + + + + + ```typescript + import { Social } from '@builddao/near-social-js'; + + const social = new Social({ + contractId: 'mysocialcontract.near', + }); + ``` + + + + +## Initialization with a different network ID + +You can use a specific network by initializing the SDK with a known network ID. See [networks](../api-reference/networks) on the supported networks and the RPC URLs they point to. + ```typescript - import { Social } from '@builddao/near-social-js'; + import { NetworkIDEnum, Social } from '@builddao/near-social-js'; const social = new Social({ contractId: 'v1.social08.testnet', + network: NetworkIDEnum.Testnet, + }); + ``` + + + + +:::caution + +If using the network ID, make sure you provide the correct social contract for that network. + +::: + +:::note + +An [`UnknownNetworkError`](../api-reference/errors#unkownnetworkerror) will be thrown if the supplied network ID is not known. + +::: + +## Initialization with a custom RPC provider + +If you want to use your own RPC provider, you can initialize the SDK by specifying it in the options: + + + + + ```js + const { Social } = require('@builddao/near-social-js'); + + const social = new Social({ + network: { + apiKey: 'some key', + url: 'https://custom-rpc.near.org', + }, + }); + ``` + + + + + ```js + var social = new NEARSocialSDK({ + network: { + apiKey: 'some key', + url: 'https://custom-rpc.near.org' + } + }); + ``` + + + + + ```typescript + import { Social } from '@builddao/near-social-js'; + + const social = new Social({ + network: { + apiKey: 'some key', + url: 'https://custom-rpc.near.org', + }, }); ``` diff --git a/jest.config.ts b/jest.config.ts index 3491434..7290674 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -24,6 +24,7 @@ const config: Config = { ], }, verbose: true, + testPathIgnorePatterns: ['/.*Count.*'], // here }; export default config; diff --git a/package.json b/package.json index 534908e..1341812 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@builddao/near-social-js", - "version": "1.0.1", + "version": "1.1.0", "description": "A JavaScript SDK for interacting with the social contract (social.near) with helper functions for typical social features.", "homepage": "https://nearbuilders.github.io/near-social-js/", "main": "dist/index.js", diff --git a/src/constants/Config.ts b/src/constants/Config.ts new file mode 100644 index 0000000..4b3d527 --- /dev/null +++ b/src/constants/Config.ts @@ -0,0 +1,7 @@ +export const networkRPCs = { + testnet: 'https://rpc.testnet.near.org', + mainnet: 'https://rpc.mainnet.near.org', + betanet: 'https://rpc.betanet.near.org', + localnet: 'http://localhost:3030', + // Add more networks if needed +}; diff --git a/src/constants/index.ts b/src/constants/index.ts index bcdfd91..2788c3c 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1 +1,2 @@ export * from './Fees'; +export * from './Config'; diff --git a/src/controllers/Social.get.test.ts b/src/controllers/Social.get.test.ts index ed2ba58..03a629c 100644 --- a/src/controllers/Social.get.test.ts +++ b/src/controllers/Social.get.test.ts @@ -1,35 +1,113 @@ -import type { Account } from 'near-api-js'; - +import { Account, providers, transactions, utils } from 'near-api-js'; // credentials import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; // controllers import Social from './Social'; +// enums +import { NetworkIDEnum } from '@app/enums'; + // helpers import createEphemeralAccount from '@test/helpers/createEphemeralAccount'; +import { randomBytes } from 'node:crypto'; + +// utils +import convertNEARToYoctoNEAR from '@app/utils/convertNEARToYoctoNEAR'; + +async function sendTransaction( + transaction: transactions.Transaction, + signer: Account +): Promise { + const [_, signedTransaction] = await transactions.signTransaction( + transaction, + signer.connection.signer, + signer.accountId, + signer.connection.networkId + ); + const { status } = + await signer.connection.provider.sendTransaction(signedTransaction); + const failure = (status as providers.FinalExecutionStatus)?.Failure || null; + + if (failure) { + throw new Error(JSON.stringify(failure)); + } +} describe(`${Social.name}#get`, () => { - let signer: Account; + const client = new Social({ + contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, + }); + it('should return an empty object when the contract does not know the account', async () => { + // act + const result = await client.get({ + keys: ['unknown.test.near/profile/name'], + useApiServer: false, + }); + + // assert + expect(result).toEqual({}); + }); - beforeEach(async () => { - const result = await createEphemeralAccount(); + it('should return the expected value for a key as set in the test.', async () => { + let keyPair: utils.KeyPairEd25519; + let signer: Account; + const result = await createEphemeralAccount(convertNEARToYoctoNEAR('100')); + keyPair = result.keyPair; signer = result.account; - }); - it('should return an empty object when the contract does not know the account', async () => { + let name = randomBytes(16).toString('hex'); // arrange + const data: Record> = { + [signer.accountId]: { + profile: { + name: name, + }, + }, + }; + let getResult: Record; + let transaction: transactions.Transaction; + + // act + transaction = await client.set({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, + data, + }); + + // assert + await sendTransaction(transaction, signer); + + getResult = await client.get({ + keys: [`${signer.accountId}/profile/name`], + useApiServer: false, + }); + + expect(getResult).toEqual(data); + }); + + it('should return the object from mainnet api server', async () => { + //api server is only available for mainnet so we can't test this with the local test. + //Hence for now, I have written a mainnet test. const client = new Social({ contractId: socialContractAccountId, + network: NetworkIDEnum.Mainnet, }); // act const result = await client.get({ - keys: ['unknown.test.near/profile/name'], - signer, + keys: ['jass.near/profile/name/'], }); - // assert - expect(result).toEqual({}); + expect(result).toEqual({ + 'jass.near': { + profile: { + name: 'Jaswinder Singh', + }, + }, + }); }); }); diff --git a/src/controllers/Social.getAccount.test.ts b/src/controllers/Social.getAccount.test.ts new file mode 100644 index 0000000..6a7b726 --- /dev/null +++ b/src/controllers/Social.getAccount.test.ts @@ -0,0 +1,84 @@ +import { Account, providers, transactions } from 'near-api-js'; +import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; +import Social from './Social'; +import { NetworkIDEnum } from '@app/enums'; +import createEphemeralAccount from '@test/helpers/createEphemeralAccount'; +import { randomBytes } from 'node:crypto'; +import convertNEARToYoctoNEAR from '@app/utils/convertNEARToYoctoNEAR'; + +async function sendTransaction( + transaction: transactions.Transaction, + signer: Account +): Promise { + const [_, signedTransaction] = await transactions.signTransaction( + transaction, + signer.connection.signer, + signer.accountId, + signer.connection.networkId + ); + const { status } = + await signer.connection.provider.sendTransaction(signedTransaction); + const failure = (status as providers.FinalExecutionStatus)?.Failure || null; + + if (failure) { + throw new Error(JSON.stringify(failure)); + } +} + +describe(`${Social.name}#getAccount`, () => { + const client = new Social({ + contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, + }); + + it('should return null when the account does not exist', async () => { + // act + const result = await client.getAccount({ + accountId: 'nonexistent.test.near', + }); + + // assert + expect(result).toBeNull(); + }); + + it('should return the account data when the account exists', async () => { + // arrange + const { account: signer, keyPair } = await createEphemeralAccount( + convertNEARToYoctoNEAR('100') + ); + const name = randomBytes(16).toString('hex'); + const data = { + [signer.accountId]: { + profile: { + name: name, + }, + }, + }; + + // act + const setTransaction = await client.set({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, + data, + }); + await sendTransaction(setTransaction, signer); + + const result = await client.getAccount({ + accountId: signer.accountId, + }); + + expect(result).toEqual( + expect.objectContaining({ + permissions: [], + shared_storage: null, + storage_balance: '20000000000000000000000', + used_bytes: 956, + }) + ); + // Separately check that node_id exists and is a number + expect(result).toHaveProperty('node_id'); + expect(typeof result.node_id).toBe('number'); + }); +}); diff --git a/src/controllers/Social.getAccountCount.test.ts b/src/controllers/Social.getAccountCount.test.ts new file mode 100644 index 0000000..56becbe --- /dev/null +++ b/src/controllers/Social.getAccountCount.test.ts @@ -0,0 +1,68 @@ +import { Account, providers, transactions } from 'near-api-js'; +import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; +import Social from './Social'; +import { NetworkIDEnum } from '@app/enums'; +import createEphemeralAccount from '@test/helpers/createEphemeralAccount'; +import { randomBytes } from 'node:crypto'; +import convertNEARToYoctoNEAR from '@app/utils/convertNEARToYoctoNEAR'; + +async function sendTransaction( + transaction: transactions.Transaction, + signer: Account +): Promise { + const [_, signedTransaction] = await transactions.signTransaction( + transaction, + signer.connection.signer, + signer.accountId, + signer.connection.networkId + ); + const { status } = + await signer.connection.provider.sendTransaction(signedTransaction); + const failure = (status as providers.FinalExecutionStatus)?.Failure || null; + + if (failure) { + throw new Error(JSON.stringify(failure)); + } +} + +//to-do - Need fix for parallel runs +describe(`${Social.name}#getAccountCount`, () => { + const client = new Social({ + contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, + }); + + it('should return the correct account count after adding accounts', async () => { + // arrange + const initialCount = await client.getAccountCount(); + const numAccountsToAdd = 2; + + // act + for (let i = 0; i < numAccountsToAdd; i++) { + const { account: signer, keyPair } = await createEphemeralAccount( + convertNEARToYoctoNEAR('100') + ); + const name = randomBytes(16).toString('hex'); + const data = { + [signer.accountId]: { + profile: { + name: name, + }, + }, + }; + const setTransaction = await client.set({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, + data, + }); + await sendTransaction(setTransaction, signer); + } + + const finalCount = await client.getAccountCount(); + + // assert + expect(finalCount).toBe(initialCount + numAccountsToAdd); + }); +}); diff --git a/src/controllers/Social.getAccounts.test.ts b/src/controllers/Social.getAccounts.test.ts new file mode 100644 index 0000000..e653b98 --- /dev/null +++ b/src/controllers/Social.getAccounts.test.ts @@ -0,0 +1,73 @@ +import { Account, providers, transactions } from 'near-api-js'; +import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; +import Social from './Social'; +import { NetworkIDEnum } from '@app/enums'; +import createEphemeralAccount from '@test/helpers/createEphemeralAccount'; +import { randomBytes } from 'node:crypto'; +import convertNEARToYoctoNEAR from '@app/utils/convertNEARToYoctoNEAR'; + +async function sendTransaction( + transaction: transactions.Transaction, + signer: Account +): Promise { + const [_, signedTransaction] = await transactions.signTransaction( + transaction, + signer.connection.signer, + signer.accountId, + signer.connection.networkId + ); + const { status } = + await signer.connection.provider.sendTransaction(signedTransaction); + const failure = (status as providers.FinalExecutionStatus)?.Failure || null; + + if (failure) { + throw new Error(JSON.stringify(failure)); + } +} + +describe(`${Social.name}#getAccounts`, () => { + const client = new Social({ + contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, + }); + + it('should return accounts with pagination', async () => { + // arrange + await Promise.all( + Array(3) + .fill(null) + .map(async () => { + const { account: signer, keyPair } = await createEphemeralAccount( + convertNEARToYoctoNEAR('100') + ); + const name = randomBytes(16).toString('hex'); + const data = { + [signer.accountId]: { + profile: { + name: name, + }, + }, + }; + const setTransaction = await client.set({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, + data, + }); + await sendTransaction(setTransaction, signer); + return { accountId: signer.accountId, name }; + }) + ); + + // act + const result1 = await client.getAccounts({ limit: 2 }); + //console.log(result1); + const result2 = await client.getAccounts({ fromIndex: 2, limit: 1 }); + + // assert + expect(Object.keys(result1)).toHaveLength(2); + expect(Object.keys(result2)).toHaveLength(1); + //@to-do better asserts + }); +}); diff --git a/src/controllers/Social.getVersion.test.ts b/src/controllers/Social.getVersion.test.ts index b359857..60ebbeb 100644 --- a/src/controllers/Social.getVersion.test.ts +++ b/src/controllers/Social.getVersion.test.ts @@ -1,30 +1,21 @@ -import type { Account } from 'near-api-js'; - // credentials import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; // controllers import Social from './Social'; -// helpers -import createEphemeralAccount from '@test/helpers/createEphemeralAccount'; +// enums +import { NetworkIDEnum } from '@app/enums'; describe(`${Social.name}#getVersion`, () => { - let signer: Account; - - beforeEach(async () => { - const result = await createEphemeralAccount(); - - signer = result.account; - }); - it('should return the version of the social contract', async () => { // arrange const client = new Social({ contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, }); // act - const version = await client.getVersion({ signer }); + const version = await client.getVersion(); // assert expect(version).toMatchSnapshot(); diff --git a/src/controllers/Social.grantWritePermission.test.ts b/src/controllers/Social.grantWritePermission.test.ts index cda0e5a..00f4466 100644 --- a/src/controllers/Social.grantWritePermission.test.ts +++ b/src/controllers/Social.grantWritePermission.test.ts @@ -8,7 +8,7 @@ import { account_id as socialContractAccountId } from '@test/credentials/localne import Social from './Social'; // enums -import { ErrorCodeEnum } from '@app/enums'; +import { ErrorCodeEnum, NetworkIDEnum } from '@app/enums'; // errors import { InvalidAccountIdError, KeyNotAllowedError } from '@app/errors'; @@ -47,6 +47,7 @@ describe(`${Social.name}#grantWritePermission`, () => { client = new Social({ contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, }); key = `${granterAccount.accountId}/profile/name`; granterKeyResponse = await accountAccessKey( @@ -66,8 +67,10 @@ describe(`${Social.name}#grantWritePermission`, () => { }, }, nonce: BigInt(granterNonce), - publicKey: granterKeyPair.publicKey, - signer: granterAccount, + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, }); await signAndSendTransaction({ @@ -83,12 +86,14 @@ describe(`${Social.name}#grantWritePermission`, () => { // act try { await client.grantWritePermission({ + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, blockHash: granterKeyResponse.block_hash, granteeAccountId: invalidGranteeAccountId, keys: [key], nonce: BigInt(granterNonce + 1), - publicKey: granterKeyPair.publicKey, - signer: granterAccount, }); } catch (error) { // assert @@ -108,12 +113,14 @@ describe(`${Social.name}#grantWritePermission`, () => { // act try { await client.grantWritePermission({ + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, blockHash: granterKeyResponse.block_hash, granteeAccountId: granteeAccount.accountId, keys: [`${invalidKeyAccountId}/profile/name`], nonce: BigInt(granterNonce + 1), - publicKey: granterKeyPair.publicKey, - signer: granterAccount, }); } catch (error) { // assert @@ -133,12 +140,14 @@ describe(`${Social.name}#grantWritePermission`, () => { // act try { await client.grantWritePermission({ + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, blockHash: granterKeyResponse.block_hash, granteeAccountId: granteeAccount.accountId, keys: [key], nonce: BigInt(granterNonce + 1), - publicKey: granterKeyPair.publicKey, - signer: granterAccount, }); } catch (error) { // assert @@ -156,12 +165,14 @@ describe(`${Social.name}#grantWritePermission`, () => { // act transaction = await client.grantWritePermission({ + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, blockHash: granterKeyResponse.block_hash, granteeAccountId: granteeAccount.accountId, keys: [key], nonce: BigInt(granterNonce + 1), - publicKey: granterKeyPair.publicKey, - signer: granterAccount, }); await signAndSendTransaction({ @@ -173,7 +184,6 @@ describe(`${Social.name}#grantWritePermission`, () => { result = await client.isWritePermissionGranted({ granteeAccountId: granteeAccount.accountId, key, - signer: granteeAccount, }); expect(result).toBe(true); @@ -186,12 +196,14 @@ describe(`${Social.name}#grantWritePermission`, () => { // act transaction = await client.grantWritePermission({ + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, blockHash: granterKeyResponse.block_hash, - granteePublicKey: granteeKeyPair.getPublicKey(), + granteePublicKey: granteeKeyPair.publicKey, keys: [key], nonce: BigInt(granterNonce + 1), - publicKey: granterKeyPair.getPublicKey(), - signer: granterAccount, }); await signAndSendTransaction({ @@ -201,9 +213,8 @@ describe(`${Social.name}#grantWritePermission`, () => { // assert result = await client.isWritePermissionGranted({ - granteePublicKey: granteeKeyPair.getPublicKey(), + granteePublicKey: granteeKeyPair.publicKey, key, - signer: granteeAccount, }); expect(result).toBe(true); diff --git a/src/controllers/Social.index.test.ts b/src/controllers/Social.index.test.ts new file mode 100644 index 0000000..8a962f5 --- /dev/null +++ b/src/controllers/Social.index.test.ts @@ -0,0 +1,29 @@ +// credentials +import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; + +// controllers +import Social from './Social'; + +// enums +import { NetworkIDEnum } from '@app/enums'; + +describe(`${Social.name}#index`, () => { + it('should return the object from mainnet api server', async () => { + //api server is only available for mainnet so we can't test this with the local test. + //Hence for now, I have written a mainnet test. + const client = new Social({ + contractId: socialContractAccountId, + network: NetworkIDEnum.Mainnet, + }); + // act + const result = await client.index({ + action: 'post', + key: 'main', + limit: '1', + }); + + expect(result).toEqual([ + { accountId: 'mob.near', blockHeight: 81101335, value: { type: 'md' } }, + ]); + }); +}); diff --git a/src/controllers/Social.isWritePermissionGranted.test.ts b/src/controllers/Social.isWritePermissionGranted.test.ts index b56facf..0520c78 100644 --- a/src/controllers/Social.isWritePermissionGranted.test.ts +++ b/src/controllers/Social.isWritePermissionGranted.test.ts @@ -8,7 +8,7 @@ import { account_id as socialContractAccountId } from '@test/credentials/localne import Social from './Social'; // enums -import { ErrorCodeEnum } from '@app/enums'; +import { ErrorCodeEnum, NetworkIDEnum } from '@app/enums'; // errors import { InvalidAccountIdError } from '@app/errors'; @@ -49,6 +49,7 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { client = new Social({ contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, }); key = `${granterAccount.accountId}/profile/name`; granterKeyResponse = await accountAccessKey( @@ -59,6 +60,10 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { // set the granter transaction = await client.set({ + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, blockHash: granterKeyResponse.block_hash, data: { [granterAccount.accountId]: { @@ -68,8 +73,6 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { }, }, nonce: BigInt(granterNonce), - publicKey: granterKeyPair.publicKey, - signer: granterAccount, }); await signAndSendTransaction({ @@ -87,7 +90,6 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { await client.isWritePermissionGranted({ granteeAccountId: invalidGranteeAccountId, key, - signer: granterAccount, }); } catch (error) { // assert @@ -106,7 +108,6 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { const result = await client.isWritePermissionGranted({ granteeAccountId: granterAccount.accountId, key, - signer: granterAccount, }); // assert @@ -119,7 +120,6 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { const result = await client.isWritePermissionGranted({ granteeAccountId: granteeAccount.accountId, key, - signer: granterAccount, }); // assert @@ -129,10 +129,12 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { it('should return true if the grantee has been given permission (using account id)', async () => { // arrange const transaction = await client.grantWritePermission({ - granteeAccountId: granteeAccount.accountId, + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, keys: [key], - publicKey: granterKeyPair.getPublicKey(), - signer: granterAccount, + granteeAccountId: granteeAccount.accountId, }); await signAndSendTransaction({ @@ -144,7 +146,6 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { const result = await client.isWritePermissionGranted({ granteeAccountId: granteeAccount.accountId, key, - signer: granterAccount, }); // assert @@ -154,10 +155,12 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { it('should return true if the grantee has been given permission (using public key)', async () => { // arrange const transaction = await client.grantWritePermission({ - granteePublicKey: granteeKeyPair.getPublicKey(), + account: { + accountID: granterAccount.accountId, + publicKey: granterKeyPair.publicKey, + }, + granteePublicKey: granteeKeyPair.publicKey, keys: [key], - publicKey: granterKeyPair.getPublicKey(), - signer: granterAccount, }); await signAndSendTransaction({ @@ -167,9 +170,8 @@ describe(`${Social.name}#isWritePermissionGranted`, () => { // act const result = await client.isWritePermissionGranted({ - granteePublicKey: granteeKeyPair.getPublicKey(), + granteePublicKey: granteeKeyPair.publicKey, key, - signer: granterAccount, }); // assert diff --git a/src/controllers/Social.keys.test.ts b/src/controllers/Social.keys.test.ts new file mode 100644 index 0000000..372f6ef --- /dev/null +++ b/src/controllers/Social.keys.test.ts @@ -0,0 +1,105 @@ +import { Account, providers, transactions, utils } from 'near-api-js'; +// credentials +import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; + +// controllers +import Social from './Social'; + +// enums +import { NetworkIDEnum } from '@app/enums'; + +// helpers +import createEphemeralAccount from '@test/helpers/createEphemeralAccount'; +import { randomBytes } from 'node:crypto'; + +// utils +import convertNEARToYoctoNEAR from '@app/utils/convertNEARToYoctoNEAR'; + +async function sendTransaction( + transaction: transactions.Transaction, + signer: Account +): Promise { + const [_, signedTransaction] = await transactions.signTransaction( + transaction, + signer.connection.signer, + signer.accountId, + signer.connection.networkId + ); + const { status } = + await signer.connection.provider.sendTransaction(signedTransaction); + const failure = (status as providers.FinalExecutionStatus)?.Failure || null; + + if (failure) { + throw new Error(JSON.stringify(failure)); + } +} + +describe(`${Social.name}#keys`, () => { + const client = new Social({ + contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, + }); + + it('should return the expected value for a key as set in the test.', async () => { + let keyPair: utils.KeyPairEd25519; + let signer: Account; + const result = await createEphemeralAccount(convertNEARToYoctoNEAR('100')); + + keyPair = result.keyPair; + signer = result.account; + + let name = randomBytes(16).toString('hex'); + // arrange + const data: Record> = { + [signer.accountId]: { + profile: { + name: name, + }, + }, + }; + let transaction: transactions.Transaction; + + // act + transaction = await client.set({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, + data, + }); + + // assert + await sendTransaction(transaction, signer); + + let keysResult: Record; + keysResult = await client.keys({ + keys: [`${signer.accountId}/profile/name`], + useApiServer: false, + }); + + expect(keysResult).toEqual({ + [signer.accountId]: { profile: { name: true } }, + }); + }); + + it('should return the object from mainnet api server', async () => { + //api server is only available for mainnet so we can't test this with the local test. + //Hence for now, I have written a mainnet test. + const client = new Social({ + contractId: socialContractAccountId, + network: NetworkIDEnum.Mainnet, + }); + // act + const result = await client.keys({ + keys: ['jass.near/profile/name/'], + }); + + expect(result).toEqual({ + 'jass.near': { + profile: { + name: true, + }, + }, + }); + }); +}); diff --git a/src/controllers/Social.set.test.ts b/src/controllers/Social.set.test.ts index cfdbb28..b5ca9e8 100644 --- a/src/controllers/Social.set.test.ts +++ b/src/controllers/Social.set.test.ts @@ -1,17 +1,17 @@ import { Account, providers, transactions, utils } from 'near-api-js'; import { randomBytes } from 'node:crypto'; -// credentials -import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; - // constants import { MINIMUM_STORAGE_IN_BYTES } from '@app/constants'; // controllers import Social from './Social'; +// credentials +import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; + // enums -import { ErrorCodeEnum } from '@app/enums'; +import { ErrorCodeEnum, NetworkIDEnum } from '@app/enums'; // errors import { KeyNotAllowedError } from '@app/errors'; @@ -45,6 +45,7 @@ async function sendTransaction( } describe(`${Social.name}#set`, () => { + let client: Social; let keyPair: utils.KeyPairEd25519; let signer: Account; let signerAccessKeyResponse: IAccessKeyResponse; @@ -53,6 +54,10 @@ describe(`${Social.name}#set`, () => { beforeEach(async () => { const result = await createEphemeralAccount(convertNEARToYoctoNEAR('100')); + client = new Social({ + contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, + }); keyPair = result.keyPair; signer = result.account; signerAccessKeyResponse = await accountAccessKey(signer, keyPair.publicKey); @@ -61,13 +66,13 @@ describe(`${Social.name}#set`, () => { it('should throw an error if the public key does not have write permission', async () => { // arrange - const client = new Social({ - contractId: socialContractAccountId, - }); - try { // act await client.set({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, blockHash: signerAccessKeyResponse.block_hash, data: { ['iamnotthesigner.test.near']: { @@ -77,8 +82,6 @@ describe(`${Social.name}#set`, () => { }, }, nonce: BigInt(signerNonce + 1), - publicKey: keyPair.publicKey, - signer, }); } catch (error) { // assert @@ -94,9 +97,6 @@ describe(`${Social.name}#set`, () => { it('should add some arbitrary data', async () => { // arrange - const client = new Social({ - contractId: socialContractAccountId, - }); const data: Record> = { [signer.accountId]: { profile: { @@ -109,9 +109,11 @@ describe(`${Social.name}#set`, () => { // act transaction = await client.set({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, data, - publicKey: keyPair.publicKey, - signer, }); // assert @@ -119,7 +121,7 @@ describe(`${Social.name}#set`, () => { result = await client.get({ keys: [`${signer.accountId}/profile/name`], - signer, + useApiServer: false, }); expect(result).toEqual(data); @@ -127,9 +129,6 @@ describe(`${Social.name}#set`, () => { it('should add data that exceeds the minimum storage amount', async () => { // arrange - const client = new Social({ - contractId: socialContractAccountId, - }); const data: Record> = { [signer.accountId]: { profile: { @@ -144,9 +143,11 @@ describe(`${Social.name}#set`, () => { // act transaction = await client.set({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, data, - publicKey: keyPair.publicKey, - signer, }); // assert @@ -154,7 +155,7 @@ describe(`${Social.name}#set`, () => { result = await client.get({ keys: [`${signer.accountId}/profile/name`], - signer, + useApiServer: false, }); expect(result).toEqual(data); diff --git a/src/controllers/Social.storageDeposit.test.ts b/src/controllers/Social.storageDeposit.test.ts index 2eae83a..1f11949 100644 --- a/src/controllers/Social.storageDeposit.test.ts +++ b/src/controllers/Social.storageDeposit.test.ts @@ -1,12 +1,13 @@ import { Account, providers, transactions, utils } from 'near-api-js'; -//import { randomBytes } from 'node:crypto'; -import { ViewMethodEnum } from '@app/enums'; + +// controllers +import Social from './Social'; // credentials import { account_id as socialContractAccountId } from '@test/credentials/localnet/social.test.near.json'; -// controllers -import Social from './Social'; +// enums +import { NetworkIDEnum, ViewMethodEnum } from '@app/enums'; // helpers import accountAccessKey, { @@ -31,6 +32,7 @@ describe(`${Social.name}#storageDeposit`, () => { // arrange const client = new Social({ contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, }); let result: Record; let transaction: transactions.Transaction; @@ -41,10 +43,12 @@ describe(`${Social.name}#storageDeposit`, () => { let deposit = '2000000000000000000000000'; //testing optional blockhash and nonce too transaction = await client.storageDeposit({ - publicKey: keyPair.publicKey, - signer, accountId, deposit, + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, }); // assert @@ -90,9 +94,11 @@ describe(`${Social.name}#storageDeposit`, () => { transaction = await client.storageDeposit({ blockHash: signerAccessKeyResponse.block_hash, nonce: BigInt(signerAccessKeyResponse.nonce + 1), - publicKey: keyPair.publicKey, - signer, deposit, + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, }); // assert @@ -139,10 +145,12 @@ describe(`${Social.name}#storageDeposit`, () => { transaction = await client.storageDeposit({ blockHash: signerAccessKeyResponse.block_hash, nonce: BigInt(signerAccessKeyResponse.nonce + 1), - publicKey: keyPair.publicKey, - signer, accountId, deposit, + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, }); // assert @@ -170,7 +178,6 @@ describe(`${Social.name}#storageDeposit`, () => { contractId: socialContractAccountId, methodName: ViewMethodEnum.StorageBalanceOf, }); - console.log(result); expect(result.total).toEqual(deposit); }); }); diff --git a/src/controllers/Social.storageWithdraw.test.ts b/src/controllers/Social.storageWithdraw.test.ts index 0f1f514..f7a0de0 100644 --- a/src/controllers/Social.storageWithdraw.test.ts +++ b/src/controllers/Social.storageWithdraw.test.ts @@ -1,5 +1,4 @@ import { Account, providers, transactions, utils } from 'near-api-js'; -//import { randomBytes } from 'node:crypto'; import { ViewMethodEnum } from '@app/enums'; // credentials @@ -8,6 +7,9 @@ import { account_id as socialContractAccountId } from '@test/credentials/localne // controllers import Social from './Social'; +// enums +import { NetworkIDEnum } from '@app/enums'; + // helpers import accountAccessKey, { IAccessKeyResponse, @@ -31,6 +33,7 @@ describe(`${Social.name}#storageWithdraw`, () => { // arrange const client = new Social({ contractId: socialContractAccountId, + network: NetworkIDEnum.Localnet, }); let resultBefore: Record; let resultAfter: Record; @@ -45,10 +48,12 @@ describe(`${Social.name}#storageWithdraw`, () => { //2N deposit let deposit = '2000000000000000000000000'; transaction = await client.storageDeposit({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, blockHash: signerAccessKeyResponse.block_hash, nonce: BigInt(signerAccessKeyResponse.nonce + 1), - publicKey: keyPair.publicKey, - signer, accountId, deposit, }); @@ -85,11 +90,13 @@ describe(`${Social.name}#storageWithdraw`, () => { //1N withdraw let withdraw_amount = '1000000000000000000000000'; transaction = await client.storageWithdraw({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, + amount: withdraw_amount, blockHash: signerAccessKeyResponse.block_hash, nonce: BigInt(signerAccessKeyResponse.nonce + 1 + 1), - publicKey: keyPair.publicKey, - signer, - amount: withdraw_amount, }); // assert @@ -145,10 +152,12 @@ describe(`${Social.name}#storageWithdraw`, () => { //2N deposit let deposit = '2000000000000000000000000'; transaction = await client.storageDeposit({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, blockHash: signerAccessKeyResponse.block_hash, nonce: BigInt(signerAccessKeyResponse.nonce + 1), - publicKey: keyPair.publicKey, - signer, accountId, deposit, }); @@ -184,10 +193,12 @@ describe(`${Social.name}#storageWithdraw`, () => { //No withdrawal amount specified transaction = await client.storageWithdraw({ + account: { + accountID: signer.accountId, + publicKey: keyPair.publicKey, + }, blockHash: signerAccessKeyResponse.block_hash, nonce: BigInt(signerAccessKeyResponse.nonce + 1 + 1), - publicKey: keyPair.publicKey, - signer, }); // assert diff --git a/src/controllers/Social.ts b/src/controllers/Social.ts index 117d390..83582b0 100644 --- a/src/controllers/Social.ts +++ b/src/controllers/Social.ts @@ -1,14 +1,13 @@ import type { AccessKeyView } from '@near-js/types'; import BigNumber from 'bignumber.js'; -import { - type Account, - type Connection, - transactions, - utils, -} from 'near-api-js'; +import { providers, transactions, utils } from 'near-api-js'; // constants -import { GAS_FEE_IN_ATOMIC_UNITS, ONE_YOCTO } from '@app/constants'; +import { + GAS_FEE_IN_ATOMIC_UNITS, + networkRPCs, + ONE_YOCTO, +} from '@app/constants'; // enums import { ChangeMethodEnum, ViewMethodEnum } from '@app/enums'; @@ -18,40 +17,105 @@ import { AccountNotFoundError, InvalidAccountIdError, KeyNotAllowedError, + UnknownNetworkError, } from '@app/errors'; // types import type { IGetOptions, - IGetVersionOptions, IGrantWritePermissionWithAccountIdOptions, IGrantWritePermissionWithPublicKeyOptions, IIsWritePermissionGrantedWithAccountIdOptions, IIsWritePermissionGrantedWithPublicKeyOptions, INewSocialOptions, + IRPCOptions, ISetOptions, IStorageDepositOptions, IStorageWithdrawOptions, ISocialDBContractGetArgs, + ISocialDBContractGetAccountsArgs, + ISocialDBContractGetAccountArgs, + ISocialApiServerGetArgs, ISocialDBContractGrantWritePermissionArgs, ISocialDBContractSetArgs, ISocialDBContractStorageBalance, - IStorageBalanceOfOptions, ISocialDBContractIsWritePermissionGrantedArgs, ISocialDBContractStorageWithdrawArgs, ISocialDBContractStorageDepositArgs, + IKeysOptions, + ISocialApiServerKeysArgs, + ISocialDBContractKeysArgs, + IAccount, + IIndexOptions, + ISocialApiServerIndexArgs, + IGetAccountsOptions, + IGetAccountOptions, } from '@app/types'; // utils import calculateRequiredDeposit from '@app/utils/calculateRequiredDeposit'; import parseKeysFromData from '@app/utils/parseKeysFromData'; +import rpcURLFromNetworkID from '@app/utils/rpcURLFromNetworkID'; import validateAccountId from '@app/utils/validateAccountId'; +import viewAccessKeyList from '@app/utils/rpcQueries/viewAccessKeyList'; +import viewFunction from '@app/utils/rpcQueries/viewFunction'; export default class Social { - private contractId: string; + // private variables + private readonly _contractId: string; + private readonly _provider: providers.JsonRpcProvider; + private readonly _apiServer?: string; constructor(options?: INewSocialOptions) { - this.contractId = options?.contractId || 'social.near'; + this._contractId = options?.contractId || 'social.near'; + this._provider = Social._initializeProvider(options?.network); + this._apiServer = options?.apiServer || 'https://api.near.social'; + } + + /** + * private static methods + */ + + /** + * Initializes the provider with the supplied network options. If the network options are empty, the default mainnet + * is used. + * @param {string | IRPCOptions} networkIDOrRPCOptions - [optional] a network ID or the RPC options to initialize a + * provider. + * @returns {providers.JsonRpcProvider} an initialized provider to query the network with. + * @throws {UnknownNetworkError} if a network ID is supplied, but is not known. + * @private + * @static + */ + private static _initializeProvider( + networkIDOrRPCOptions?: string | IRPCOptions + ): providers.JsonRpcProvider { + let url: string | null; + + // if there is no network id/rpc details, default to mainnet + if (!networkIDOrRPCOptions) { + return new providers.JsonRpcProvider({ url: networkRPCs.mainnet }); + } + + // if there is a network id, attempt to get the rpc url + if (typeof networkIDOrRPCOptions === 'string') { + url = rpcURLFromNetworkID(networkIDOrRPCOptions); + + if (!url) { + throw new UnknownNetworkError(networkIDOrRPCOptions); + } + + return new providers.JsonRpcProvider({ url }); + } + + // otherwise, use the rpc details + return new providers.JsonRpcProvider({ + url: networkIDOrRPCOptions.url, + ...(networkIDOrRPCOptions.apiKey && { + headers: { + ['X-Api-Key']: networkIDOrRPCOptions.apiKey, + }, + }), + }); } /** @@ -60,17 +124,19 @@ export default class Social { /** * Gets the access key view. - * @param {Account} account - an initialized account. - * @param {string | utils.PublicKey} publicKey - the public key of the access key to query. + * @param {IAccount} account - the account ID and public key of the account. * @returns {Promise} a promise that resolves to the access key view or null if the access key * for the given public key does not exist. * @private */ - private async _accessKeyView( - account: Account, - publicKey: string | utils.PublicKey - ): Promise { - const accessKeys = await account.getAccessKeys(); + private async _accessKeyView({ + accountID, + publicKey, + }: IAccount): Promise { + const accessKeys = await viewAccessKeyList({ + accountID, + provider: this._provider, + }); return ( accessKeys.find((value) => value.public_key === publicKey.toString()) @@ -80,27 +146,47 @@ export default class Social { /** * Queries the node to get the latest block hash. - * @param {Connection} connection - an initialized NEAR connection. * @returns {Promise} a promise that resolves to the latest block hash. The hash will be a base58 encoded string. * @private */ - private async _latestBlockHash(connection: Connection): Promise { - const { sync_info } = await connection.provider.status(); + private async _latestBlockHash(): Promise { + const { sync_info } = await this._provider.status(); return sync_info.latest_block_hash; } - private async _storageBalanceOf({ - accountId, - signer, - }: IStorageBalanceOfOptions): Promise { - return await signer.viewFunction({ + private async _storageBalanceOf( + accountID: string + ): Promise { + const result = await viewFunction({ args: { - account_id: accountId, + account_id: accountID, }, - contractId: this.contractId, - methodName: ViewMethodEnum.StorageBalanceOf, + contractId: this._contractId, + method: ViewMethodEnum.StorageBalanceOf, + provider: this._provider, }); + + if (this._isStorageBalance(result)) { + return result; + } else if (result === null) { + return null; + } else { + throw new Error('Unexpected response format from storage_balance_of'); + } + } + + private _isStorageBalance( + data: unknown + ): data is ISocialDBContractStorageBalance { + return ( + typeof data === 'object' && + data !== null && + 'total' in data && + 'available' in data && + typeof (data as ISocialDBContractStorageBalance).total === 'string' && + typeof (data as ISocialDBContractStorageBalance).available === 'string' + ); } private _uniqueAccountIdsFromKeys(keys: string[]): string[] { @@ -119,20 +205,41 @@ export default class Social { /** * Reads the data for given set of keys. - * @param {IGetOptions} options - the signer and a set of keys to read. + * @param {IGetOptions} options - the set of keys to read and other options. * @returns {Promise>} a promise that resolves to the given data. + * @public */ public async get({ - signer, keys, + blockHeight, returnDeleted, withBlockHeight, withNodeId, + withTimestamp, + useApiServer = true, }: IGetOptions): Promise> { - return await signer.viewFunction({ - contractId: this.contractId, - methodName: ViewMethodEnum.Get, - args: { + if (useApiServer) { + return await ( + await fetch(this._apiServer + '/get', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + keys, + blockHeight, + ...((returnDeleted || withBlockHeight || withTimestamp) && { + options: { + with_block_height: withBlockHeight, + return_deleted: returnDeleted, + with_timestamp: withTimestamp, + }, + }), + } as ISocialApiServerGetArgs), + }) + ).json(); + } else { + const args: ISocialDBContractGetArgs = { keys, ...((returnDeleted || withBlockHeight || withNodeId) && { options: { @@ -141,19 +248,212 @@ export default class Social { return_deleted: returnDeleted, }, }), - } as ISocialDBContractGetArgs, - }); + }; + + return (await viewFunction({ + args, + contractId: this._contractId, + method: ViewMethodEnum.Get, + provider: this._provider, + })) as Record; + } + } + + /** + * Retrieves a list of keys that match the specified path pattern. + * This method is useful for querying data structure without reading actual values. + * @param {IKeysOptions} options - The options for querying keys. + * @param {string[]} options.keys - The set of key patterns to match. + * @param {number} [options.blockHeight] - The block height to query from (optional). + * @param {boolean} [options.returnDeleted] - Whether to include deleted keys in the result (optional). + * @param {string} [options.returnType] - Specifies the type of data to return (optional). + * @param {boolean} [options.valuesOnly] - If true, returns only values without keys (optional). + * @param {boolean} [options.useApiServer=true] - Whether to use the API server or view function using RPC (default: true). + * @returns {Promise>} A promise that resolves to the matching keys and their metadata. + */ + public async keys({ + keys, + blockHeight, + returnDeleted, + returnType, + valuesOnly, + useApiServer = true, + }: IKeysOptions): Promise> { + if (useApiServer) { + return await ( + await fetch(this._apiServer + '/keys', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + keys, + blockHeight, + ...((returnDeleted || returnType || valuesOnly) && { + options: { + return_deleted: returnDeleted, + return_type: returnType, //Server supports additional "History" type. + values_only: valuesOnly, + }, + }), + } as ISocialApiServerKeysArgs), + }) + ).json(); + } else { + const args: ISocialDBContractKeysArgs = { + keys, + ...((returnDeleted || returnType || valuesOnly) && { + options: { + return_deleted: returnDeleted, + return_type: returnType, + values_only: valuesOnly, + }, + }), + }; + + return (await viewFunction({ + args, + contractId: this._contractId, + method: ViewMethodEnum.Keys, + provider: this._provider, + })) as Record; + } + } + + /** + * Retrieves indexed values based on specified criteria from the Social API server. + * This function allows querying of indexed data, which can be used + * for efficient lookups of social interactions or custom indexed data. It supports + * filtering by action type (e.g., likes, follows), specific keys, and optionally by + * account IDs. The results can be ordered and paginated for flexible data retrieval. + * + * Use cases include: + * - Fetching all 'like' actions for a specific post + * - Retrieving recent 'follow' actions for a user + * - Querying custom indexed data based on application-specific schemas + * + * @param {IIndexOptions} options - The options for querying indexed values. + * @param {string} options.action - The index_type from the standard (e.g., 'like' in the path 'index/like'). + * @param {string} options.key - The inner indexed value from the standard. + * @param {string|string[]} [options.accountId] - Optional. A string or array of account IDs to filter values. + * @param {'asc'|'desc'} [options.order='asc'] - Optional. The order of results. Either 'asc' or 'desc'. + * @param {number} [options.limit=100] - Optional. The number of values to return. + * @param {number} [options.from] - Optional. The starting point for fetching results. Defaults to 0 or Max depending on order. + * @returns {Promise>} A promise that resolves to an array of matched indexed values, ordered by blockHeight. + */ + public async index({ + action, + key, + accountId, + order, + limit, + from, + }: IIndexOptions): Promise> { + return await ( + await fetch(this._apiServer + '/index', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + action, + key, + ...((accountId || order || limit || from) && { + options: { + accountId: accountId, + order: order, + limit: limit, + from: from, + }, + }), + } as ISocialApiServerIndexArgs), + }) + ).json(); } /** * Gets the current version of the social contract. * @returns {Promise} a promise that resolves to the current version of the contract. + * @public */ - public async getVersion({ signer }: IGetVersionOptions): Promise { - return await signer.viewFunction({ - contractId: this.contractId, - methodName: ViewMethodEnum.GetVersion, + public async getVersion(): Promise { + const version = await viewFunction({ + contractId: this._contractId, + method: ViewMethodEnum.GetVersion, + provider: this._provider, }); + + if (typeof version !== 'string') { + throw new Error( + `Unexpected response format from get_version: ${JSON.stringify(version)}` + ); + } + return version; + } + + /** + * Retrieves account information based on the provided options. + * @param {IGetAccountOptions} options - The options for retrieving accounts. + * @param {string} [accountId] - AccountId for which you want to fetch Near Social Account + * @returns {Promise>} A promise that resolves to the retrieved account data. + * @public + */ + public async getAccount({ + accountId, + }: IGetAccountOptions): Promise> { + const args: ISocialDBContractGetAccountArgs = { account_id: accountId }; + + return (await viewFunction({ + args, + contractId: this._contractId, + method: ViewMethodEnum.GetAccount, + provider: this._provider, + })) as Record; + } + + /** + * Retrieves account information based on the provided options. + * @param {IGetAccountsOptions} options - The options for retrieving accounts. + * @param {number} [options.fromIndex] - The starting index for pagination (optional). + * @param {number} [options.limit] - The maximum number of accounts to retrieve (optional). + * @returns {Promise>} A promise that resolves to the retrieved account data. + * @public + */ + public async getAccounts({ + fromIndex, + limit, + }: IGetAccountsOptions): Promise> { + const args: ISocialDBContractGetAccountsArgs = { + ...(fromIndex !== undefined && { from_index: fromIndex }), + ...(limit !== undefined && { limit }), + }; + + return (await viewFunction({ + args, + contractId: this._contractId, + method: ViewMethodEnum.GetAccounts, + provider: this._provider, + })) as Record; + } + + /** + * Gets the current Account count on the social contract. + * @returns {Promise} a promise that resolves to the current number of accounts. + * @public + */ + public async getAccountCount(): Promise { + const accountCount = await viewFunction({ + contractId: this._contractId, + method: ViewMethodEnum.GetAccountCount, + provider: this._provider, + }); + + if (typeof accountCount !== 'number') { + throw new Error( + `Unexpected response format from get_account_count: ${JSON.stringify(accountCount)}` + ); + } + return accountCount; } /** @@ -164,13 +464,14 @@ export default class Social { * and sent to the network. * @throws {InvalidAccountIdError} if the grantee account ID or the account ID specified in the keys is invalid. * @throws {KeyNotAllowedError} if account IDs specified in the keys does not match the signer (granter) account ID. + * @public */ public async grantWritePermission( options: | IGrantWritePermissionWithAccountIdOptions | IGrantWritePermissionWithPublicKeyOptions ): Promise { - const { blockHash, keys, nonce, publicKey, signer } = options; + const { account, blockHash, keys, nonce } = options; let accessKeyView: AccessKeyView | null; let _blockHash: string | null = blockHash || null; let _nonce: bigint | null = nonce || null; @@ -196,25 +497,25 @@ export default class Social { } // if the key does not belong to the signer (granter) it cannot give grant permission - if (accountId !== signer.accountId) { + if (accountId !== account.accountID) { throw new KeyNotAllowedError( value, - `key "${value}" does not belong to granter "${signer.accountId}"` + `key "${value}" does not belong to granter "${account.accountID}"` ); } }); if (!_blockHash) { - _blockHash = await this._latestBlockHash(signer.connection); + _blockHash = await this._latestBlockHash(); } if (!_nonce) { - accessKeyView = await this._accessKeyView(signer, publicKey); + accessKeyView = await this._accessKeyView(account); if (!accessKeyView) { throw new AccountNotFoundError( - signer.accountId, - `failed to get nonce for access key for "${signer.accountId}" with public key "${publicKey.toString()}"` + account.accountID, + `failed to get nonce for access key for "${account.accountID}" with public key "${account.publicKey.toString()}"` ); } @@ -222,9 +523,9 @@ export default class Social { } return transactions.createTransaction( - signer.accountId, - utils.PublicKey.fromString(publicKey.toString()), - this.contractId, + account.accountID, + utils.PublicKey.fromString(account.publicKey.toString()), + this._contractId, _nonce, [ transactions.functionCall( @@ -259,13 +560,14 @@ export default class Social { * @returns {Promise} a promise that resolves to true, if the grantee account ID has write access for the * given key, or false. * @throws {InvalidAccountIdError} if the grantee account ID is not a valid account ID. + * @public */ public async isWritePermissionGranted( options: | IIsWritePermissionGrantedWithAccountIdOptions | IIsWritePermissionGrantedWithPublicKeyOptions ): Promise { - const { key, signer } = options; + const { key } = options; if ( (options as IIsWritePermissionGrantedWithAccountIdOptions) @@ -294,9 +596,7 @@ export default class Social { } } - return await signer.viewFunction({ - contractId: this.contractId, - methodName: ViewMethodEnum.IsWritePermissionGranted, + const result = await viewFunction({ args: { key, ...((options as IIsWritePermissionGrantedWithAccountIdOptions) @@ -312,7 +612,18 @@ export default class Social { ).granteePublicKey.toString(), }), } as ISocialDBContractIsWritePermissionGrantedArgs, + contractId: this._contractId, + method: ViewMethodEnum.IsWritePermissionGranted, + provider: this._provider, }); + + if (typeof result !== 'boolean') { + throw new Error( + `Unexpected response format from isWritePermissionGranted: ${JSON.stringify(result)}` + ); + } + + return result; } /** @@ -321,33 +632,33 @@ export default class Social { * @param {ISetOptions} options - the necessary options to set some data. * @returns {Promise} a promise that resolves to a transaction that is ready to be signed * and sent to the network. + * @public */ public async set({ + account, blockHash, data, nonce, - publicKey, refundUnusedDeposit, - signer, }: ISetOptions): Promise { const keys = parseKeysFromData(data); - let _blockHash: string | null = blockHash || null; - let _nonce: bigint | null = nonce || null; + let _blockHash = blockHash || null; + let _nonce = nonce || null; let accessKeyView: AccessKeyView | null; let deposit: BigNumber = new BigNumber('1'); let storageBalance: ISocialDBContractStorageBalance | null; if (!_blockHash) { - _blockHash = await this._latestBlockHash(signer.connection); + _blockHash = await this._latestBlockHash(); } if (!_nonce) { - accessKeyView = await this._accessKeyView(signer, publicKey); + accessKeyView = await this._accessKeyView(account); if (!accessKeyView) { throw new AccountNotFoundError( - signer.accountId, - `failed to get nonce for access key for "${signer.accountId}" with public key "${publicKey.toString()}"` + account.accountID, + `failed to get nonce for access key for "${account.accountID}" with public key "${account.publicKey.toString()}"` ); } @@ -357,11 +668,10 @@ export default class Social { // for each key, check if the signer has been granted write permission for the key for (let i = 0; i < keys.length; i++) { if ( - (keys[i].split('/')[0] || '') !== signer.accountId && + (keys[i].split('/')[0] || '') !== account.accountID && !(await this.isWritePermissionGranted({ - granteePublicKey: publicKey, + granteePublicKey: account.publicKey, key: keys[i], - signer, })) ) { throw new KeyNotAllowedError( @@ -374,13 +684,10 @@ export default class Social { // if the signer is updating their own data, calculate storage deposit if ( this._uniqueAccountIdsFromKeys(keys).find( - (value) => value === signer.accountId + (value) => value === account.accountID ) ) { - storageBalance = await this._storageBalanceOf({ - accountId: signer.accountId, - signer, - }); + storageBalance = await this._storageBalanceOf(account.accountID); deposit = calculateRequiredDeposit({ data, @@ -389,9 +696,9 @@ export default class Social { } return transactions.createTransaction( - signer.accountId, - utils.PublicKey.fromString(publicKey.toString()), - this.contractId, + account.accountID, + utils.PublicKey.fromString(account.publicKey.toString()), + this._contractId, _nonce, [ transactions.functionCall( @@ -418,12 +725,12 @@ export default class Social { * @param {IStorageDepositOptions} options - the necessary options to deposit NEAR for covering storage for the account_id or the signer. * @returns {Promise} a promise that resolves to a transaction that is ready to be signed * and sent to the network. + * @public */ public async storageDeposit({ + account, blockHash, nonce, - publicKey, - signer, registrationOnly, accountId, deposit, @@ -448,16 +755,16 @@ export default class Social { let accessKeyView: AccessKeyView | null; if (!_blockHash) { - _blockHash = await this._latestBlockHash(signer.connection); + _blockHash = await this._latestBlockHash(); } if (!_nonce) { - accessKeyView = await this._accessKeyView(signer, publicKey); + accessKeyView = await this._accessKeyView(account); if (!accessKeyView) { throw new AccountNotFoundError( - signer.accountId, - `failed to get nonce for access key for "${signer.accountId}" with public key "${publicKey.toString()}"` + account.accountID, + `failed to get nonce for access key for "${account.accountID}" with public key "${account.publicKey.toString()}"` ); } @@ -465,9 +772,9 @@ export default class Social { } return transactions.createTransaction( - signer.accountId, - utils.PublicKey.fromString(publicKey.toString()), - this.contractId, + account.accountID, + utils.PublicKey.fromString(account.publicKey.toString()), + this._contractId, _nonce, actions, utils.serialize.base_decode(_blockHash) @@ -479,13 +786,13 @@ export default class Social { * @param {IStorageWithdrawOptions} options - define the amount to be withdrawn. * @returns {Promise} a promise that resolves to a transaction that is ready to be signed * and sent to the network. + * @public */ public async storageWithdraw({ + account, blockHash, amount, nonce, - publicKey, - signer, }: IStorageWithdrawOptions): Promise { const actions: transactions.Action[] = []; @@ -506,16 +813,16 @@ export default class Social { let accessKeyView: AccessKeyView | null; if (!_blockHash) { - _blockHash = await this._latestBlockHash(signer.connection); + _blockHash = await this._latestBlockHash(); } if (!_nonce) { - accessKeyView = await this._accessKeyView(signer, publicKey); + accessKeyView = await this._accessKeyView(account); if (!accessKeyView) { throw new AccountNotFoundError( - signer.accountId, - `failed to get nonce for access key for "${signer.accountId}" with public key "${publicKey.toString()}"` + account.accountID, + `failed to get nonce for access key for "${account.accountID}" with public key "${account.publicKey.toString()}"` ); } @@ -523,19 +830,12 @@ export default class Social { } return transactions.createTransaction( - signer.accountId, - utils.PublicKey.fromString(publicKey.toString()), - this.contractId, + account.accountID, + utils.PublicKey.fromString(account.publicKey.toString()), + this._contractId, _nonce, actions, utils.serialize.base_decode(_blockHash) ); } - /** - * Sets the new social contract ID. - * @param {string} contractId - the account of the new social contract ID. - */ - public setContractId(contractId: string): void { - this.contractId = contractId; - } } diff --git a/src/enums/ErrorCodeEnum.ts b/src/enums/ErrorCodeEnum.ts index 99075a6..04fabb5 100644 --- a/src/enums/ErrorCodeEnum.ts +++ b/src/enums/ErrorCodeEnum.ts @@ -5,6 +5,9 @@ enum ErrorCodeEnum { // keys KeyNotAllowedError = 3000, + + // network + UnknownNetworkError = 4000, } export default ErrorCodeEnum; diff --git a/src/enums/NetworkIDEnum.ts b/src/enums/NetworkIDEnum.ts new file mode 100644 index 0000000..99cc2ad --- /dev/null +++ b/src/enums/NetworkIDEnum.ts @@ -0,0 +1,8 @@ +enum NetworkIDEnum { + Betanet = 'betanet', + Localnet = 'localnet', + Mainnet = 'mainnet', + Testnet = 'testnet', +} + +export default NetworkIDEnum; diff --git a/src/enums/ViewMethodEnum.ts b/src/enums/ViewMethodEnum.ts index 85cb2f9..8585025 100644 --- a/src/enums/ViewMethodEnum.ts +++ b/src/enums/ViewMethodEnum.ts @@ -3,6 +3,10 @@ enum ViewMethodEnum { GetVersion = 'get_version', IsWritePermissionGranted = 'is_write_permission_granted', StorageBalanceOf = 'storage_balance_of', + Keys = 'keys', + GetAccounts = 'get_accounts', + GetAccount = 'get_account', + GetAccountCount = 'get_account_count', } export default ViewMethodEnum; diff --git a/src/enums/index.ts b/src/enums/index.ts index dc3b207..3154a17 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -1,3 +1,4 @@ export { default as ChangeMethodEnum } from './ChangeMethodEnum'; export { default as ErrorCodeEnum } from './ErrorCodeEnum'; +export { default as NetworkIDEnum } from './NetworkIDEnum'; export { default as ViewMethodEnum } from './ViewMethodEnum'; diff --git a/src/errors/UnknownNetworkError.ts b/src/errors/UnknownNetworkError.ts new file mode 100644 index 0000000..c2ebf05 --- /dev/null +++ b/src/errors/UnknownNetworkError.ts @@ -0,0 +1,17 @@ +// enums +import { ErrorCodeEnum } from '@app/enums'; + +// errors +import BaseError from './BaseError'; + +export default class UnknownNetworkError extends BaseError { + public readonly networkID: string; + public readonly code = ErrorCodeEnum.UnknownNetworkError; + public readonly name: string = 'UnknownNetworkError'; + + constructor(networkID: string, message?: string) { + super(message || `network ID "${networkID}" not known`); + + this.networkID = networkID; + } +} diff --git a/src/errors/index.ts b/src/errors/index.ts index 79f5064..cac0fdd 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -2,3 +2,4 @@ export { default as AccountNotFoundError } from './AccountNotFoundError'; export { default as BaseError } from './BaseError'; export { default as InvalidAccountIdError } from './InvalidAccountIdError'; export { default as KeyNotAllowedError } from './KeyNotAllowedError'; +export { default as UnknownNetworkError } from './UnknownNetworkError'; diff --git a/src/index.ts b/src/index.ts index 9d8c459..f2e48c5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,4 @@ export * from './constants'; export * from './controllers'; export * from './enums'; export * from './types'; +export * from './utils'; diff --git a/src/types/IAccount.ts b/src/types/IAccount.ts new file mode 100644 index 0000000..3d58d2d --- /dev/null +++ b/src/types/IAccount.ts @@ -0,0 +1,8 @@ +import { utils } from 'near-api-js'; + +interface IAccount { + accountID: string; + publicKey: string | utils.PublicKey; +} + +export default IAccount; diff --git a/src/types/IDefaultChangeOptions.ts b/src/types/IDefaultChangeOptions.ts index 14acc85..363e3e2 100644 --- a/src/types/IDefaultChangeOptions.ts +++ b/src/types/IDefaultChangeOptions.ts @@ -1,12 +1,10 @@ -import { utils } from 'near-api-js'; - // types -import type IDefaultOptions from './IDefaultOptions'; +import type IAccount from './IAccount'; -interface IDefaultChangeOptions extends IDefaultOptions { +interface IDefaultChangeOptions { + account: IAccount; blockHash?: string; nonce?: bigint; - publicKey: utils.PublicKey | string; } export default IDefaultChangeOptions; diff --git a/src/types/IDefaultOptions.ts b/src/types/IDefaultOptions.ts deleted file mode 100644 index ce7166e..0000000 --- a/src/types/IDefaultOptions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Account } from 'near-api-js'; - -interface IDefaultOptions { - signer: Account; -} - -export default IDefaultOptions; diff --git a/src/types/IDefaultViewOptions.ts b/src/types/IDefaultViewOptions.ts deleted file mode 100644 index 740c6ea..0000000 --- a/src/types/IDefaultViewOptions.ts +++ /dev/null @@ -1,6 +0,0 @@ -// types -import type IDefaultOptions from './IDefaultOptions'; - -type IDefaultViewOptions = IDefaultOptions; - -export default IDefaultViewOptions; diff --git a/src/types/IGetAccountOptions.ts b/src/types/IGetAccountOptions.ts new file mode 100644 index 0000000..83171cf --- /dev/null +++ b/src/types/IGetAccountOptions.ts @@ -0,0 +1,5 @@ +interface IGetAccountOptions { + accountId: string; +} + +export default IGetAccountOptions; diff --git a/src/types/IGetAccountsOptions.ts b/src/types/IGetAccountsOptions.ts new file mode 100644 index 0000000..542d177 --- /dev/null +++ b/src/types/IGetAccountsOptions.ts @@ -0,0 +1,6 @@ +interface IGetAccountsOptions { + fromIndex?: number; + limit?: number; +} + +export default IGetAccountsOptions; diff --git a/src/types/IGetOptions.ts b/src/types/IGetOptions.ts index 4c90399..4da2fd6 100644 --- a/src/types/IGetOptions.ts +++ b/src/types/IGetOptions.ts @@ -1,11 +1,11 @@ -// types -import type IDefaultViewOptions from './IDefaultViewOptions'; - -interface IGetOptions extends IDefaultViewOptions { +interface IGetOptions { keys: string[]; + blockHeight?: bigint; returnDeleted?: boolean; withBlockHeight?: boolean; withNodeId?: boolean; + useApiServer?: boolean; + withTimestamp?: boolean; } export default IGetOptions; diff --git a/src/types/IGetVersionOptions.ts b/src/types/IGetVersionOptions.ts deleted file mode 100644 index 0eeb5ae..0000000 --- a/src/types/IGetVersionOptions.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type IDefaultViewOptions from './IDefaultViewOptions'; - -type IGetVersionOptions = IDefaultViewOptions; - -export default IGetVersionOptions; diff --git a/src/types/IIndexOptions.ts b/src/types/IIndexOptions.ts new file mode 100644 index 0000000..91c79a1 --- /dev/null +++ b/src/types/IIndexOptions.ts @@ -0,0 +1,16 @@ +interface IIndexOptions { + action: string; + key: + | string + | { + type?: string; + path?: string; + blockHeight?: number; + }; + accountId?: string; + order?: string; + limit?: string; + from?: string; +} + +export default IIndexOptions; diff --git a/src/types/IIsWritePermissionGrantedWithAccountIdOptions.ts b/src/types/IIsWritePermissionGrantedWithAccountIdOptions.ts index f1dafbe..7894366 100644 --- a/src/types/IIsWritePermissionGrantedWithAccountIdOptions.ts +++ b/src/types/IIsWritePermissionGrantedWithAccountIdOptions.ts @@ -1,11 +1,8 @@ -// types -import type IDefaultViewOptions from './IDefaultViewOptions'; - /** * @property {string} granteeAccountId - the account ID to check if it has write permissions for a key. * @property {string} key - the key to check if the grantee has write permission. */ -interface IIsWritePermissionGrantedOptions extends IDefaultViewOptions { +interface IIsWritePermissionGrantedOptions { granteeAccountId: string; key: string; } diff --git a/src/types/IIsWritePermissionGrantedWithPublicKeyOptions.ts b/src/types/IIsWritePermissionGrantedWithPublicKeyOptions.ts index a9d0dd6..390f026 100644 --- a/src/types/IIsWritePermissionGrantedWithPublicKeyOptions.ts +++ b/src/types/IIsWritePermissionGrantedWithPublicKeyOptions.ts @@ -1,15 +1,11 @@ import { utils } from 'near-api-js'; -// types -import type IDefaultViewOptions from './IDefaultViewOptions'; - /** * @property {utils.PublicKey | string} granteePublicKey - the public key the account to check if it has write * permissions for a key. * @property {string} key - the key to check if the grantee has write permission. */ -interface IIsWritePermissionGrantedWithPublicKeyOptions - extends IDefaultViewOptions { +interface IIsWritePermissionGrantedWithPublicKeyOptions { granteePublicKey: utils.PublicKey | string; key: string; } diff --git a/src/types/IKeysOptions.ts b/src/types/IKeysOptions.ts new file mode 100644 index 0000000..bc116fe --- /dev/null +++ b/src/types/IKeysOptions.ts @@ -0,0 +1,10 @@ +interface IKeyOptions { + keys: string[]; + blockHeight?: bigint; + returnDeleted?: boolean; + returnType?: boolean; + valuesOnly?: boolean; + useApiServer?: boolean; +} + +export default IKeyOptions; diff --git a/src/types/INewSocialOptions.ts b/src/types/INewSocialOptions.ts index a64ecb0..640c54a 100644 --- a/src/types/INewSocialOptions.ts +++ b/src/types/INewSocialOptions.ts @@ -1,5 +1,10 @@ +// types +import type IRPCOptions from './IRPCOptions'; + interface INewSocialOptions { contractId?: string; + network?: string | IRPCOptions; + apiServer?: string; } export default INewSocialOptions; diff --git a/src/types/IRPCOptions.ts b/src/types/IRPCOptions.ts new file mode 100644 index 0000000..8588448 --- /dev/null +++ b/src/types/IRPCOptions.ts @@ -0,0 +1,6 @@ +interface IRPCOptions { + apiKey?: string; + url: string; +} + +export default IRPCOptions; diff --git a/src/types/ISocialApiServerGetArgs.ts b/src/types/ISocialApiServerGetArgs.ts new file mode 100644 index 0000000..a3ef092 --- /dev/null +++ b/src/types/ISocialApiServerGetArgs.ts @@ -0,0 +1,13 @@ +interface ISocialApiServerGetArgsOptions { + with_block_height?: boolean; + return_deleted?: boolean; + with_timestamp?: boolean; +} + +interface ISocialApiServerGetArgs { + keys: string[]; + blockHeight?: bigint; + options?: ISocialApiServerGetArgsOptions; +} + +export default ISocialApiServerGetArgs; diff --git a/src/types/ISocialApiServerIndexArgs.ts b/src/types/ISocialApiServerIndexArgs.ts new file mode 100644 index 0000000..094648f --- /dev/null +++ b/src/types/ISocialApiServerIndexArgs.ts @@ -0,0 +1,20 @@ +interface ISocialApiServerIndexArgsOptions { + accountId?: string; + order?: string; + limit?: string; + from?: string; +} + +interface ISocialApiServerIndexArgs { + action: string; + key: + | string + | { + type?: string; + path?: string; + blockHeight?: number; + }; + options?: ISocialApiServerIndexArgsOptions; +} + +export default ISocialApiServerIndexArgs; diff --git a/src/types/ISocialApiServerKeysArgs.ts b/src/types/ISocialApiServerKeysArgs.ts new file mode 100644 index 0000000..846f5a8 --- /dev/null +++ b/src/types/ISocialApiServerKeysArgs.ts @@ -0,0 +1,13 @@ +interface ISocialApiServerKeysArgsOptions { + return_deleted?: boolean; + return_type?: boolean; + values_only?: boolean; +} + +interface ISocialApiServerKeysArgs { + keys: string[]; + blockHeight?: bigint; + options?: ISocialApiServerKeysArgsOptions; +} + +export default ISocialApiServerKeysArgs; diff --git a/src/types/ISocialDBContractGetAccountsArgs.ts b/src/types/ISocialDBContractGetAccountsArgs.ts new file mode 100644 index 0000000..26e44e0 --- /dev/null +++ b/src/types/ISocialDBContractGetAccountsArgs.ts @@ -0,0 +1,6 @@ +interface ISocialDBContractGetAccountsArgs { + from_index?: number; + limit?: number; +} + +export default ISocialDBContractGetAccountsArgs; diff --git a/src/types/ISocialDBContractKeysArgs.ts b/src/types/ISocialDBContractKeysArgs.ts new file mode 100644 index 0000000..483e20f --- /dev/null +++ b/src/types/ISocialDBContractKeysArgs.ts @@ -0,0 +1,13 @@ +interface ISocialDBContractKeysArgsOptions { + return_deleted?: boolean; + return_type?: boolean; + values_only?: boolean; +} + +interface ISocialDBContractKeysArgs { + keys: string[]; + blockHeight?: bigint; + options?: ISocialDBContractKeysArgsOptions; +} + +export default ISocialDBContractKeysArgs; diff --git a/src/types/IStorageBalanceOfOptions.ts b/src/types/IStorageBalanceOfOptions.ts deleted file mode 100644 index 366c822..0000000 --- a/src/types/IStorageBalanceOfOptions.ts +++ /dev/null @@ -1,8 +0,0 @@ -// types -import type IDefaultViewOptions from './IDefaultViewOptions'; - -interface IStorageBalanceOfOptions extends IDefaultViewOptions { - accountId: string; -} - -export default IStorageBalanceOfOptions; diff --git a/src/types/NetworkIds.ts b/src/types/NetworkIds.ts new file mode 100644 index 0000000..d7a9949 --- /dev/null +++ b/src/types/NetworkIds.ts @@ -0,0 +1,3 @@ +type NetworkIds = 'testnet' | 'mainnet' | 'betanet' | 'localnet'; + +export default NetworkIds; diff --git a/src/types/index.ts b/src/types/index.ts index 20f7e67..b8ab980 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,20 +1,29 @@ +export type { default as IAccount } from './IAccount'; export type { default as IDefaultChangeOptions } from './IDefaultChangeOptions'; -export type { default as IDefaultOptions } from './IDefaultOptions'; -export type { default as IDefaultViewOptions } from './IDefaultViewOptions'; export type { default as IGetOptions } from './IGetOptions'; +export type { default as IGetAccountOptions } from './IGetAccountOptions'; +export type { default as IGetAccountsOptions } from './IGetAccountsOptions'; +export type { default as IIndexOptions } from './IIndexOptions'; +export type { default as IKeysOptions } from './IKeysOptions'; +export type { default as NetworkIds } from './NetworkIds'; export type { default as IGrantWritePermissionWithAccountIdOptions } from './IGrantWritePermissionWithAccountIdOptions'; export type { default as IGrantWritePermissionWithPublicKeyOptions } from './IGrantWritePermissionWithPublicKeyOptions'; export type { default as IIsWritePermissionGrantedWithAccountIdOptions } from './IIsWritePermissionGrantedWithAccountIdOptions'; export type { default as IIsWritePermissionGrantedWithPublicKeyOptions } from './IIsWritePermissionGrantedWithPublicKeyOptions'; -export type { default as IGetVersionOptions } from './IGetVersionOptions'; export type { default as INewSocialOptions } from './INewSocialOptions'; +export type { default as IRPCOptions } from './IRPCOptions'; export type { default as ISetOptions } from './ISetOptions'; export type { default as IStorageDepositOptions } from './IStorageDepositOptions'; export type { default as IStorageWithdrawOptions } from './IStorageWithdrawOptions'; export type { default as ISocialDBContractAccount } from './ISocialDBContractAccount'; export type { default as ISocialDBContractAccountSharedStorage } from './ISocialDBContractAccountSharedStorage'; export type { default as ISocialDBContractGetArgs } from './ISocialDBContractGetArgs'; +export type { default as ISocialDBContractKeysArgs } from './ISocialDBContractKeysArgs'; +export type { default as ISocialApiServerGetArgs } from './ISocialApiServerGetArgs'; +export type { default as ISocialApiServerIndexArgs } from './ISocialApiServerIndexArgs'; +export type { default as ISocialApiServerKeysArgs } from './ISocialApiServerKeysArgs'; export type { default as ISocialDBContractGetAccountArgs } from './ISocialDBContractGetAccountArgs'; +export type { default as ISocialDBContractGetAccountsArgs } from './ISocialDBContractGetAccountsArgs'; export type { default as ISocialDBContractGrantWritePermissionArgs } from './ISocialDBContractGrantWritePermissionArgs'; export type { default as ISocialDBContractIsWritePermissionGrantedArgs } from './ISocialDBContractIsWritePermissionGrantedArgs'; export type { default as ISocialDBContractSetArgs } from './ISocialDBContractSetArgs'; @@ -24,4 +33,3 @@ export type { default as ISocialDBContractStorageBalanceOfArgs } from './ISocial export type { default as ISocialDBContractStorageDepositArgs } from './ISocialDBContractStorageDepositArgs'; export type { default as ISocialDBContractStorageWithdrawArgs } from './ISocialDBContractStorageWithdrawArgs'; export type { default as ISocialDBContractStorageTracker } from './ISocialDBContractStorageTracker'; -export type { default as IStorageBalanceOfOptions } from './IStorageBalanceOfOptions'; diff --git a/src/utils/calculateRequiredDeposit/calculateRequiredDeposit.test.ts b/src/utils/calculateRequiredDeposit/calculateRequiredDeposit.test.ts index 0765058..2fd1bbb 100644 --- a/src/utils/calculateRequiredDeposit/calculateRequiredDeposit.test.ts +++ b/src/utils/calculateRequiredDeposit/calculateRequiredDeposit.test.ts @@ -109,12 +109,12 @@ describe('calculateRequiredDeposit()', () => { data, storageBalance: { available: BigInt(storageCostOfData.plus('1').toFixed()), - total: BigInt(storageCostOfData.minus('1').toFixed()), + total: BigInt(storageCostOfData.plus('10').toFixed()), }, }); // assert - expect(result.toFixed()).toBe('1'); + expect(result.toFixed()).toBe('0'); }); }); }); diff --git a/src/utils/calculateRequiredDeposit/calculateRequiredDeposit.ts b/src/utils/calculateRequiredDeposit/calculateRequiredDeposit.ts index 75ff1ce..1c51bde 100644 --- a/src/utils/calculateRequiredDeposit/calculateRequiredDeposit.ts +++ b/src/utils/calculateRequiredDeposit/calculateRequiredDeposit.ts @@ -3,7 +3,6 @@ import BigNumber from 'bignumber.js'; // constants import { MINIMUM_STORAGE_IN_BYTES, - ONE_YOCTO, STORAGE_COST_PER_BYTES_IN_ATOMIC_UNITS, } from '@app/constants'; import { EXTRA_STORAGE_BALANCE } from './constants'; @@ -48,5 +47,5 @@ export default function calculateRequiredDeposit({ // if the storage deposit available is less than the cost of storage, use the difference as the required deposit return storageDepositAvailable.lt(storageCostOfData) ? storageCostOfData.minus(storageDepositAvailable) - : new BigNumber(ONE_YOCTO); + : new BigNumber('0'); } diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..276087c --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,10 @@ +export * from './calculateRequiredDeposit'; +export * from './calculateSizeOfData'; +export * from './convertNEARToYoctoNEAR'; +export * from './convertYoctoNEARToNEAR'; +export * from './isObject'; +export * from './parseKeysFromData'; +export * from './rpcQueries'; +export * from './rpcURLFromNetworkID'; +export * from './transformActions'; +export * from './validateAccountId'; diff --git a/src/utils/rpcQueries/index.ts b/src/utils/rpcQueries/index.ts new file mode 100644 index 0000000..d805491 --- /dev/null +++ b/src/utils/rpcQueries/index.ts @@ -0,0 +1,2 @@ +export * from './viewAccessKeyList'; +export * from './viewFunction'; diff --git a/src/utils/rpcQueries/viewAccessKeyList/index.ts b/src/utils/rpcQueries/viewAccessKeyList/index.ts new file mode 100644 index 0000000..bbdb66f --- /dev/null +++ b/src/utils/rpcQueries/viewAccessKeyList/index.ts @@ -0,0 +1,2 @@ +export { default } from './viewAccessKeyList'; +export * from './types'; diff --git a/src/utils/rpcQueries/viewAccessKeyList/types/IAccessKeyInfoViewRaw.ts b/src/utils/rpcQueries/viewAccessKeyList/types/IAccessKeyInfoViewRaw.ts new file mode 100644 index 0000000..699be14 --- /dev/null +++ b/src/utils/rpcQueries/viewAccessKeyList/types/IAccessKeyInfoViewRaw.ts @@ -0,0 +1,8 @@ +import type { AccessKeyViewRaw } from '@near-js/types'; + +interface IAccessKeyInfoViewRaw { + access_key: AccessKeyViewRaw; + public_key: string; +} + +export default IAccessKeyInfoViewRaw; diff --git a/src/utils/rpcQueries/viewAccessKeyList/types/IOptions.ts b/src/utils/rpcQueries/viewAccessKeyList/types/IOptions.ts new file mode 100644 index 0000000..9d255e5 --- /dev/null +++ b/src/utils/rpcQueries/viewAccessKeyList/types/IOptions.ts @@ -0,0 +1,8 @@ +import { providers } from 'near-api-js'; + +interface IOptions { + accountID: string; + provider: providers.JsonRpcProvider; +} + +export default IOptions; diff --git a/src/utils/rpcQueries/viewAccessKeyList/types/IResponse.ts b/src/utils/rpcQueries/viewAccessKeyList/types/IResponse.ts new file mode 100644 index 0000000..3d8db6e --- /dev/null +++ b/src/utils/rpcQueries/viewAccessKeyList/types/IResponse.ts @@ -0,0 +1,10 @@ +import type { QueryResponseKind } from '@near-js/types'; + +// types +import type IAccessKeyInfoViewRaw from './IAccessKeyInfoViewRaw'; + +interface IResponse extends QueryResponseKind { + keys: IAccessKeyInfoViewRaw[]; +} + +export default IResponse; diff --git a/src/utils/rpcQueries/viewAccessKeyList/types/index.ts b/src/utils/rpcQueries/viewAccessKeyList/types/index.ts new file mode 100644 index 0000000..91119e0 --- /dev/null +++ b/src/utils/rpcQueries/viewAccessKeyList/types/index.ts @@ -0,0 +1,3 @@ +export type { default as IAccessKeyInfoViewRaw } from './IAccessKeyInfoViewRaw'; +export type { default as IOptions } from './IOptions'; +export type { default as IResponse } from './IResponse'; diff --git a/src/utils/rpcQueries/viewAccessKeyList/viewAccessKeyList.ts b/src/utils/rpcQueries/viewAccessKeyList/viewAccessKeyList.ts new file mode 100644 index 0000000..e95f629 --- /dev/null +++ b/src/utils/rpcQueries/viewAccessKeyList/viewAccessKeyList.ts @@ -0,0 +1,29 @@ +import type { AccessKeyInfoView } from '@near-js/types'; + +// types +import type { IOptions, IResponse } from './types'; + +/** + * Convenience function to get an account's access keys details. + * @param {IOptions} options - the account ID and a provider to direct to which RPC to query. + * @returns {Promise} a promise that resolves to the account's access keys details. + */ +export default async function viewAccessKeyList({ + accountID, + provider, +}: IOptions): Promise { + const response = await provider.query({ + account_id: accountID, + finality: 'optimistic', + request_type: 'view_access_key_list', + }); + + // convert the nonces into bigint + return response.keys.map(({ access_key, public_key }) => ({ + public_key, + access_key: { + ...access_key, + nonce: BigInt(access_key.nonce), + }, + })); +} diff --git a/src/utils/rpcQueries/viewFunction/index.ts b/src/utils/rpcQueries/viewFunction/index.ts new file mode 100644 index 0000000..02837f7 --- /dev/null +++ b/src/utils/rpcQueries/viewFunction/index.ts @@ -0,0 +1 @@ +export { default } from './viewFunction'; diff --git a/src/utils/rpcQueries/viewFunction/types/IOptions.ts b/src/utils/rpcQueries/viewFunction/types/IOptions.ts new file mode 100644 index 0000000..f2e0e67 --- /dev/null +++ b/src/utils/rpcQueries/viewFunction/types/IOptions.ts @@ -0,0 +1,10 @@ +import { providers } from 'near-api-js'; + +interface IOptions { + args?: unknown; + contractId: string; + method: string; + provider: providers.JsonRpcProvider; +} + +export default IOptions; diff --git a/src/utils/rpcQueries/viewFunction/types/index.ts b/src/utils/rpcQueries/viewFunction/types/index.ts new file mode 100644 index 0000000..68e7001 --- /dev/null +++ b/src/utils/rpcQueries/viewFunction/types/index.ts @@ -0,0 +1 @@ +export type { default as IOptions } from './IOptions'; diff --git a/src/utils/rpcQueries/viewFunction/viewFunction.test.ts b/src/utils/rpcQueries/viewFunction/viewFunction.test.ts new file mode 100644 index 0000000..ebe3bae --- /dev/null +++ b/src/utils/rpcQueries/viewFunction/viewFunction.test.ts @@ -0,0 +1,69 @@ +import { providers } from 'near-api-js'; +import type { IOptions } from './types'; +import viewFunction from './viewFunction'; // Adjust the import path as needed + +// Mock the near-api-js providers +jest.mock('near-api-js', () => ({ + providers: { + JsonRpcProvider: jest.fn().mockImplementation(() => ({ + query: jest.fn(), + })), + }, +})); + +describe('viewFunction', () => { + let defaultOptions: IOptions; + let mockQuery: jest.Mock; + + beforeEach(() => { + mockQuery = jest.fn(); + (providers.JsonRpcProvider as jest.Mock).mockImplementation(() => ({ + query: mockQuery, + })); + + defaultOptions = { + contractId: 'test.near', + method: 'get_value', + provider: new providers.JsonRpcProvider({ + url: 'https://a.query.to.nowhere', + }), + }; + }); + + it('should call the provider with correct parameters and return parsed result', async () => { + const mockResult = { + result: Buffer.from(JSON.stringify({ value: 'test' })).toJSON().data, + }; + mockQuery.mockResolvedValue(mockResult); + + const result = await viewFunction({ + ...defaultOptions, + args: { key: 'someKey' }, + }); + + expect(mockQuery).toHaveBeenCalledWith({ + request_type: 'call_function', + account_id: 'test.near', + method_name: 'get_value', + args_base64: Buffer.from(JSON.stringify({ key: 'someKey' })).toString( + 'base64' + ), + finality: 'optimistic', + }); + expect(result).toEqual({ value: 'test' }); + }); + + it('should handle empty args', async () => { + mockQuery.mockResolvedValue({ + result: Buffer.from('{}').toString('base64'), + }); + + await viewFunction(defaultOptions); + + expect(mockQuery).toHaveBeenCalledWith( + expect.objectContaining({ + args_base64: Buffer.from('{}').toString('base64'), + }) + ); + }); +}); diff --git a/src/utils/rpcQueries/viewFunction/viewFunction.ts b/src/utils/rpcQueries/viewFunction/viewFunction.ts new file mode 100644 index 0000000..d919e7a --- /dev/null +++ b/src/utils/rpcQueries/viewFunction/viewFunction.ts @@ -0,0 +1,55 @@ +import { printTxOutcomeLogs } from '@near-js/utils'; + +// types +import type { IOptions } from './types'; + +type ViewFunctionResult = + | string + | number + | boolean + | null + | object + | unknown[] + | undefined; + +function parseJsonFromRawResponse(response: Uint8Array): ViewFunctionResult { + return JSON.parse(new TextDecoder().decode(response)); +} + +function base64Encode(str: string): string { + try { + return btoa(str); + } catch (err) { + return Buffer.from(str).toString('base64'); + } +} + +export default async function viewFunction({ + args = {}, + contractId, + method, + provider, +}: IOptions): Promise { + const res = await provider.query({ + request_type: 'call_function', + account_id: contractId, + method_name: method, + args_base64: base64Encode(JSON.stringify(args)), + finality: 'optimistic', + }); + + if ('logs' in res && Array.isArray(res.logs) && res.logs.length > 0) { + printTxOutcomeLogs({ contractId, logs: res.logs }); + } + + if ( + 'result' in res && + Array.isArray(res.result) && + res.result.length > 0 && + res.result !== undefined + ) { + return parseJsonFromRawResponse(new Uint8Array(res.result)); + } + + return undefined; +} diff --git a/src/utils/rpcURLFromNetworkID/index.ts b/src/utils/rpcURLFromNetworkID/index.ts new file mode 100644 index 0000000..c220a3d --- /dev/null +++ b/src/utils/rpcURLFromNetworkID/index.ts @@ -0,0 +1 @@ +export { default } from './rpcURLFromNetworkID'; diff --git a/src/utils/rpcURLFromNetworkID/rpcURLFromNetworkID.ts b/src/utils/rpcURLFromNetworkID/rpcURLFromNetworkID.ts new file mode 100644 index 0000000..bc88bc9 --- /dev/null +++ b/src/utils/rpcURLFromNetworkID/rpcURLFromNetworkID.ts @@ -0,0 +1,25 @@ +// constants +import { networkRPCs } from '@app/constants'; + +// enums +import { NetworkIDEnum } from '@app/enums'; + +/** + * Convenience function that gets the RPC URL for a given network ID. If the network ID is unknown, null is returned. + * @param {string} networkID - a network ID. Should be one of betanet, localnet, mainnet or testnet. + * @returns {string | null} the RPC URL for a given network ID, or null if the network ID is unknown. + */ +export default function rpcURLFromNetworkID(networkID: string): string | null { + switch (networkID) { + case NetworkIDEnum.Betanet: + return networkRPCs.betanet; + case NetworkIDEnum.Localnet: + return networkRPCs.localnet; + case NetworkIDEnum.Mainnet: + return networkRPCs.mainnet; + case NetworkIDEnum.Testnet: + return networkRPCs.testnet; + default: + return null; + } +} diff --git a/src/utils/transformActions/index.ts b/src/utils/transformActions/index.ts new file mode 100644 index 0000000..6c012a6 --- /dev/null +++ b/src/utils/transformActions/index.ts @@ -0,0 +1 @@ +export * from './transformActions'; diff --git a/src/utils/transformActions/transformActions.test.ts b/src/utils/transformActions/transformActions.test.ts new file mode 100644 index 0000000..24a7043 --- /dev/null +++ b/src/utils/transformActions/transformActions.test.ts @@ -0,0 +1,87 @@ +import { transformActions } from './transformActions'; +import { Action, FunctionCall } from '@near-js/transactions'; + +describe('transformActions', () => { + it('should transform a valid FunctionCall action', () => { + const mockFunctionCall: FunctionCall = { + methodName: 'testMethod', + args: new Uint8Array([1, 2, 3]), + gas: BigInt(1000000), + deposit: BigInt(0), + }; + + const mockAction: Action = { + enum: 'FunctionCall', + functionCall: mockFunctionCall, + }; + + const result = transformActions([mockAction]); + + expect(result).toEqual([ + { + type: 'FunctionCall', + params: { + methodName: 'testMethod', + args: new Uint8Array([1, 2, 3]), + gas: BigInt(1000000), + deposit: BigInt(0), + }, + }, + ]); + }); + + it('should throw an error for unsupported action types', () => { + const mockUnsupportedAction: Action = { + enum: 'CreateAccount', + createAccount: {}, + }; + + expect(() => transformActions([mockUnsupportedAction])).toThrow( + 'Unsupported action type: CreateAccount' + ); + }); + + it('should transform multiple actions', () => { + const mockFunctionCall1: FunctionCall = { + methodName: 'method1', + args: new Uint8Array([4, 5, 6]), + gas: BigInt(2000000), + deposit: BigInt(100), + }; + + const mockFunctionCall2: FunctionCall = { + methodName: 'method2', + args: new Uint8Array([7, 8, 9]), + gas: BigInt(3000000), + deposit: BigInt(200), + }; + + const mockActions: Action[] = [ + { enum: 'FunctionCall', functionCall: mockFunctionCall1 }, + { enum: 'FunctionCall', functionCall: mockFunctionCall2 }, + ]; + + const result = transformActions(mockActions); + + expect(result).toEqual([ + { + type: 'FunctionCall', + params: { + methodName: 'method1', + args: new Uint8Array([4, 5, 6]), + gas: BigInt(2000000), + deposit: BigInt(100), + }, + }, + { + type: 'FunctionCall', + params: { + methodName: 'method2', + args: new Uint8Array([7, 8, 9]), + gas: BigInt(3000000), + deposit: BigInt(200), + }, + }, + ]); + }); +}); diff --git a/src/utils/transformActions/transformActions.ts b/src/utils/transformActions/transformActions.ts new file mode 100644 index 0000000..0c06a59 --- /dev/null +++ b/src/utils/transformActions/transformActions.ts @@ -0,0 +1,22 @@ +import { Action } from '@near-js/transactions'; +import { TransformedAction } from './types'; + +// Helper function to transform Actions +export const transformActions = (actions: Action[]): TransformedAction[] => { + return actions.map((action) => { + if (!action.functionCall) { + throw new Error(`Unsupported action type: ${action.enum}`); + } + + const functionCall = action.functionCall; + return { + type: 'FunctionCall', + params: { + methodName: functionCall.methodName, + args: functionCall.args, + gas: functionCall.gas, + deposit: functionCall.deposit, + }, + }; + }); +}; diff --git a/src/utils/transformActions/types/ITransformedActions.ts b/src/utils/transformActions/types/ITransformedActions.ts new file mode 100644 index 0000000..7ff3185 --- /dev/null +++ b/src/utils/transformActions/types/ITransformedActions.ts @@ -0,0 +1,9 @@ +export interface TransformedAction { + type: 'FunctionCall'; + params: { + methodName: string; + args: Uint8Array; + gas: bigint; + deposit: bigint; + }; +} diff --git a/src/utils/transformActions/types/index.ts b/src/utils/transformActions/types/index.ts new file mode 100644 index 0000000..3a67a48 --- /dev/null +++ b/src/utils/transformActions/types/index.ts @@ -0,0 +1 @@ +export * from './ITransformedActions';