diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b0e879..7aae52a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Bug Fixes -* **io:** add getDemandFactor api ([feab461](https://github.com/ar-io/ar-io-sdk/commit/feab4612d4e95b0fea14a4e0fa9f17a5c57779e1)) +* **io:** add `getDemandFactor` api ([feab461](https://github.com/ar-io/ar-io-sdk/commit/feab4612d4e95b0fea14a4e0fa9f17a5c57779e1)) * **io:** update `getTokenSupply` to type that returns full breakdown of tokens ([e790055](https://github.com/ar-io/ar-io-sdk/commit/e790055d03df0786c93db54d4d3294160fac1ad5)) * **types:** add `totalEligibleGateways` to `AoEpochDistributionData` type ([9a35d39](https://github.com/ar-io/ar-io-sdk/commit/9a35d39ed811076a03a392cd5ac7b8f2ce878d98)) * **types:** update gateways to include services ([a3fe5b4](https://github.com/ar-io/ar-io-sdk/commit/a3fe5b41725d1648f19d8e72d71e59d41bed91fe)) diff --git a/README.md b/README.md index 824a4431..b2ae6d81 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,16 @@ This is the home of [ar.io] SDK. This SDK provides functionality for interacting - [`getTokenSupply()`](#gettokensupply) - [`getBalance({ address })`](#getbalance-address-) - [`getBalances({ cursor, limit, sortBy, sortOrder })`](#getbalances-cursor-limit-sortby-sortorder-) + - [`getVault({ address, vaultId })`](#getvault-address-vaultid-) + - [`getVaults({ cursor, limit, sortBy, sortOrder })`](#getvaults-cursor-limit-sortby-sortorder-) - [`getGateway({ address })`](#getgateway-address-) - [`getGateways({ cursor, limit, sortBy, sortOrder })`](#getgateways-cursor-limit-sortby-sortorder-) + - [`buyRecord({ name, type, years, processId })`](#buyrecord-name-type-years-processid-) - [`getArNSRecord({ name })`](#getarnsrecord-name-) - [`getArNSRecords({ cursor, limit, sortBy, sortOrder })`](#getarnsrecords-cursor-limit-sortby-sortorder-) + - [`getArNSAuctions({ cursor, limit, sortBy, sortOrder })`](#getarnsauctions-cursor-limit-sortby-sortorder-) + - [`getArNSAuction({ name })`](#getarnsauction-name-) + - [`getArNSAuctionPrices({ name, type, years, intervalMs })`](#getarnsauctionprices-name-type-years-intervalms-) - [`getDemandFactor()`](#getdemandfactor) - [`getObservations({ epochIndex })`](#getobservations-epochindex-) - [`getDistributions({ epochIndex })`](#getdistributions-epochindex-) @@ -44,14 +50,16 @@ This is the home of [ar.io] SDK. This SDK provides functionality for interacting - [`leaveNetwork()`](#leavenetwork) - [`updateGatewaySettings(gatewaySettings)`](#updategatewaysettingsgatewaysettings) - [`increaseDelegateStake({ target, qty })`](#increasedelegatestake-target-qty-) - - [`decreaseDelegateStake({ target, qty })`](#decreasedelegatestake-target-qty-) + - [`decreaseDelegateStake({ target, qty, instant })`](#decreasedelegatestake-target-qty-instant-) + - [`instantWithdrawal({ gatewayAddress, vaultId })`](#instantwithdrawal-gatewayaddress-vaultid-) - [`increaseOperatorStake({ qty })`](#increaseoperatorstake-qty-) - [`decreaseOperatorStake({ qty })`](#decreaseoperatorstake-qty-) - [`saveObservations({ reportTxId, failedGateways })`](#saveobservations-reporttxid-failedgateways-) - [`transfer({ target, qty })`](#transfer-target-qty-) - [`increaseUndernameLimit({ name, qty })`](#increaseundernamelimit-name-qty-) - [`extendLease({ name, years })`](#extendlease-name-years-) - - [`cancelDelegateWithdrawal({ address, vaultId })`](#canceldelegatewithdrawal-address-vaultid-) + - [`cancelWithdrawal({ gatewayAddress, vaultId })`](#cancelwithdrawal-gatewayaddress-vaultid-) + - [`submitAuctionBid({ name, type, years, processId })`](#submitauctionbid-name-type-years-processid-) - [Configuration](#configuration) - [Arweave Name Tokens (ANT's)](#arweave-name-tokens-ants) - [ANT APIs](#ant-apis) @@ -68,6 +76,10 @@ This is the home of [ar.io] SDK. This SDK provides functionality for interacting - [`removeRecord({ undername })`](#removerecord-undername-) - [`setName({ name })`](#setname-name-) - [`setTicker({ ticker })`](#setticker-ticker-) + - [`setDescription({ description })`](#setdescription-description-) + - [`setKeywords({ keywords })`](#setkeywords-keywords-) + - [`releaseName({ name, ioProcessId })`](#releasename-name-ioprocessid-) + - [`reassignName({ name, ioProcessId, antProcessId })`](#reassignname-name-ioprocessid-antprocessid-) - [Configuration](#configuration-1) - [Logging](#logging) - [Configuration](#configuration-2) @@ -386,6 +398,73 @@ const balances = await io.getBalances({ ``` + +#### `getVault({ address, vaultId })` + +Retrieves the locked-balance user vault of the IO process by the specified wallet address and vault ID. + +```typescript +const io = IO.init(); +const vault = await io.getVault({ + address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + vaultId: 'vaultIdOne', +}); +``` + +
+ Output + +```json +{ + "balance": 1000000, + "startTimestamp": 123, + "endTimestamp": 4567 +} +``` + +#### `getVaults({ cursor, limit, sortBy, sortOrder })` + +Retrieves all locked-balance user vaults of the IO process, paginated and sorted by the specified criteria. The `cursor` used for pagination is the last wallet address from the previous request. + +```typescript +const io = IO.init(); +const vaults = await io.getVaults({ + cursor: '0', + limit: 100, + sortBy: 'balance', + sortOrder: 'desc', +}); +``` + +
+ Output + +```json +{ + "items": [ + { + "address": "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ", + "vaultId": "vaultIdOne", + "balance": 1000000, + "startTimestamp": 123, + "endTimestamp": 4567 + }, + { + "address": "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ", + "vaultId": "vaultIdTwo", + "balance": 1000000, + "startTimestamp": 123, + "endTimestamp": 4567 + } + // ...98 other addresses with vaults + ], + "hasMore": true, + "nextCursor": "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ", + "totalItems": 1789, + "sortBy": "balance", + "sortOrder": "desc" +} +``` #### `getGateway({ address })` @@ -499,6 +578,23 @@ Available `sortBy` options are any of the keys on the gateway object, e.g. `oper
+#### `buyRecord({ name, type, years, processId })` + +Purchases a new ArNS record with the specified name, type, and duration. + +_Note: Requires `signer` to be provided on `IO.init` to sign the transaction._ + +```typescript +const io = IO.init({ processId: IO_DEVNET_PROCESS_ID, signer }); +const record = await io.buyRecord( + { name: 'ardrive', type: 'lease', years: 1 }, + { + // optional tags + tags: [{ name: 'App-Name', value: 'ArNS-App' }], + }, +); +``` + #### `getArNSRecord({ name })` Retrieves the record info of the specified ArNS name. @@ -597,6 +693,136 @@ Available `sortBy` options are any of the keys on the record object, e.g. `name`
+#### `getArNSAuctions({ cursor, limit, sortBy, sortOrder })` + +Retrieves all active auctions of the IO process, paginated and sorted by the specified criteria. The `cursor` used for pagination is the last auction name from the previous request. + +```typescript +const io = IO.init(); +const auctions = await io.getArNSAuctions({ + limit: 100, + sortBy: 'endTimestamp', + sortOrder: 'asc', // return the auctions ending soonest first +}); +``` + +
+ Output + +```json +{ + "items": [ + { + "name": "permalink", + "endTimestamp": 1730985241349, + "startTimestamp": 1729775641349, + "baseFee": 250000000, + "demandFactor": 1.05256, + "initiator": "GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc", + "settings": { + "durationMs": 1209600000, + "decayRate": 0.000000000016847809193121693, + "scalingExponent": 190, + "startPriceMultiplier": 50 + } + } + ], + "hasMore": false, + "totalItems": 1, + "sortBy": "endTimestamp", + "sortOrder": "asc" +} +``` + +
+ +#### `getArNSAuction({ name })` + +Retrieves the auction data for the specified auction name. + +```typescript +const io = IO.init(); +const auction = await io.getArNSAuction({ name: 'permalink' }); +``` + +
+ Output + +```json +{ + "name": "permalink", + "endTimestamp": 1730985241349, + "startTimestamp": 1729775641349, + "baseFee": 250000000, + "demandFactor": 1.05256, + "initiator": "GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc", + "settings": { + "durationMs": 1209600000, + "decayRate": 0.000000000016847809193121693, + "scalingExponent": 190, + "startPriceMultiplier": 50 + } +} +``` + +
+ +#### `getArNSAuctionPrices({ name, type, years, intervalMs })` + +Retrieves the auction price curve of the specified auction name for the specified type, duration, and interval. The `intervalMs` is the number of milliseconds between price points on the curve. The default interval is 15 minutes. + +```typescript +const io = IO.init(); +const priceCurve = await io.getArNSAuctionPrices({ + name: 'permalink', + type: 'lease', + years: 1, + intervalMs: 3600000, // 1 hour price intervals (default is 15 minutes) +}); +``` + +
+ Output + +```json +{ + "name": "permalink", + "type": "lease", + "currentPrice": 12582015000, + "years": 1, + "prices": { + "1730412841349": 1618516789, + "1729908841349": 8210426826, + "1730722441349": 592768907, + "1730859241349": 379659914, + "1730866441349": 370850139, + "1730884441349": 349705277, + "1730150041349": 3780993370, + "1730031241349": 5541718397, + "1730603641349": 872066253, + "1730715241349": 606815377, + "1730942041349": 289775172, + "1730916841349": 314621977, + "1730484841349": 1281957300, + "1730585641349": 924535164, + "1730232841349": 2895237473, + "1730675641349": 690200977, + "1730420041349": 1581242331, + "1729786441349": 12154428186, + "1730308441349": 2268298483, + "1730564041349": 991657913, + "1730081641349": 4712427282, + "1730909641349": 322102563, + "1730945641349": 286388732, + "1730024041349": 5671483398, + "1729937641349": 7485620175 + // ... + } +} +``` + +
+ #### `getDemandFactor()` Retrieves the current demand factor of the network. The demand factor is a multiplier applied to the cost of ArNS interactions based on the current network demand. @@ -941,9 +1167,9 @@ const { id: txId } = await io.increaseDelegateStake( ); ``` -#### `decreaseDelegateStake({ target, qty })` +#### `decreaseDelegateStake({ target, qty, instant })` -Decreases the callers stake on the target gateway. +Decreases the callers stake on the target gateway. Can instantly decrease stake by setting instant to `true`. _Note: Requires `signer` to be provided on `IO.init` to sign the transaction._ @@ -960,6 +1186,46 @@ const { id: txId } = await io.decreaseDelegateStake( ); ``` +Pay the early withdrawal fee and withdraw instantly. + +```typescript +const io = IO.init({ signer: new ArweaveSigner(jwk) }); +const { id: txId } = await io.decreaseDelegateStake({ + target: 't4Xr0_J4Iurt7caNST02cMotaz2FIbWQ4Kbj616RHl3', + qty: new IOToken(100).toMIO(), + instant: true, // Immediately withdraw this stake and pay the instant withdrawal fee +}); +``` + +#### `instantWithdrawal({ gatewayAddress, vaultId })` + +Instantly withdraws an existing vault on a gateway. If no `gatewayAddress` is provided, the signer's address will be used. + +_Note: Requires `signer` to be provided on `IO.init` to sign the transaction._ + +```typescript +const io = IO.init({ signer: new ArweaveSigner(jwk) }); +// removes a delegated vault from a gateway +const { id: txId } = await io.instantWithdrawal( + { + // gateway address where delegate vault exists + gatewayAddress: 't4Xr0_J4Iurt7caNST02cMotaz2FIbWQ4Kbj616RHl3', + // delegated vault id to cancel + vaultId: 'fDrr0_J4Iurt7caNST02cMotaz2FIbWQ4Kcj616RHl3', + }, + // optional additional tags + { + tags: [{ name: 'App-Name', value: 'My-Awesome-App' }], + }, +); +// removes an operator vault from a gateway +const { id: txId } = await io.instantWithdrawal( + { + vaultId: 'fDrr0_J4Iurt7caNST02cMotaz2FIbWQ4Kcj616RHl3', + }, +); +``` + #### `increaseOperatorStake({ qty })` Increases the callers operator stake. Must be executed with a wallet registered as a gateway operator. @@ -1067,24 +1333,58 @@ const { id: txId } = await io.extendLease( ); ``` -#### `cancelDelegateWithdrawal({ address, vaultId })` +#### `cancelWithdrawal({ gatewayAddress, vaultId })` -Cancels a pending delegate withdrawal. +Cancels an existing vault on a gateway. The vaulted stake will be returned to the callers stake. If no `gatewayAddress` is provided, the signer's address will be used. _Note: Requires `signer` to be provided on `IO.init` to sign the transaction._ ```typescript const io = IO.init({ signer: new ArweaveSigner(jwk) }); -const { id: txId } = await io.cancelDelegateWithdrawal( +// cancels a delegated vault from a gateway +const { id: txId } = await io.cancelWithdrawal( { // gateway address where vault exists - address: 't4Xr0_J4Iurt7caNST02cMotaz2FIbWQ4Kbj616RHl3', + gatewayAddress: 't4Xr0_J4Iurt7caNST02cMotaz2FIbWQ4Kbj616RHl3', // vault id to cancel vaultId: 'fDrr0_J4Iurt7caNST02cMotaz2FIbWQ4Kcj616RHl3', }, // optional additional tags { tags: [{ name: 'App-Name', value: 'My-Awesome-App' }] }, ); +// cancels an operator vault from a gateway +const { id: txId } = await io.cancelWithdrawal( + { + // operator vault id to cancel + vaultId: 'fDrr0_J4Iurt7caNST02cMotaz2FIbWQ4Kcj616RHl3', + }, +); +``` + +#### `submitAuctionBid({ name, type, years, processId })` + +Submit a bid for the current auction. If the bid is accepted, the name will be leased for the specified duration and assigned the specified type and processId. + +_Note: Requires `signer` to be provided on `IO.init` to sign the transaction._ + +```typescript +const io = IO.init({ signer: new ArweaveSigner(jwk) }); + +const auction = await io.getArNSAuction({ name: 'permalink' }); + +// check the current price is under some threshold +if (auction && auction.currentPrice <= new IOToken(20_000).toMIO().valueOf()) { + const { id: txId } = await io.submitAuctionBid( + { + name: 'permalink', + type: 'lease', + years: 1, + processId: 'bh9l1cy0aksiL_x9M359faGzM_yjralacHIUo8_nQXM', + }, + // optional additional tags + { tags: [{ name: 'App-Name', value: 'My-Awesome-App' }] }, + ); +} ``` ### Configuration @@ -1144,8 +1444,10 @@ const info = await ant.getInfo(); ```json { - "name": "Ardrive", + "name": "ArDrive", "ticker": "ANT-ARDRIVE", + "description": "This is the ANT for the ArDrive decentralized web app.", + "keywords": ["File-sharing", "Publishing", "dApp"], "owner": "QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ" } ``` @@ -1186,6 +1488,8 @@ const state = await ant.getState(); }, "Initialized": true, "Ticker": "ANT-AR-IO", + "Description": "A friendly description for this ANT.", + "Keywords": ["keyword1", "keyword2", "keyword3"], "Logo": "Sie_26dvgyok0PZD_-iQAFOhOd5YxDTkczOLoqTTL_A", "Denomination": 0, "Name": "AR.IO Foundation", @@ -1379,6 +1683,61 @@ const { id: txId } = await ant.setTicker( ); ``` +#### `setDescription({ description })` + +Sets the description of the ANT process. + +_Note: Requires `signer` to be provided on `ANT.init` to sign the transaction._ + +```typescript +const { id: txId } = await ant.setDescription( + { description: 'A friendly description of this ANT' }, + // optional tags + { tags: [{ name: 'App-Name', value: 'My-Awesome-App' }] }, +); +``` + +#### `setKeywords({ keywords })` + +Sets the keywords of the ANT process. + +_Note: Requires `signer` to be provided on `ANT.init` to sign the transaction._ + +```typescript +const { id: txId } = await ant.setDescription( + { keywords: ['Game', 'FPS', 'AO'] }, + // optional tags + { tags: [{ name: 'App-Name', value: 'My-Awesome-App' }] }, +); +``` + +#### `releaseName({ name, ioProcessId })` + +Releases a name from the auction and makes it available for auction on the IO contract. The name must be permanently owned by the releasing wallet. 50% of the winning bid will be distributed to the ANT owner at the time of release. If no bids, the name will be released and can be reregistered by anyone. + +_Note: Requires `signer` to be provided on `ANT.init` to sign the transaction._ + +```typescript +const { id: txId } = await ant.releaseName({ + name: 'permalink', + ioProcessId: IO_TESTNET_PROCESS_ID, // releases the name owned by the ANT and sends it to auction on the IO contract +}); +``` + +#### `reassignName({ name, ioProcessId, antProcessId })` + +Reassigns a name to a new ANT. This can only be done by the current owner of the ANT. + +_Note: Requires `signer` to be provided on `ANT.init` to sign the transaction._ + +```typescript +const { id: txId } = await ant.reassignName({ + name: 'ardrive', + ioProcessId: IO_TESTNET_PROCESS_ID, + antProcessId: NEW_ANT_PROCESS_ID, // the new ANT process id that will take over ownership of the name +}); +``` + ### Configuration ANT clients can be configured to use custom AO process. Refer to [AO Connect] for more information on how to configure the AO process to use specific AO infrastructure. @@ -1486,6 +1845,7 @@ For more information on how to use AO and AO Connect within this library, please - `yarn example:web` - opens up the example web page - `yarn example:cjs` - runs example CJS node script - `yarn example:esm` - runs example ESM node script +- `yarn example:vite` - runs example Vite web page ### Linting & Formatting diff --git a/examples/cjs/yarn.lock b/examples/cjs/yarn.lock index ca268865..3890cbda 100644 --- a/examples/cjs/yarn.lock +++ b/examples/cjs/yarn.lock @@ -796,10 +796,10 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.4: - version "6.5.5" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" - integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== +elliptic@^6.5.7: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -1317,11 +1317,11 @@ scrypt-js@3.0.1: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.1.tgz#dc2c86187d48ff2da756f0f7e96417ee03c414b1" + integrity sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.7" node-addon-api "^5.0.0" node-gyp-build "^4.2.0" diff --git a/examples/esm/index.mjs b/examples/esm/index.mjs index 7d6eead8..347b3c6f 100644 --- a/examples/esm/index.mjs +++ b/examples/esm/index.mjs @@ -20,7 +20,7 @@ import { const distributions = await arIO.getDistributions({ epochIndex: 0 }); const buyRecordCost = await arIO.getTokenCost({ intent: 'Buy-Record', - purchaseType: 'lease', + type: 'lease', name: 'ar-io-dapp-record', years: 1, }); diff --git a/examples/esm/yarn.lock b/examples/esm/yarn.lock index c4c85e13..32b96509 100644 --- a/examples/esm/yarn.lock +++ b/examples/esm/yarn.lock @@ -774,10 +774,10 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.4: - version "6.5.5" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" - integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== +elliptic@^6.5.7: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -1246,11 +1246,11 @@ scrypt-js@3.0.1: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.1.tgz#dc2c86187d48ff2da756f0f7e96417ee03c414b1" + integrity sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.7" node-addon-api "^5.0.0" node-gyp-build "^4.2.0" diff --git a/examples/vite/README.md b/examples/vite/README.md index 5bd392c5..b05aeaaa 100644 --- a/examples/vite/README.md +++ b/examples/vite/README.md @@ -2,6 +2,13 @@ This example shows how to use the `@ar.io/sdk` within a Typescript/React project using [Vite]. + + +- [Getting Started](#getting-started) +- [Polyfills](#polyfills) + + + ## Getting Started 1. Install the dependencies: @@ -18,7 +25,7 @@ yarn start 3. Open your browser and navigate to `http://localhost:3000`. You should see: -![screenshot](./public/screenshot.png) +![screenshot](./public/image.png) ## Polyfills diff --git a/examples/vite/package.json b/examples/vite/package.json index b1422ca0..0d8cb871 100644 --- a/examples/vite/package.json +++ b/examples/vite/package.json @@ -7,6 +7,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", + "recharts": "^2.13.0", "remark-gfm": "^4.0.0" }, "devDependencies": { diff --git a/examples/vite/public/image.png b/examples/vite/public/image.png new file mode 100644 index 00000000..23a210b9 Binary files /dev/null and b/examples/vite/public/image.png differ diff --git a/examples/vite/public/screenshot.png b/examples/vite/public/screenshot.png deleted file mode 100644 index d519ef4e..00000000 Binary files a/examples/vite/public/screenshot.png and /dev/null differ diff --git a/examples/vite/src/App.css b/examples/vite/src/App.css index d99bf119..ed6a79d2 100644 --- a/examples/vite/src/App.css +++ b/examples/vite/src/App.css @@ -1,6 +1,6 @@ .App { text-align: center; - background-color: #282c34; + background-color: #f0f0f0; } .markdown { diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index d931d45b..4bbc7483 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -1,33 +1,357 @@ -import { ANT } from '@ar.io/sdk/web'; +import { + ANT, + AoArNSNameDataWithName, + AoAuction, + AoAuctionPriceData, + AoGatewayWithAddress, + AoWeightedObserver, + IO, + IO_DEVNET_PROCESS_ID, + PaginationResult, +} from '@ar.io/sdk/web'; import { useEffect, useState } from 'react'; -import Markdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; +import { + Label, + Line, + LineChart, + ReferenceLine, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; import './App.css'; -const processId = 'U2dlP4DMMZFO82tWzcDfcgNGq5aGYY6CU84oeHAfNHE'; -const antContract = ANT.init({ processId }); +const io = IO.init({ processId: IO_DEVNET_PROCESS_ID }); + +type AuctionWithPrices = AoAuction & { + prices: { timestamp: string; price: number }[]; + currentPrice: number; +}; function App() { - const [contract, setContract] = useState('Loading...'); + const [auctions, setAuctions] = useState([]); + const [selectedAuction, setSelectedAuction] = + useState(null); + const [names, setNames] = useState([]); + const [gateways, setGateways] = useState([]); + const [totalGateways, setTotalGateways] = useState(0); + const [totalNames, setTotalNames] = useState(0); + const [totalAuctions, setTotalAuctions] = useState(0); + const [prescribedObservers, setPrescribedObservers] = useState< + AoWeightedObserver[] + >([]); useEffect(() => { - antContract - .getState() - .then((state: any) => { - setContract(`\`\`\`json\n${JSON.stringify(state, null, 2)}`); - }) - .catch((error: unknown) => { - console.error(error); - setContract('Error loading contract state'); - }); + // fetch first page of arns names + io.getArNSRecords({ limit: 10 }).then( + (page: PaginationResult) => { + setNames(page.items); + setTotalNames(page.totalItems); + }, + ); + + // fetch first page of gateways + io.getGateways({ limit: 10 }).then( + (page: PaginationResult) => { + setGateways(page.items); + setTotalGateways(page.totalItems); + }, + ); + + // get auction and prices for each auction + io.getArNSAuctions({ limit: 10 }).then( + (page: PaginationResult) => { + setAuctions(page.items); + setTotalAuctions(page.totalItems); + page.items.forEach((auction: AoAuction) => { + io.getArNSAuctionPrices({ + name: auction.name, + type: 'lease', + intervalMs: 1000 * 60 * 60 * 24, // 1 day + }).then((price: AoAuctionPriceData) => { + const arrayOfPrices = Object.entries(price.prices) + .sort(([timestampA], [timestampB]) => +timestampA - +timestampB) + .map(([timestamp, price]) => ({ + timestamp: new Date(+timestamp).toLocaleString('en-US', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + }), + price: price / 10 ** 6, + })); + const auctionWithPrices = { + ...auction, + prices: arrayOfPrices, + currentPrice: price.currentPrice / 10 ** 6, + }; + setSelectedAuction(auctionWithPrices); + }); + }); + }, + ); + + io.getPrescribedObservers().then((observers: AoWeightedObserver[]) => { + setPrescribedObservers(observers); + }); }, []); return ( -
- - {contract} - +
+
+
+

ArNS Names

+
+ Total Names: {totalNames} +
+
+ + + + + + + + + + + {names.map((record) => ( + + + + + + + ))} + +
Name + Process + Type + Expiry +
{record.name} + {record.processId.slice(0, 8)}... + {record.type} + {record.type === 'lease' && record.endTimestamp + ? new Date(record.endTimestamp).toLocaleDateString() + : 'N/A'} +
+
+
+ +
+ {' '} +

Active Gateways

+
+ Total Gateways: {totalGateways} +
+
+ + + + + + + + + + + {gateways.map((gateway) => ( + + + + + + + ))} + +
+ Address + + Status + + Operator Stake (IO) + + Total Delegated Stake (IO) +
+ {gateway.gatewayAddress.slice(0, 8)}... + {gateway.status} + {gateway.operatorStake / 10 ** 6} + + {gateway.totalDelegatedStake / 10 ** 6} +
+
+
+
+ {' '} +

Active Auctions

+
+ Total Auctions: {totalAuctions} +
+
+ + + + + + + + + + + + {auctions.map((auction) => ( + + + + + + + + ))} + +
Name + Starts + Ends + Base Fee + + Initiator +
{auction.name} + {new Date(auction.startTimestamp).toLocaleDateString()} + + {new Date(auction.endTimestamp).toLocaleDateString()} + + {auction.baseFee / 10 ** 6} + + {auction.initiator.slice(0, 8)}... +
+
+ {selectedAuction && ( +
+ + + + + + + + + + + + +
+ )} +
+
+

Prescribed Observers

+ + + + + + + + + + + + {prescribedObservers.map((observer) => ( + + + + + + + + ))} + +
+ Observer + Stake + Tenure Weight + + Stake Weight + + Normalized Weight +
+ {observer.gatewayAddress.slice(0, 8)}... + {observer.stake}{observer.tenureWeight}{observer.stakeWeight} + {observer.compositeWeight} +
+
+
); } diff --git a/examples/vite/src/index.css b/examples/vite/src/index.css index e8c77d29..20d9a81b 100644 --- a/examples/vite/src/index.css +++ b/examples/vite/src/index.css @@ -5,7 +5,7 @@ body { sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background-color: #282c34; + background-color: #f0f0f0; } code { diff --git a/examples/vite/yarn.lock b/examples/vite/yarn.lock index 8b1eaf2a..2978e213 100644 --- a/examples/vite/yarn.lock +++ b/examples/vite/yarn.lock @@ -199,6 +199,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.9.tgz#65884fd6dc255a775402cc1d9811082918f4bf00" + integrity sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15", "@babel/template@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" @@ -952,6 +959,57 @@ dependencies: "@babel/types" "^7.20.7" +"@types/d3-array@^3.0.3": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-ease@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-interpolate@^3.0.1": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" + integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== + +"@types/d3-scale@^4.0.2": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" + integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-shape@^3.1.0": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" + integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time@*", "@types/d3-time@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" + integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== + +"@types/d3-timer@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" @@ -1738,6 +1796,11 @@ clean-css@^5.2.2: dependencies: source-map "~0.6.0" +clsx@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1933,6 +1996,77 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-ease@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-format@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +d3-shape@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +d3-timer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + debug@^4.0.0, debug@^4.1.0, debug@^4.3.1: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -1940,6 +2074,11 @@ debug@^4.0.0, debug@^4.1.0, debug@^4.3.1: dependencies: ms "2.1.2" +decimal.js-light@^2.4.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decode-named-character-reference@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" @@ -2038,6 +2177,14 @@ dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" @@ -2123,7 +2270,7 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: +elliptic@^6.5.3, elliptic@^6.5.5: version "6.5.5" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== @@ -2136,6 +2283,19 @@ elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +elliptic@^6.5.7: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -2234,6 +2394,11 @@ estree-walker@^2.0.1, estree-walker@^2.0.2: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== +eventemitter3@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + events@3.3.0, events@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -2273,6 +2438,11 @@ fast-copy@^3.0.0: resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35" integrity sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ== +fast-equals@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d" + integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== + fast-glob@^3.2.11: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" @@ -2614,6 +2784,11 @@ internal-slot@^1.0.4: hasown "^2.0.0" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + is-alphabetical@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" @@ -3021,7 +3196,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== -lodash@^4.17.15: +lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3031,7 +3206,7 @@ longest-streak@^3.0.0: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== -loose-envify@^1.1.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -3754,6 +3929,11 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + object-inspect@^1.13.1: version "1.13.1" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" @@ -3950,6 +4130,15 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== +prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + property-information@^6.0.0: version "6.5.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" @@ -4017,12 +4206,17 @@ react-dom@^18.3.1: loose-envify "^1.1.0" scheduler "^0.23.2" +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -4048,6 +4242,25 @@ react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== +react-smooth@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a" + integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w== + dependencies: + fast-equals "^5.0.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -4084,6 +4297,27 @@ readdir-glob@^1.1.2: dependencies: minimatch "^5.1.0" +recharts-scale@^0.4.4: + version "0.4.5" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" + integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== + dependencies: + decimal.js-light "^2.4.1" + +recharts@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.13.0.tgz#a293322ea357491393cc7ad6fcbb1e5f8e99bc93" + integrity sha512-sbfxjWQ+oLWSZEWmvbq/DFVdeRLqqA6d0CDjKx2PkxVVdoXo16jvENCE+u/x7HxOO+/fwx//nYRwb8p8X6s/lQ== + dependencies: + clsx "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.21" + react-is "^18.3.1" + react-smooth "^4.0.0" + recharts-scale "^0.4.4" + tiny-invariant "^1.3.1" + victory-vendor "^36.6.8" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -4265,11 +4499,11 @@ scrypt-js@3.0.1: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.1.tgz#dc2c86187d48ff2da756f0f7e96417ee03c414b1" + integrity sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.7" node-addon-api "^5.0.0" node-gyp-build "^4.2.0" @@ -4471,6 +4705,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-invariant@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tmp-promise@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" @@ -4667,6 +4906,26 @@ vfile@^6.0.0: unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" +victory-vendor@^36.6.8: + version "36.9.2" + resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801" + integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ== + dependencies: + "@types/d3-array" "^3.0.3" + "@types/d3-ease" "^3.0.0" + "@types/d3-interpolate" "^3.0.1" + "@types/d3-scale" "^4.0.2" + "@types/d3-shape" "^3.1.0" + "@types/d3-time" "^3.0.0" + "@types/d3-timer" "^3.0.0" + d3-array "^3.1.6" + d3-ease "^3.0.1" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-shape "^3.1.0" + d3-time "^3.0.0" + d3-timer "^3.0.1" + vite-plugin-html@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/vite-plugin-html/-/vite-plugin-html-3.2.2.tgz#661834fa09015d3fda48ba694dbaa809396f5f7a" diff --git a/examples/webpack/yarn.lock b/examples/webpack/yarn.lock index 4c56f6ac..e247bb2a 100644 --- a/examples/webpack/yarn.lock +++ b/examples/webpack/yarn.lock @@ -3374,7 +3374,7 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: +elliptic@^6.5.3, elliptic@^6.5.5: version "6.5.5" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== @@ -3387,6 +3387,19 @@ elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +elliptic@^6.5.7: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -6538,11 +6551,11 @@ scrypt-js@3.0.1: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.1.tgz#dc2c86187d48ff2da756f0f7e96417ee03c414b1" + integrity sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.7" node-addon-api "^5.0.0" node-gyp-build "^4.2.0" diff --git a/package.json b/package.json index beb4ad34..b85459f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ar.io/sdk", - "version": "2.3.2", + "version": "2.4.0-alpha.16", "repository": { "type": "git", "url": "git+https://github.com/ar-io/ar-io-sdk.git" @@ -66,16 +66,18 @@ "format": "prettier --check .", "format:fix": "prettier --write .", "test": "yarn test:unit && yarn test:e2e", - "test:cjs": "yarn test:link && cd ./tests/e2e/cjs && yarn && yarn test", - "test:esm": "yarn test:link && cd ./tests/e2e/esm && yarn && yarn test", - "test:web": "yarn test:link && cd ./tests/e2e/web && yarn && yarn test", + "test:cjs": "yarn build:cjs && yarn link && cd ./tests/e2e/cjs && yarn && yarn test", + "test:esm": "yarn build:esm && yarn link && cd ./tests/e2e/esm && yarn && yarn test", + "test:web": "yarn build:esm && yarn link && cd ./tests/e2e/web && yarn && yarn test", "test:unit": "NODE_OPTIONS=\"--import=./register.mjs\" node --test tests/unit/**.test.ts", "test:link": "yarn build && yarn link", "test:e2e": "yarn test:cjs && yarn test:esm && yarn test:web", "prepare": "husky install", + "docs:update": "markdown-toc-gen insert README.md", "example:esm": "cd examples/esm && yarn && node index.mjs", - "example:cjs": "yarn test:link && cd examples/cjs && yarn && node index.cjs", - "example:web": "yarn test:link && build:web && http-server --port 8080 --host -o examples/web" + "example:cjs": "yarn build:cjs && yarn link && cd examples/cjs && yarn && node index.cjs", + "example:web": "yarn build:web && http-server --port 8080 --host -o examples/web", + "example:vite": "yarn build:esm && yarn link && cd examples/vite && yarn && yarn start" }, "devDependencies": { "@commitlint/cli": "^17.1.2", @@ -107,6 +109,7 @@ "husky": "^8.0.3", "lint-staged": "^15.2.2", "markdown-toc-gen": "^1.0.1", + "nock": "^13.5.5", "prettier": "^3.0.2", "rimraf": "^5.0.1", "semantic-release": "^21.0.7", diff --git a/src/common/ant-registry.ts b/src/common/ant-registry.ts index adb3b51e..eeb85a54 100644 --- a/src/common/ant-registry.ts +++ b/src/common/ant-registry.ts @@ -17,6 +17,8 @@ import { ANT_REGISTRY_ID } from '../constants.js'; import { AoANTRegistryRead, AoANTRegistryWrite, +} from '../types/ant-registry.js'; +import { AoMessageResult, AoSigner, OptionalSigner, @@ -24,7 +26,7 @@ import { WithSigner, isProcessConfiguration, isProcessIdConfiguration, -} from '../types.js'; +} from '../types/index.js'; import { createAoSigner } from '../utils/ao.js'; import { AOProcess, InvalidContractConfigurationError } from './index.js'; diff --git a/src/common/ant.ts b/src/common/ant.ts index 74f97056..f715e30f 100644 --- a/src/common/ant.ts +++ b/src/common/ant.ts @@ -13,12 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { z } from 'zod'; + import { + AntBalancesSchema, + AntControllersSchema, + AntInfoSchema, + AntReadOptions, + AntRecordSchema, + AntRecordsSchema, + AntStateSchema, AoANTInfo, AoANTRead, AoANTRecord, AoANTState, AoANTWrite, +} from '../types/ant.js'; +import { AoMessageResult, AoSigner, OptionalSigner, @@ -28,28 +39,37 @@ import { WriteOptions, isProcessConfiguration, isProcessIdConfiguration, -} from '../types.js'; +} from '../types/index.js'; import { createAoSigner } from '../utils/ao.js'; +import { parseSchemaResult } from '../utils/schema.js'; import { AOProcess, InvalidContractConfigurationError } from './index.js'; export class ANT { static init( - config: Required & { signer?: undefined }, + config: Required & { + signer?: undefined; + strict?: boolean; + }, ): AoANTRead; static init({ signer, ...config - }: WithSigner>): AoANTWrite; + }: WithSigner> & { + strict?: boolean; + }): AoANTWrite; static init({ signer, + strict = false, ...config - }: OptionalSigner>): AoANTRead | AoANTWrite { + }: OptionalSigner> & { strict?: boolean }): + | AoANTRead + | AoANTWrite { // ao supported implementation if (isProcessConfiguration(config) || isProcessIdConfiguration(config)) { if (!signer) { - return new AoANTReadable(config); + return new AoANTReadable({ strict, ...config }); } - return new AoANTWriteable({ signer, ...config }); + return new AoANTWriteable({ signer, strict, ...config }); } throw new InvalidContractConfigurationError(); @@ -58,8 +78,10 @@ export class ANT { export class AoANTReadable implements AoANTRead { protected process: AOProcess; + private strict: boolean; - constructor(config: Required) { + constructor(config: Required & { strict?: boolean }) { + this.strict = config.strict || false; if (isProcessConfiguration(config)) { this.process = config.process; } else if (isProcessIdConfiguration(config)) { @@ -71,19 +93,37 @@ export class AoANTReadable implements AoANTRead { } } - async getState(): Promise { + async getState( + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise { const tags = [{ name: 'Action', value: 'State' }]; const res = await this.process.read({ tags, }); + if (strict) { + parseSchemaResult( + AntStateSchema.passthrough().and( + z.object({ + Records: z.record(z.string(), AntRecordSchema.passthrough()), + }), + ), + res, + ); + } + return res; } - async getInfo(): Promise { + async getInfo( + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise { const tags = [{ name: 'Action', value: 'Info' }]; const info = await this.process.read({ tags, }); + if (strict) { + parseSchemaResult(AntInfoSchema.passthrough(), info); + } return info; } @@ -96,7 +136,10 @@ export class AoANTReadable implements AoANTRead { * ant.getRecord({ undername: "john" }); * ``` */ - async getRecord({ undername }: { undername: string }): Promise { + async getRecord( + { undername }: { undername: string }, + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise { const tags = [ { name: 'Sub-Domain', value: undername }, { name: 'Action', value: 'Record' }, @@ -105,6 +148,8 @@ export class AoANTReadable implements AoANTRead { const record = await this.process.read({ tags, }); + if (strict) parseSchemaResult(AntRecordSchema.passthrough(), record); + return record; } @@ -116,11 +161,14 @@ export class AoANTReadable implements AoANTRead { * ant.getRecords(); * ```` */ - async getRecords(): Promise> { + async getRecords( + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise> { const tags = [{ name: 'Action', value: 'Records' }]; const records = await this.process.read>({ tags, }); + if (strict) parseSchemaResult(AntRecordsSchema, records); return records; } @@ -132,8 +180,10 @@ export class AoANTReadable implements AoANTRead { * ant.getOwner(); * ``` */ - async getOwner(): Promise { - const info = await this.getInfo(); + async getOwner( + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise { + const info = await this.getInfo({ strict }); return info.Owner; } @@ -145,11 +195,14 @@ export class AoANTReadable implements AoANTRead { * ant.getControllers(); * ``` */ - async getControllers(): Promise { + async getControllers( + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise { const tags = [{ name: 'Action', value: 'Controllers' }]; const controllers = await this.process.read({ tags, }); + if (strict) parseSchemaResult(AntControllersSchema, controllers); return controllers; } @@ -161,8 +214,10 @@ export class AoANTReadable implements AoANTRead { * ant.getName(); * ``` */ - async getName(): Promise { - const info = await this.getInfo(); + async getName( + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise { + const info = await this.getInfo({ strict }); return info.Name; } @@ -174,8 +229,10 @@ export class AoANTReadable implements AoANTRead { * ant.getTicker(); * ``` */ - async getTicker(): Promise { - const info = await this.getInfo(); + async getTicker( + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise { + const info = await this.getInfo({ strict }); return info.Ticker; } @@ -187,11 +244,14 @@ export class AoANTReadable implements AoANTRead { * ant.getBalances(); * ``` */ - async getBalances(): Promise> { + async getBalances( + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise> { const tags = [{ name: 'Action', value: 'Balances' }]; const balances = await this.process.read>({ tags, }); + if (strict) parseSchemaResult(AntBalancesSchema, balances); return balances; } @@ -204,7 +264,10 @@ export class AoANTReadable implements AoANTRead { * ant.getBalance({ address }); * ``` */ - async getBalance({ address }: { address: string }): Promise { + async getBalance( + { address }: { address: string }, + { strict }: AntReadOptions = { strict: this.strict }, + ): Promise { const tags = [ { name: 'Action', value: 'Balance' }, { name: 'Recipient', value: address }, @@ -212,6 +275,7 @@ export class AoANTReadable implements AoANTRead { const balance = await this.process.read({ tags, }); + if (strict) parseSchemaResult(z.number(), balance); return balance; } } @@ -222,7 +286,7 @@ export class AoANTWriteable extends AoANTReadable implements AoANTWrite { constructor({ signer, ...config - }: WithSigner>) { + }: WithSigner> & { strict?: boolean }) { super(config); this.signer = createAoSigner(signer); } @@ -393,7 +457,7 @@ export class AoANTWriteable extends AoANTReadable implements AoANTWrite { * @returns {Promise} The result of the interaction. * @example * ```ts - * ant.setName({ name: "ships at sea" }); + * ant.setName({ name: "test" }); // results in the resolution of `test_.ar.io` * ``` */ async setName( @@ -409,4 +473,103 @@ export class AoANTWriteable extends AoANTReadable implements AoANTWrite { signer: this.signer, }); } + + /** + * @param description @type {string} Sets the ANT Description. + * @returns {Promise} The result of the interaction. + * @example + * ```ts + * ant.setDescription({ description: "This name is used for the ArDrive" }); + * ``` + */ + async setDescription( + { description }: { description: string }, + options?: WriteOptions, + ): Promise { + return this.process.send({ + tags: [ + ...(options?.tags ?? []), + { name: 'Action', value: 'Set-Description' }, + { name: 'Description', value: description }, + ], + signer: this.signer, + }); + } + + /** + * @param keywords @type {string[]} Sets the ANT Keywords. + * @returns {Promise} The result of the interaction. + * @example + * ```ts + * ant.setKeywords({ keywords: ['keyword1', 'keyword2', 'keyword3']}); + * ``` + */ + async setKeywords( + { keywords }: { keywords: string[] }, + options?: WriteOptions, + ): Promise { + return this.process.send({ + tags: [ + ...(options?.tags ?? []), + { name: 'Action', value: 'Set-Keywords' }, + { name: 'Description', value: JSON.stringify(keywords) }, + ], + signer: this.signer, + }); + } + + /** + * @param name @type {string} The name you want to release. The name will be put up for auction on the IO contract. 50% of the winning bid will be distributed to the ANT owner at the time of release. If no bids, the name will be released and can be reregistered by anyone. + * @param ioProcessId @type {string} The processId of the IO contract. This is where the ANT will send the message to release the name. + * @returns {Promise} The result of the interaction. + * @example + * ```ts + * ant.releaseName({ name: "ardrive", ioProcessId: IO_TESTNET_PROCESS_ID }); + * ``` + */ + async releaseName( + { name, ioProcessId }: { name: string; ioProcessId: string }, + options?: WriteOptions, + ): Promise { + return this.process.send({ + tags: [ + ...(options?.tags ?? []), + { name: 'Action', value: 'Release-Name' }, + { name: 'Name', value: name }, + { name: 'IO-Process-Id', value: ioProcessId }, + ], + signer: this.signer, + }); + } + + /** + * Sends a message to the IO contract to reassign the name to a new ANT. This can only be done by the current owner of the ANT. + * @param name @type {string} The name you want to reassign. + * @param ioProcessId @type {string} The processId of the IO contract. + * @param antProcessId @type {string} The processId of the ANT contract. + * @returns {Promise} The result of the interaction. + * @example + * ```ts + * ant.reassignName({ name: "ardrive", ioProcessId: IO_TESTNET_PROCESS_ID, antProcessId: NEW_ANT_PROCESS_ID }); + * ``` + */ + async reassignName( + { + name, + ioProcessId, + antProcessId, + }: { name: string; ioProcessId: string; antProcessId: string }, + options?: WriteOptions, + ): Promise { + return this.process.send({ + tags: [ + ...(options?.tags ?? []), + { name: 'Action', value: 'Reassign-Name' }, + { name: 'Name', value: name }, + { name: 'IO-Process-Id', value: ioProcessId }, + { name: 'Process-Id', value: antProcessId }, + ], + signer: this.signer, + }); + } } diff --git a/src/common/contracts/ao-process.ts b/src/common/contracts/ao-process.ts index 2de06a03..ca80de60 100644 --- a/src/common/contracts/ao-process.ts +++ b/src/common/contracts/ao-process.ts @@ -15,7 +15,7 @@ */ import { connect } from '@permaweb/aoconnect'; -import { AOContract, AoClient, AoSigner } from '../../types.js'; +import { AOContract, AoClient, AoSigner } from '../../types/index.js'; import { safeDecode } from '../../utils/json.js'; import { version } from '../../version.js'; import { WriteInteractionError } from '../error.js'; @@ -59,25 +59,33 @@ export class AOProcess implements AOContract { process: this.processId, tags, }); + this.logger.debug(`Read interaction result`, { + result, + }); if (result.Messages === undefined || result.Messages.length === 0) { + this.logger.debug( + `Process ${this.processId} does not support provided action.`, + result, + tags, + ); throw new Error( `Process ${this.processId} does not support provided action.`, ); } const tagsOutput = result.Messages[0].Tags; + const messageData = result.Messages[0].Data; + const error = tagsOutput.find((tag) => tag.name === 'Error'); if (error) { - throw new Error(`${error.Value}: ${result.Messages[0].Data}`); + throw new Error( + `${error.value}${messageData ? `: ${messageData}` : ''}`, + ); } - this.logger.debug(`Read interaction result`, { - result: result.Messages[0].Data, - }); - // return empty object if no data is returned - if (result.Messages[0].Data === undefined) { + if (messageData === undefined) { return {} as K; } @@ -90,6 +98,7 @@ export class AOProcess implements AOContract { tags, }); lastError = e; + // exponential backoff await new Promise((resolve) => setTimeout(resolve, 2 ** attempts * 1000), diff --git a/src/common/http.ts b/src/common/http.ts index 943de9c0..e79dfadb 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -15,8 +15,8 @@ */ import { AxiosInstance } from 'axios'; -import { HTTPClient } from '../types.js'; -import { createAxiosInstance } from '../utils/index.js'; +import { HTTPClient } from '../types/index.js'; +import { createAxiosInstance } from '../utils/http-client.js'; import { FailedRequestError, NotFound, UnknownError } from './error.js'; import { ILogger, Logger } from './logger.js'; diff --git a/src/common/io.ts b/src/common/io.ts index 016b94fc..24b31c68 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -16,22 +16,10 @@ import Arweave from 'arweave'; import { IO_TESTNET_PROCESS_ID } from '../constants.js'; -import { - AoArNSNameData, - AoEpochData, - AoEpochSettings, - AoGateway, - AoIORead, - AoIOWrite, - AoRegistrationFees, - EpochInput, - isProcessConfiguration, - isProcessIdConfiguration, -} from '../io.js'; -import { AoSigner, mIOToken } from '../token.js'; import { AoArNSNameDataWithName, AoArNSReservedNameData, + AoAuction, AoBalanceWithAddress, AoEpochDistributionData, AoEpochObservationData, @@ -50,8 +38,26 @@ import { WalletAddress, WithSigner, WriteOptions, -} from '../types.js'; +} from '../types/index.js'; +import { + AoArNSNameData, + AoAuctionPriceData, + AoEpochData, + AoEpochSettings, + AoGateway, + AoGatewayDelegateWithAddress, + AoIORead, + AoIOWrite, + AoRegistrationFees, + AoVaultData, + AoWalletVault, + EpochInput, + isProcessConfiguration, + isProcessIdConfiguration, +} from '../types/io.js'; +import { AoSigner, mIOToken } from '../types/token.js'; import { createAoSigner } from '../utils/ao.js'; +import { getCurrentBlockUnixTimestampMs, pruneTags } from '../utils/arweave.js'; import { defaultArweave } from './arweave.js'; import { AOProcess } from './contracts/ao-process.js'; import { InvalidContractConfigurationError } from './error.js'; @@ -145,16 +151,7 @@ export class IOReadable implements AoIORead { name: 'Timestamp', value: (params as { timestamp?: number })?.timestamp?.toString() ?? - ( - await this.arweave.blocks - .getCurrent() - .then((block) => { - return { timestamp: block.timestamp * 1000 }; - }) - .catch(() => { - return { timestamp: Date.now() }; // fallback to current time - }) - ).timestamp.toString(), + (await getCurrentBlockUnixTimestampMs(this.arweave)).toString(), }, { name: 'Epoch-Index', @@ -162,15 +159,8 @@ export class IOReadable implements AoIORead { }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read({ - tags: prunedTags, + tags: pruneTags(allTags), }); } async getEpoch(epoch?: EpochInput): Promise { @@ -180,16 +170,7 @@ export class IOReadable implements AoIORead { name: 'Timestamp', value: (epoch as { timestamp?: number })?.timestamp?.toString() ?? - ( - await this.arweave.blocks - .getCurrent() - .then((block) => { - return { timestamp: block.timestamp * 1000 }; - }) - .catch(() => { - return { timestamp: Date.now() }; // fallback to current time - }) - ).timestamp.toString(), + (await getCurrentBlockUnixTimestampMs(this.arweave)).toString(), }, { name: 'Epoch-Index', @@ -197,15 +178,8 @@ export class IOReadable implements AoIORead { }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read({ - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -223,25 +197,18 @@ export class IOReadable implements AoIORead { } async getArNSRecords( - pageParams?: PaginationParams, + params?: PaginationParams, ): Promise> { const allTags = [ { name: 'Action', value: 'Paginated-Records' }, - { name: 'Cursor', value: pageParams?.cursor?.toString() }, - { name: 'Limit', value: pageParams?.limit?.toString() }, - { name: 'Sort-By', value: pageParams?.sortBy }, - { name: 'Sort-Order', value: pageParams?.sortOrder }, + { name: 'Cursor', value: params?.cursor?.toString() }, + { name: 'Limit', value: params?.limit?.toString() }, + { name: 'Sort-By', value: params?.sortBy }, + { name: 'Sort-Order', value: params?.sortOrder }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read>({ - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -276,25 +243,50 @@ export class IOReadable implements AoIORead { } async getBalances( - pageParams?: PaginationParams, + params?: PaginationParams, ): Promise> { const allTags = [ { name: 'Action', value: 'Paginated-Balances' }, - { name: 'Cursor', value: pageParams?.cursor?.toString() }, - { name: 'Limit', value: pageParams?.limit?.toString() }, - { name: 'Sort-By', value: pageParams?.sortBy }, - { name: 'Sort-Order', value: pageParams?.sortOrder }, + { name: 'Cursor', value: params?.cursor?.toString() }, + { name: 'Limit', value: params?.limit?.toString() }, + { name: 'Sort-By', value: params?.sortBy }, + { name: 'Sort-Order', value: params?.sortOrder }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read>({ - tags: prunedTags, + tags: pruneTags(allTags), + }); + } + + async getVault({ + address, + vaultId, + }: { + address: WalletAddress; + vaultId: string; + }): Promise { + return this.process.read({ + tags: [ + { name: 'Action', value: 'Vault' }, + { name: 'Address', value: address }, + { name: 'Vault-Id', value: vaultId }, + ], + }); + } + + async getVaults( + params?: PaginationParams, + ): Promise> { + const allTags = [ + { name: 'Action', value: 'Paginated-Vaults' }, + { name: 'Cursor', value: params?.cursor?.toString() }, + { name: 'Limit', value: params?.limit?.toString() }, + { name: 'Sort-By', value: params?.sortBy }, + { name: 'Sort-Order', value: params?.sortOrder }, + ]; + + return this.process.read>({ + tags: pruneTags(allTags), }); } @@ -311,8 +303,48 @@ export class IOReadable implements AoIORead { }); } + async getGatewayDelegates({ + address, + ...pageParams + }): Promise> { + const allTags = [ + { name: 'Action', value: 'Paginated-Delegates' }, + { name: 'Address', value: address }, + { name: 'Cursor', value: pageParams?.cursor?.toString() }, + { name: 'Limit', value: pageParams?.limit?.toString() }, + { name: 'Sort-By', value: pageParams?.sortBy }, + { name: 'Sort-Order', value: pageParams?.sortOrder }, + ]; + + return this.process.read>({ + tags: pruneTags(allTags), + }); + } + + async getGatewayDelegateAllowList({ + address, + ...pageParams + }: { + address: WalletAddress; + } & PaginationParams): Promise< + PaginationResult + > { + const allTags = [ + { name: 'Action', value: 'Paginated-Allowed-Delegates' }, + { name: 'Address', value: address }, + { name: 'Cursor', value: pageParams?.cursor?.toString() }, + { name: 'Limit', value: pageParams?.limit?.toString() }, + { name: 'Sort-Order', value: pageParams?.sortOrder }, + // note: sortBy is omitted because it's not supported for this action as table is an of addresses + ]; + + return this.process.read>({ + tags: pruneTags(allTags), + }); + } + async getGateways( - pageParams?: PaginationParams, + pageParams?: PaginationParams, ): Promise> { const allTags = [ { name: 'Action', value: 'Paginated-Gateways' }, @@ -322,15 +354,8 @@ export class IOReadable implements AoIORead { { name: 'Sort-Order', value: pageParams?.sortOrder }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read>({ - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -364,16 +389,7 @@ export class IOReadable implements AoIORead { name: 'Timestamp', value: (epoch as { timestamp?: number })?.timestamp?.toString() ?? - ( - await this.arweave.blocks - .getCurrent() - .then((block) => { - return { timestamp: block.timestamp * 1000 }; - }) - .catch(() => { - return { timestamp: Date.now() }; // fallback to current time - }) - ).timestamp.toString(), + (await getCurrentBlockUnixTimestampMs(this.arweave)).toString(), }, { name: 'Epoch-Index', @@ -381,15 +397,8 @@ export class IOReadable implements AoIORead { }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read({ - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -400,16 +409,7 @@ export class IOReadable implements AoIORead { name: 'Timestamp', value: (epoch as { timestamp?: number })?.timestamp?.toString() ?? - ( - await this.arweave.blocks - .getCurrent() - .then((block) => { - return { timestamp: block.timestamp * 1000 }; - }) - .catch(() => { - return { timestamp: Date.now() }; // fallback to current time - }) - ).timestamp.toString(), + (await getCurrentBlockUnixTimestampMs(this.arweave)).toString(), }, { name: 'Epoch-Index', @@ -417,15 +417,8 @@ export class IOReadable implements AoIORead { }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read({ - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -453,15 +446,8 @@ export class IOReadable implements AoIORead { }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read({ - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -472,16 +458,7 @@ export class IOReadable implements AoIORead { name: 'Timestamp', value: (epoch as { timestamp?: number })?.timestamp?.toString() ?? - ( - await this.arweave.blocks - .getCurrent() - .then((block) => { - return { timestamp: block.timestamp * 1000 }; - }) - .catch(() => { - return { timestamp: Date.now() }; // fallback to current time - }) - ).timestamp.toString(), + (await getCurrentBlockUnixTimestampMs(this.arweave)).toString(), }, { name: 'Epoch-Index', @@ -489,21 +466,14 @@ export class IOReadable implements AoIORead { }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read({ - tags: prunedTags, + tags: pruneTags(allTags), }); } async getTokenCost(params: { intent: 'Buy-Record'; - purchaseType: 'permabuy' | 'lease'; + type: 'permabuy' | 'lease'; years: number; name: string; }): Promise; @@ -519,13 +489,13 @@ export class IOReadable implements AoIORead { }): Promise; async getTokenCost({ intent, - purchaseType, + type, years, name, quantity, }: { intent: 'Buy-Record' | 'Extend-Lease' | 'Increase-Undername-Limit'; - purchaseType?: 'permabuy' | 'lease'; + type?: 'permabuy' | 'lease'; years?: number; name?: string; quantity?: number; @@ -550,7 +520,7 @@ export class IOReadable implements AoIORead { }, { name: 'Purchase-Type', - value: purchaseType, + value: type, }, { name: 'Timestamp', @@ -567,15 +537,8 @@ export class IOReadable implements AoIORead { }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.read({ - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -590,6 +553,90 @@ export class IOReadable implements AoIORead { tags: [{ name: 'Action', value: 'Demand-Factor' }], }); } + + // Auctions + async getArNSAuctions( + params?: PaginationParams, + ): Promise> { + const allTags = [ + { name: 'Action', value: 'Auctions' }, + { name: 'Cursor', value: params?.cursor?.toString() }, + { name: 'Limit', value: params?.limit?.toString() }, + { name: 'Sort-By', value: params?.sortBy }, + { name: 'Sort-Order', value: params?.sortOrder }, + ]; + + return this.process.read>({ + tags: pruneTags(allTags), + }); + } + + async getArNSAuction({ + name, + }: { + name: string; + }): Promise { + const allTags = [ + { name: 'Action', value: 'Auction-Info' }, + { name: 'Name', value: name }, + ]; + + return this.process.read({ + tags: allTags, + }); + } + + /** + * Get auction prices for a given auction at the provided intervals + * + * @param {Object} params - The parameters for fetching auction prices + * @param {string} params.name - The name of the auction + * @param {('permabuy'|'lease')} [params.type='lease'] - The type of purchase + * @param {number} [params.years=1] - The number of years for lease (only applicable if type is 'lease') + * @param {number} [params.timestamp=Date.now()] - The timestamp to fetch prices for + * @param {number} [params.intervalMs=900000] - The interval in milliseconds between price points (default is 15 minutes) + * @returns {Promise} The auction price data + */ + async getArNSAuctionPrices({ + name, + type, + years, + timestamp, + intervalMs, + }: { + name: string; + type?: 'permabuy' | 'lease'; + years?: number; + timestamp?: number; + intervalMs?: number; + }): Promise { + const prunedPriceTags: { name: string; value: string }[] = [ + { name: 'Action', value: 'Auction-Prices' }, + { name: 'Name', value: name }, + { + name: 'Timestamp', + value: timestamp?.toString() ?? Date.now().toString(), + }, + { name: 'Purchase-Type', value: type ?? 'lease' }, + { + name: 'Years', + value: + type == undefined || type === 'lease' + ? years?.toString() ?? '1' + : undefined, + }, + { + name: 'Price-Interval-Ms', + value: intervalMs?.toString() ?? '900000', + }, + ].filter( + (tag): tag is { name: string; value: string } => tag.value !== undefined, + ); + + return this.process.read({ + tags: prunedPriceTags, + }); + } } export class IOWriteable extends IOReadable implements AoIOWrite { @@ -658,6 +705,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { { operatorStake, allowDelegatedStaking, + allowedDelegates, delegateRewardShareRatio, fqdn, label, @@ -683,6 +731,10 @@ export class IOWriteable extends IOReadable implements AoIOWrite { name: 'Allow-Delegated-Staking', value: allowDelegatedStaking?.toString(), }, + { + name: 'Allowed-Delegates', + value: allowedDelegates?.join(','), + }, { name: 'Delegate-Reward-Share-Ratio', value: delegateRewardShareRatio?.toString(), @@ -725,16 +777,9 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.send({ signer: this.signer, - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -749,6 +794,7 @@ export class IOWriteable extends IOReadable implements AoIOWrite { async updateGatewaySettings( { allowDelegatedStaking, + allowedDelegates, delegateRewardShareRatio, fqdn, label, @@ -777,6 +823,10 @@ export class IOWriteable extends IOReadable implements AoIOWrite { name: 'Allow-Delegated-Staking', value: allowDelegatedStaking?.toString(), }, + { + name: 'Allowed-Delegates', + value: allowedDelegates?.join(','), + }, { name: 'Delegate-Reward-Share-Ratio', value: delegateRewardShareRatio?.toString(), @@ -788,16 +838,9 @@ export class IOWriteable extends IOReadable implements AoIOWrite { { name: 'Auto-Stake', value: autoStake?.toString() }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); - return this.process.send({ signer: this.signer, - tags: prunedTags, + tags: pruneTags(allTags), }); } @@ -824,10 +867,12 @@ export class IOWriteable extends IOReadable implements AoIOWrite { params: { target: string; decreaseQty: number | mIOToken; + instant?: boolean; }, options?: WriteOptions, ): Promise { const { tags = [] } = options || {}; + return this.process.send({ signer: this.signer, tags: [ @@ -835,10 +880,41 @@ export class IOWriteable extends IOReadable implements AoIOWrite { { name: 'Action', value: 'Decrease-Delegate-Stake' }, { name: 'Target', value: params.target }, { name: 'Quantity', value: params.decreaseQty.valueOf().toString() }, + { name: 'Instant', value: `${params.instant || false}` }, ], }); } + /** + * Initiates an instant withdrawal from a gateway. + * + * @param {Object} params - The parameters for initiating an instant withdrawal + * @param {string} params.address - The gateway address of the withdrawal, if not provided, the signer's address will be used + * @param {string} params.vaultId - The vault ID of the withdrawal + * @returns {Promise} The result of the withdrawal + */ + async instantWithdrawal( + params: { + gatewayAddress?: string; + vaultId: string; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; + + const allTags = [ + ...tags, + { name: 'Action', value: 'Instant-Withdrawal' }, + { name: 'Vault-Id', value: params.vaultId }, + { name: 'Address', value: params.gatewayAddress }, + ]; + + return this.process.send({ + signer: this.signer, + tags: pruneTags(allTags), + }); + } + async increaseOperatorStake( params: { increaseQty: number | mIOToken; @@ -917,19 +993,46 @@ export class IOWriteable extends IOReadable implements AoIOWrite { { name: 'Purchase-Type', value: params.type || 'lease' }, ]; - const prunedTags: { name: string; value: string }[] = allTags.filter( - (tag: { - name: string; - value: string | undefined; - }): tag is { name: string; value: string } => tag.value !== undefined, - ); + return this.process.send({ + signer: this.signer, + tags: pruneTags(allTags), + }); + } + /** + * Upgrades an existing leased record to a permabuy. + * + * @param {Object} params - The parameters for upgrading a record + * @param {string} params.name - The name of the record to upgrade + * @param {Object} [options] - The options for the upgrade + * @returns {Promise} The result of the upgrade + */ + async upgradeRecord( + params: { + name: string; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; return this.process.send({ signer: this.signer, - tags: prunedTags, + tags: [ + ...tags, + { name: 'Action', value: 'Upgrade-Name' }, // TODO: align on Update-Record vs. Upgrade-Name (contract currently uses Upgrade-Name) + { name: 'Name', value: params.name }, + ], }); } + /** + * Extends the lease of an existing leased record. + * + * @param {Object} params - The parameters for extending a lease + * @param {string} params.name - The name of the record to extend + * @param {number} params.years - The number of years to extend the lease + * @param {Object} [options] - The options for the extension + * @returns {Promise} The result of the extension + */ async extendLease( params: { name: string; @@ -968,19 +1071,58 @@ export class IOWriteable extends IOReadable implements AoIOWrite { }); } - async cancelDelegateWithdrawal( - params: { address: string; vaultId: string }, + /** + * Cancel a withdrawal from a gateway. + * + * @param {Object} params - The parameters for cancelling a withdrawal + * @param {string} [params.address] - The address of the withdrawal (optional). If not provided, the signer's address will be used. + * @param {string} params.vaultId - The vault ID of the withdrawal. + * @param {Object} [options] - The options for the cancellation + * @returns {Promise} The result of the cancellation + */ + async cancelWithdrawal( + params: { gatewayAddress?: WalletAddress; vaultId: string }, options?: WriteOptions | undefined, ): Promise { const { tags = [] } = options || {}; + + const allTags = [ + ...tags, + { name: 'Action', value: 'Cancel-Withdrawal' }, + { name: 'Vault-Id', value: params.vaultId }, + { name: 'Address', value: params.gatewayAddress }, + ]; + return this.process.send({ signer: this.signer, - tags: [ - ...tags, - { name: 'Action', value: 'Cancel-Delegate-Withdrawal' }, - { name: 'Address', value: params.address }, - { name: 'Vault-Id', value: params.vaultId }, - ], + tags: pruneTags(allTags), + }); + } + + async submitAuctionBid( + params: { + name: string; + processId: string; + quantity?: number; + type?: 'lease' | 'permabuy'; + years?: number; + }, + options?: WriteOptions, + ): Promise { + const { tags = [] } = options || {}; + const allTags = [ + ...tags, + { name: 'Action', value: 'Auction-Bid' }, + { name: 'Name', value: params.name }, + { name: 'Process-Id', value: params.processId }, + { name: 'Quantity', value: params.quantity?.toString() ?? undefined }, + { name: 'Purchase-Type', value: params.type || 'lease' }, + { name: 'Years', value: params.years?.toString() ?? undefined }, + ]; + + return this.process.send({ + signer: this.signer, + tags: pruneTags(allTags), }); } } diff --git a/src/node/index.ts b/src/node/index.ts index a40d0e6c..cd943831 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -15,7 +15,7 @@ */ export { ArweaveSigner, ArconnectSigner } from '@dha-team/arbundles'; -export * from '../types.js'; +export * from '../types/index.js'; export * from '../common/index.js'; export * from '../constants.js'; export * from '../utils/index.js'; diff --git a/src/types/ant-registry.ts b/src/types/ant-registry.ts new file mode 100644 index 00000000..781bd5b3 --- /dev/null +++ b/src/types/ant-registry.ts @@ -0,0 +1,26 @@ +/** + * Copyright (C) 2022-2024 Permanent Data Solutions, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { AoMessageResult } from './common.js'; + +export interface AoANTRegistryRead { + accessControlList(params: { + address: string; + }): Promise<{ Owned: string[]; Controlled: string[] }>; +} + +export interface AoANTRegistryWrite extends AoANTRegistryRead { + register(params: { processId: string }): Promise; +} diff --git a/src/types/ant.ts b/src/types/ant.ts new file mode 100644 index 00000000..a2e607ab --- /dev/null +++ b/src/types/ant.ts @@ -0,0 +1,260 @@ +/** + * Copyright (C) 2022-2024 Permanent Data Solutions, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { z } from 'zod'; + +import { ARWEAVE_TX_REGEX } from '../constants.js'; +import { AoMessageResult, WalletAddress, WriteOptions } from './common.js'; + +/** + * example error: + * { + "code": "custom", + "message": "Must be an Arweave Transaction ID", + "path": [ + "Records", + "record1", + "transactionId" + ] + }, + */ +export const ArweaveTxIdSchema = z + .string({ + description: 'Arweave Transaction ID', + }) + .refine((val) => ARWEAVE_TX_REGEX.test(val), { + message: 'Must be an Arweave Transaction ID', + }); + +export const IntegerStringSchema = z + .string({ + description: 'Integer String', + }) + .refine( + (val) => { + const num = parseInt(val); + return Number.isInteger(num) && num >= 0; + }, + { message: 'Must be a non negative integer string' }, + ); + +export const AntDescriptionSchema = z.string(); // TODO: add specific limits for description ie max length +export const AntKeywordsSchema = z.array(z.string()); // TODO: add specific limits for keywords ie max amount and max length +export const AntRecordSchema = z.object({ + transactionId: ArweaveTxIdSchema.describe('The Target ID of the undername'), + ttlSeconds: z.number(), +}); +export type AoANTRecord = z.infer; + +export const AntRecordsSchema = z.record(z.string(), AntRecordSchema); +export const AntControllersSchema = z.array( + ArweaveTxIdSchema.describe('Controller address'), +); +export const AntBalancesSchema = z.record( + ArweaveTxIdSchema.describe('Holder address'), + z.number(), +); + +export const AntStateSchema = z.object({ + Name: z.string().describe('The name of the ANT.'), + Ticker: z.string().describe('The ticker symbol for the ANT.'), + Description: z.string().describe('The description for the ANT.'), + Keywords: AntKeywordsSchema.describe('The keywords for the ANT.'), + Denomination: z + .number() + .describe( + 'The number of decimal places to use for the ANT. Defaults to 0 if not set representing whole numbers.', + ) + .min(0, { message: 'Denomination must be a non-negative number' }), + Owner: ArweaveTxIdSchema.describe('The Owners address.'), + Controllers: AntControllersSchema.describe( + 'Controllers of the ANT who have administrative privileges.', + ), + Records: AntRecordsSchema.describe('Records associated with the ANT.'), + Balances: AntBalancesSchema.describe( + 'Balance details for each address holding the ANT.', + ), + Logo: ArweaveTxIdSchema.describe('Transaction ID of the ANT logo.'), + TotalSupply: z + .number() + .describe('Total supply of the ANT in circulation.') + .min(0, { message: 'Total supply must be a non-negative number' }), + Initialized: z + .boolean() + .describe('Flag indicating whether the ANT has been initialized.'), + ['Source-Code-TX-ID']: ArweaveTxIdSchema.describe( + 'Transaction ID of the Source Code for the ANT.', + ), +}); + +export type AoANTState = z.infer; +export const AntHandlerNames = [ + 'evolve', + '_eval', + '_default', + 'transfer', + 'balance', + 'balances', + 'totalSupply', + 'info', + 'addController', + 'removeController', + 'controllers', + 'setRecord', + 'removeRecord', + 'record', + 'records', + 'setName', + 'setTicker', + 'setDescription', + 'setKeywords', + 'initializeState', + 'state', + 'releaseName', + 'reassignName', +]; +export const AntHandlersSchema = z + .array(z.string({ description: 'Handler Name' })) + .refine( + (antHandlers: string[]) => { + return AntHandlerNames.every((handler) => antHandlers.includes(handler)); + }, + { + message: 'ANT is missing required handlers', + }, + ); + +export const AntInfoSchema = z.object({ + Name: z.string().describe('The name of the ANT.'), + Owner: ArweaveTxIdSchema.describe('The Owners address.'), + ['Source-Code-TX-ID']: ArweaveTxIdSchema.describe( + 'Transaction ID of the Source Code for the ANT.', + ), + Ticker: z.string().describe('The ticker symbol for the ANT.'), + ['Total-Supply']: IntegerStringSchema.describe( + 'Total supply of the ANT in circulation.', + ), + Description: AntDescriptionSchema.describe('The description for the ANT.'), + Keywords: AntKeywordsSchema.describe('The keywords for the ANT.'), + + Logo: ArweaveTxIdSchema.describe('Transaction ID of the ANT logo.'), + Denomination: IntegerStringSchema.describe( + 'The number of decimal places to use for the ANT. Defaults to 0 if not set representing whole numbers.', + ), + Handlers: AntHandlersSchema.optional().describe( + 'List of handlers for the ANT.', + ), + HandlerNames: AntHandlersSchema.optional().describe( + 'Deprecated: List of handlers for the ANT. Use "Handlers" instead.', + ), +}); + +export type AoANTInfo = z.infer; + +/** + * @param state {object} + * @returns {boolean} + */ +export function isAoANTState(state: object): state is AoANTState { + return AntStateSchema.safeParse(state).success; +} + +export type AntReadOptions = { strict?: boolean }; + +export interface AoANTRead { + getState(opts?: AntReadOptions): Promise; + getInfo(opts?: AntReadOptions): Promise; + getRecord( + { undername }: { undername: string }, + opts?: AntReadOptions, + ): Promise; + getRecords(opts?: AntReadOptions): Promise>; + getOwner(opts?: AntReadOptions): Promise; + getControllers(): Promise; + getTicker(opts?: AntReadOptions): Promise; + getName(opts?: AntReadOptions): Promise; + getBalance( + { address }: { address: WalletAddress }, + opts?: AntReadOptions, + ): Promise; + getBalances(opts?: AntReadOptions): Promise>; +} + +export interface AoANTWrite extends AoANTRead { + transfer( + { target }: { target: WalletAddress }, + options?: WriteOptions, + ): Promise; + addController( + { + controller, + }: { + controller: WalletAddress; + }, + options?: WriteOptions, + ): Promise; + removeController( + { + controller, + }: { + controller: WalletAddress; + }, + options?: WriteOptions, + ): Promise; + setRecord( + { + undername, + transactionId, + ttlSeconds, + }: { + undername: string; + transactionId: string; + ttlSeconds: number; + }, + options?: WriteOptions, + ): Promise; + removeRecord( + { undername }: { undername: string }, + options?: WriteOptions, + ): Promise; + setTicker( + { ticker }: { ticker: string }, + options?: WriteOptions, + ): Promise; + setDescription( + { description }: { description: string }, + options?: WriteOptions, + ): Promise; + setKeywords( + { keywords }: { keywords: string[] }, + options?: WriteOptions, + ): Promise; + setName( + { name }: { name: string }, + options?: WriteOptions, + ): Promise; + releaseName( + { name, ioProcessId }: { name: string; ioProcessId: string }, + options?: WriteOptions, + ): Promise; + reassignName( + { + name, + ioProcessId, + antProcessId, + }: { name: string; ioProcessId: string; antProcessId: string }, + options?: WriteOptions, + ): Promise; +} diff --git a/src/common.ts b/src/types/common.ts similarity index 87% rename from src/common.ts rename to src/types/common.ts index 528012bf..f2fde650 100644 --- a/src/common.ts +++ b/src/types/common.ts @@ -88,3 +88,22 @@ export interface AoClient { unmonitor: typeof unmonitor; dryrun: typeof dryrun; } + +export interface AOContract { + read({ + tags, + retries, + }: { + tags?: { name: string; value: string }[]; + retries?: number; + }): Promise; + send({ + tags, + data, + signer, + }: { + tags: { name: string; value: string }[]; + data: string | undefined; + signer: AoSigner; + }): Promise<{ id: string; result?: K }>; +} diff --git a/src/types.ts b/src/types/index.ts similarity index 92% rename from src/types.ts rename to src/types/index.ts index 249e70eb..2756e096 100644 --- a/src/types.ts +++ b/src/types/index.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +export * from './ant-registry.js'; +export * from './ant.js'; export * from './common.js'; -export * from './token.js'; export * from './io.js'; +export * from './token.js'; diff --git a/src/io.ts b/src/types/io.ts similarity index 74% rename from src/io.ts rename to src/types/io.ts index 4f87e24f..055b49e4 100644 --- a/src/io.ts +++ b/src/types/io.ts @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AOProcess } from './common/index.js'; -import { mIOToken } from './token.js'; +import { AOProcess } from '../common/index.js'; +import { validateArweaveId } from '../utils/arweave.js'; import { AoMessageResult, - AoSigner, AtLeastOne, BlockHeight, ProcessId, @@ -25,15 +24,15 @@ import { TransactionId, WalletAddress, WriteOptions, -} from './types.js'; -import { validateArweaveId } from './utils/arweave.js'; +} from './index.js'; +import { mIOToken } from './token.js'; // Pagination -export type PaginationParams = { +export type PaginationParams> = { cursor?: string; limit?: number; - sortBy?: string; + sortBy?: keyof T extends never ? string : keyof T; // default to string if T is empty sortOrder?: 'asc' | 'desc'; }; @@ -41,7 +40,7 @@ export type PaginationResult = { items: T[]; nextCursor: string | undefined; totalItems: number; - sortBy: keyof T; + sortBy?: keyof T; sortOrder: 'asc' | 'desc'; hasMore: boolean; }; @@ -96,7 +95,7 @@ export type AoEpochObservationData = { export type AoVaultData = { balance: number; - locked: number; + startTimestamp: Timestamp; endTimestamp: Timestamp; }; @@ -189,10 +188,19 @@ export type AoGatewayServices = } | undefined; // not required, for now +export type AoGatewayDelegates = Record; +export type AoGatewayDelegateAllowList = WalletAddress[]; + +export type AoWalletVault = AoVaultData & { + address: WalletAddress; + vaultId: string; +}; + export type AoGateway = { settings: AoGatewaySettings; + // @deprecated - use getGatewayDelegates instead + delegates: AoGatewayDelegates; stats: AoGatewayStats; - delegates: Record; totalDelegatedStake: number; vaults: Record; startTimestamp: Timestamp; @@ -240,9 +248,14 @@ export type AoGatewayDelegate = { vaults: Record; }; +export type AoGatewayDelegateWithAddress = AoGatewayDelegate & { + address: WalletAddress; +}; + export type AoGatewaySettings = { - allowDelegatedStaking: boolean; + allowDelegatedStaking: boolean | 'allowlist'; delegateRewardShareRatio: number; + allowedDelegates: WalletAddress[]; minDelegatedStake: number; autoStake: boolean; label: string; @@ -258,37 +271,29 @@ export type AoBalanceWithAddress = { balance: number; }; -// ANT Contract - -export type AoANTState = { - Name: string; - Ticker: string; - Denomination: number; - Owner: WalletAddress; - Controllers: WalletAddress[]; - Records: Record; - Balances: Record; - Logo: string; - TotalSupply: number; - Initialized: boolean; - ['Source-Code-TX-ID']: string; +// Auctions +export type AoAuctionSettings = { + durationMs: number; + decayRate: number; + scalingExponent: number; + startPriceMultiplier: number; }; -export type AoANTInfo = { - Name: string; - Owner: string; - Handlers: string[]; - ['Source-Code-TX-ID']: string; - // token related - Ticker: string; - ['Total-Supply']: string; - Logo: string; - Denomination: string; +export type AoAuction = { + name: string; + startTimestamp: Timestamp; + endTimestamp: Timestamp; + initiator: string; + baseFee: number; + demandFactor: number; + settings: AoAuctionSettings; }; -export type AoANTRecord = { - transactionId: string; - ttlSeconds: number; +export type AoAuctionPriceData = { + type: 'lease' | 'permabuy'; + years?: number; + prices: Record; + currentPrice: number; }; // Input types @@ -304,25 +309,6 @@ export type AoUpdateGatewaySettingsParams = AtLeastOne; // Interfaces -export interface AOContract { - read({ - tags, - retries, - }: { - tags?: { name: string; value: string }[]; - retries?: number; - }): Promise; - send({ - tags, - data, - signer, - }: { - tags: { name: string; value: string }[]; - data: string | undefined; - signer: AoSigner; - }): Promise<{ id: string; result?: K }>; -} - export interface AoIORead { // read interactions getInfo(): Promise<{ @@ -340,12 +326,25 @@ export interface AoIORead { }: { address: WalletAddress; }): Promise; + // TODO: these could be moved to a separate Gateways class that implements gateway specific interactions + getGatewayDelegates({ + address, + ...pageParams + }: { + address: WalletAddress; + } & PaginationParams): Promise< + PaginationResult + >; + getGatewayDelegateAllowList( + params?: PaginationParams, + ): Promise>; + // END OF GATEWAY SPECIFIC INTERACTIONS getGateways( - params?: PaginationParams, + params?: PaginationParams, ): Promise>; getBalance(params: { address: WalletAddress }): Promise; getBalances( - params?: PaginationParams, + params?: PaginationParams, ): Promise>; getArNSRecord({ name, @@ -353,7 +352,7 @@ export interface AoIORead { name: string; }): Promise; getArNSRecords( - params?: PaginationParams, + params?: PaginationParams, ): Promise>; getArNSReservedNames(): Promise< Record | Record @@ -363,6 +362,23 @@ export interface AoIORead { }: { name: string; }): Promise; + getArNSAuctions( + params?: PaginationParams, + ): Promise>; + getArNSAuction({ name }: { name: string }): Promise; + getArNSAuctionPrices({ + name, + type, + years, + timestamp, + intervalMs, + }: { + name: string; + type: 'lease' | 'permabuy'; + years?: number; + timestamp?: number; + intervalMs?: number; + }): Promise; getEpoch(epoch?: EpochInput): Promise; getCurrentEpoch(): Promise; getPrescribedObservers(epoch?: EpochInput): Promise; @@ -371,19 +387,29 @@ export interface AoIORead { getDistributions(epoch?: EpochInput): Promise; getTokenCost({ intent, - purchaseType, + type, years, name, quantity, }: { intent: 'Buy-Record' | 'Extend-Lease' | 'Increase-Undername-Limit'; - purchaseType?: 'permabuy' | 'lease'; + type?: 'permabuy' | 'lease'; years?: number; name?: string; quantity?: number; }): Promise; getRegistrationFees(): Promise; getDemandFactor(): Promise; + getVaults( + params?: PaginationParams, + ): Promise>; + getVault({ + address, + vaultId, + }: { + address: WalletAddress; + vaultId: string; + }): Promise; } export interface AoIOWrite extends AoIORead { @@ -398,38 +424,14 @@ export interface AoIOWrite extends AoIORead { }, options?: WriteOptions, ): Promise; + // TODO: these could be moved to a separate Gateways class that implements gateway specific interactions joinNetwork( - { - operatorStake, - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake, - note, - port, - properties, - protocol, - autoStake, - observerAddress, - }: AoJoinNetworkParams, + params: AoJoinNetworkParams, options?: WriteOptions, ): Promise; leaveNetwork(options?: WriteOptions): Promise; updateGatewaySettings( - { - allowDelegatedStaking, - delegateRewardShareRatio, - fqdn, - label, - minDelegatedStake, - note, - port, - properties, - protocol, - autoStake, - observerAddress, - }: AoUpdateGatewaySettingsParams, + params: AoUpdateGatewaySettingsParams, options?: WriteOptions, ): Promise; increaseOperatorStake( @@ -455,6 +457,14 @@ export interface AoIOWrite extends AoIORead { params: { target: WalletAddress; decreaseQty: number | mIOToken; + instant?: boolean; + }, + options?: WriteOptions, + ): Promise; + instantWithdrawal( + params: { + gatewayAddress?: WalletAddress; + vaultId: string; }, options?: WriteOptions, ): Promise; @@ -465,6 +475,7 @@ export interface AoIOWrite extends AoIORead { }, options?: WriteOptions, ): Promise; + // END OF GATEWAY SPECIFIC INTERACTIONS buyRecord( params: { name: string; @@ -474,6 +485,12 @@ export interface AoIOWrite extends AoIORead { }, options?: WriteOptions, ): Promise; + upgradeRecord( + params: { + name: string; + }, + options?: WriteOptions, + ): Promise; extendLease( params: { name: string; @@ -488,83 +505,23 @@ export interface AoIOWrite extends AoIORead { }, options?: WriteOptions, ): Promise; - cancelDelegateWithdrawal( + cancelWithdrawal( params: { - address: string; + gatewayAddress?: WalletAddress; vaultId: string; }, options?: WriteOptions, ): Promise; -} - -export interface AoANTRead { - getState(): Promise; - getInfo(): Promise; - getRecord({ undername }): Promise; - getRecords(): Promise>; - getOwner(): Promise; - getControllers(): Promise; - getTicker(): Promise; - getName(): Promise; - getBalance({ address }: { address: WalletAddress }): Promise; - getBalances(): Promise>; -} - -export interface AoANTWrite extends AoANTRead { - transfer( - { target }: { target: WalletAddress }, - options?: WriteOptions, - ): Promise; - addController( - { - controller, - }: { - controller: WalletAddress; - }, - options?: WriteOptions, - ): Promise; - removeController( - { - controller, - }: { - controller: WalletAddress; - }, - options?: WriteOptions, - ): Promise; - setRecord( - { - undername, - transactionId, - ttlSeconds, - }: { - undername: string; - transactionId: string; - ttlSeconds: number; + submitAuctionBid( + params: { + name: string; + processId: string; + quantity?: number; + type?: 'lease' | 'permabuy'; + years?: number; }, options?: WriteOptions, ): Promise; - removeRecord( - { undername }: { undername: string }, - options?: WriteOptions, - ): Promise; - setTicker( - { ticker }: { ticker: string }, - options?: WriteOptions, - ): Promise; - setName( - { name }: { name: string }, - options?: WriteOptions, - ): Promise; -} - -export interface AoANTRegistryRead { - accessControlList(params: { - address: string; - }): Promise<{ Owned: string[]; Controlled: string[] }>; -} - -export interface AoANTRegistryWrite extends AoANTRegistryRead { - register(params: { processId: string }): Promise; } // Typeguard functions diff --git a/src/token.ts b/src/types/token.ts similarity index 98% rename from src/token.ts rename to src/types/token.ts index 2ff0ce96..5b932339 100644 --- a/src/token.ts +++ b/src/types/token.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { MIO_PER_IO } from './constants.js'; +import { MIO_PER_IO } from '../constants.js'; interface Equatable { equals(other: T): boolean; diff --git a/src/utils/ao.ts b/src/utils/ao.ts index b5b6cfc7..ae694186 100644 --- a/src/utils/ao.ts +++ b/src/utils/ao.ts @@ -26,14 +26,13 @@ import { AOS_MODULE_ID, DEFAULT_SCHEDULER_ID, } from '../constants.js'; +import { AoANTRecord } from '../types/ant.js'; import { - AoANTRecord, - AoANTState, AoClient, AoSigner, ContractSigner, WalletAddress, -} from '../types.js'; +} from '../types/index.js'; export async function spawnANT({ signer, @@ -254,43 +253,3 @@ export function createAoSigner(signer: ContractSigner): AoSigner { return aoSigner; } - -// using passThrough to require the minimum fields and allow others (eg TotalSupply, Logo, etc) -export const AntStateSchema = z - .object({ - Name: z.string(), - Ticker: z.string(), - Owner: z.string(), - Controllers: z.array(z.string()), - Records: z.record( - z.string(), - z - .object({ - transactionId: z.string(), - ttlSeconds: z.number(), - }) - .passthrough(), - ), - Balances: z.record(z.string(), z.number()), - ['Source-Code-TX-ID']: z.string(), - }) - .passthrough(); - -/** - * @param state - * @returns {boolean} - * @throws {z.ZodError} if the state object does not match the expected schema - */ -export function isAoANTState( - state: object, - logger: Logger = Logger.default, -): state is AoANTState { - try { - AntStateSchema.parse(state); - return true; - } catch (error) { - // this allows us to see the path of the error in the object as well as the expected schema on invalid fields - logger.error(error.issues); - return false; - } -} diff --git a/src/utils/arweave.ts b/src/utils/arweave.ts index 6d5e3631..36ed72ce 100644 --- a/src/utils/arweave.ts +++ b/src/utils/arweave.ts @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { BlockHeight } from '../common.js'; +import Arweave from 'arweave'; + import { ARWEAVE_TX_REGEX } from '../constants.js'; +import { BlockHeight, Timestamp } from '../types/common.js'; export const validateArweaveId = (id: string): boolean => { return ARWEAVE_TX_REGEX.test(id); @@ -23,3 +25,27 @@ export const validateArweaveId = (id: string): boolean => { export function isBlockHeight(height: string | number): height is BlockHeight { return height !== undefined && !isNaN(parseInt(height.toString())); } + +export const pruneTags = ( + tags: { name: string; value: string | undefined }[], +): { name: string; value: string }[] => { + return tags.filter( + (tag: { + name: string; + value: string | undefined; + }): tag is { name: string; value: string } => tag.value !== undefined, + ); +}; + +export const getCurrentBlockUnixTimestampMs = async ( + arweave: Arweave, +): Promise => { + return await arweave.blocks + .getCurrent() + .then((block) => { + return block.timestamp * 1000; + }) + .catch(() => { + return Date.now(); // fallback to current time + }); +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index d7e8dd72..1dae79b6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './arweave.js'; -export * from './http-client.js'; export * from './ao.js'; +export * from './arweave.js'; +export * from './base64.js'; export * from './json.js'; export * from './processes.js'; +export * from './schema.js'; diff --git a/src/utils/processes.ts b/src/utils/processes.ts index 5a8d00f1..1c119dab 100644 --- a/src/utils/processes.ts +++ b/src/utils/processes.ts @@ -21,14 +21,14 @@ import { ANT } from '../common/ant.js'; import { IO } from '../common/io.js'; import { ILogger, Logger } from '../common/logger.js'; import { IO_TESTNET_PROCESS_ID } from '../constants.js'; +import { AoANTRegistryRead } from '../types/ant-registry.js'; +import { AoANTState } from '../types/ant.js'; import { - AoANTRegistryRead, - AoANTState, AoArNSNameData, AoIORead, ProcessId, WalletAddress, -} from '../types.js'; +} from '../types/index.js'; /** * @beta This API is in beta and may change in the future. @@ -67,6 +67,7 @@ export class ArNSEventEmitter extends EventEmitter { private timeoutMs: number; // timeout for each request to 3 seconds private throttle; private logger: ILogger; + private strict: boolean; constructor({ contract = IO.init({ processId: IO_TESTNET_PROCESS_ID, @@ -74,17 +75,20 @@ export class ArNSEventEmitter extends EventEmitter { timeoutMs = 60_000, concurrency = 30, logger = Logger.default, + strict = false, }: { contract?: AoIORead; timeoutMs?: number; concurrency?: number; logger?: ILogger; + strict?: boolean; } = {}) { super(); this.contract = contract; this.timeoutMs = timeoutMs; this.throttle = pLimit(concurrency); this.logger = logger; + this.strict = strict; } async fetchProcessesOwnedByWallet({ @@ -144,6 +148,7 @@ export class ArNSEventEmitter extends EventEmitter { } const ant = ANT.init({ processId, + strict: this.strict, }); const state: AoANTState | undefined = (await timeout( this.timeoutMs, diff --git a/src/utils/schema.ts b/src/utils/schema.ts new file mode 100644 index 00000000..5e7bf511 --- /dev/null +++ b/src/utils/schema.ts @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2022-2024 Permanent Data Solutions, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { z } from 'zod'; + +/** + * + * @param schema - zod schema + * @param v - value to parse + * @throws {z.SafeParseError} - if the value fails to parse + */ +export function parseSchemaResult(schema: z.ZodTypeAny, v: unknown) { + const schemaResult = schema.safeParse(v); + if (!schemaResult.success) { + throw new Error(JSON.stringify(schemaResult.error.format(), null, 2)); + } + return schemaResult; +} diff --git a/src/version.ts b/src/version.ts index 493353ea..da4b4698 100644 --- a/src/version.ts +++ b/src/version.ts @@ -16,4 +16,4 @@ // AUTOMATICALLY GENERATED FILE - DO NOT TOUCH -export const version = '2.3.2'; +export const version = '2.4.0-alpha.16'; diff --git a/src/web/index.ts b/src/web/index.ts index a40d0e6c..cd943831 100644 --- a/src/web/index.ts +++ b/src/web/index.ts @@ -15,7 +15,7 @@ */ export { ArweaveSigner, ArconnectSigner } from '@dha-team/arbundles'; -export * from '../types.js'; +export * from '../types/index.js'; export * from '../common/index.js'; export * from '../constants.js'; export * from '../utils/index.js'; diff --git a/tests/e2e/cjs/index.test.js b/tests/e2e/cjs/index.test.js index 5895da96..7af75f4b 100644 --- a/tests/e2e/cjs/index.test.js +++ b/tests/e2e/cjs/index.test.js @@ -30,343 +30,383 @@ const signers = [ createAoSigner(new ArweaveSigner(testWallet)), ]; +const aoClient = connect({ + CU_URL: 'http://localhost:6363', +}); + const io = IO.init({ process: new AOProcess({ processId: process.env.IO_PROCESS_ID || ioDevnetProcessId, - ao: connect({ - CU_URL: 'http://localhost:6363', - }), + ao: aoClient, }), }); -describe('IO', async () => { - let compose; - before(async () => { - compose = await new DockerComposeEnvironment( - projectRootPath, - '../docker-compose.test.yml', - ) - .withBuild() - .withWaitStrategy('ao-cu-1', Wait.forHttp('/', 6363)) - .up(['ao-cu']); - }); +describe('e2e cjs tests', async () => { + describe('IO', async () => { + let compose; + before(async () => { + compose = await new DockerComposeEnvironment( + projectRootPath, + '../docker-compose.test.yml', + ) + .withBuild() + .withWaitStrategy('ao-cu-1', Wait.forHttp('/', 6363)) + .up(['ao-cu']); + }); - after(async () => { - await compose.down(); - }); - it('should be able to get the process information', async () => { - const epoch = await io.getInfo(); - assert.ok(epoch); - }); + after(async () => { + await compose.down(); + }); + it('should be able to get the process information', async () => { + const epoch = await io.getInfo(); + assert.ok(epoch); + }); - it('should be able to get the total token supply', async () => { - const tokenSupply = await io.getTokenSupply(); - assert.ok(tokenSupply); - }); + it('should be able to get the total token supply', async () => { + const tokenSupply = await io.getTokenSupply(); + assert.ok(tokenSupply); + }); - it('should be able to get first set of arns records', async () => { - const records = await io.getArNSRecords(); - assert.ok(records); - assert(records.limit === 100); - assert(records.sortOrder === 'desc'); - assert(records.sortBy === 'startTimestamp'); - assert(typeof records.totalItems === 'number'); - assert(typeof records.sortBy === 'string'); - assert(typeof records.sortOrder === 'string'); - assert(typeof records.limit === 'number'); - assert(typeof records.hasMore === 'boolean'); - if (records.nextCursor) { - assert(typeof records.nextCursor === 'string'); - } - assert(Array.isArray(records.items)); - records.items.forEach((record) => { - assert(typeof record.processId === 'string'); - assert(typeof record.name === 'string'); - assert(typeof record.startTimestamp === 'number'); - assert(['lease', 'permabuy'].includes(record.type)); - assert(typeof record.undernameLimit === 'number'); + it('should be able to get first set of arns records', async () => { + const records = await io.getArNSRecords(); + assert.ok(records); + assert(records.limit === 100); + assert(records.sortOrder === 'desc'); + assert(records.sortBy === 'startTimestamp'); + assert(typeof records.totalItems === 'number'); + assert(typeof records.sortBy === 'string'); + assert(typeof records.sortOrder === 'string'); + assert(typeof records.limit === 'number'); + assert(typeof records.hasMore === 'boolean'); + if (records.nextCursor) { + assert(typeof records.nextCursor === 'string'); + } + assert(Array.isArray(records.items)); + records.items.forEach((record) => { + assert(typeof record.processId === 'string'); + assert(typeof record.name === 'string'); + assert(typeof record.startTimestamp === 'number'); + assert(['lease', 'permabuy'].includes(record.type)); + assert(typeof record.undernameLimit === 'number'); + }); }); - }); - it('should be able to return a specific page of arns records', async () => { - const records = await io.getArNSRecords({ - cursor: 'ardrive', - limit: 5, - sortOrder: 'desc', - sortBy: 'name', + it('should be able to return a specific page of arns records', async () => { + const records = await io.getArNSRecords({ + cursor: 'ardrive', + limit: 5, + sortOrder: 'desc', + sortBy: 'name', + }); + assert.ok(records); + assert(records.limit === 5); + assert(records.sortOrder === 'desc'); + assert(records.sortBy === 'name'); + assert(typeof records.totalItems === 'number'); + assert(typeof records.sortBy === 'string'); + assert(typeof records.sortOrder === 'string'); + assert(typeof records.limit === 'number'); + assert(typeof records.hasMore === 'boolean'); + if (records.nextCursor) { + assert(typeof records.nextCursor === 'string'); + } + assert(Array.isArray(records.items)); + records.items.forEach((record) => { + assert(typeof record.processId === 'string'); + assert(typeof record.name === 'string'); + assert(typeof record.startTimestamp === 'number'); + assert(['lease', 'permabuy'].includes(record.type)); + assert(typeof record.undernameLimit === 'number'); + }); }); - assert.ok(records); - assert(records.limit === 5); - assert(records.sortOrder === 'desc'); - assert(records.sortBy === 'name'); - assert(typeof records.totalItems === 'number'); - assert(typeof records.sortBy === 'string'); - assert(typeof records.sortOrder === 'string'); - assert(typeof records.limit === 'number'); - assert(typeof records.hasMore === 'boolean'); - if (records.nextCursor) { - assert(typeof records.nextCursor === 'string'); - } - assert(Array.isArray(records.items)); - records.items.forEach((record) => { - assert(typeof record.processId === 'string'); - assert(typeof record.name === 'string'); - assert(typeof record.startTimestamp === 'number'); - assert(['lease', 'permabuy'].includes(record.type)); - assert(typeof record.undernameLimit === 'number'); + it('should be able to get a single arns record', async () => { + const arns = await io.getArNSRecord({ name: 'ardrive' }); + assert.ok(arns); }); - }); - it('should be able to get a single arns record', async () => { - const arns = await io.getArNSRecord({ name: 'ardrive' }); - assert.ok(arns); - }); - it('should be able to get the current epoch using getCurrentEpoch', async () => { - const epoch = await io.getCurrentEpoch(); - assert.ok(epoch); - }); - - it('should be able to get the current epoch using getEpoch', async () => { - const epoch = await io.getEpoch({ epochIndex: 0 }); - assert.ok(epoch); - }); + it('should be able to get the current epoch using getCurrentEpoch', async () => { + const epoch = await io.getCurrentEpoch(); + assert.ok(epoch); + }); - it('should be able to get epoch-settings', async () => { - const epochSettings = await io.getEpochSettings(); - assert.ok(epochSettings); - }); + it('should be able to get the current epoch using getEpoch', async () => { + const epoch = await io.getEpoch({ epochIndex: 0 }); + assert.ok(epoch); + }); - it('should be able to get reserved names', async () => { - const reservedNames = await io.getArNSReservedNames(); - assert.ok(reservedNames); - }); + it('should be able to get epoch-settings', async () => { + const epochSettings = await io.getEpochSettings(); + assert.ok(epochSettings); + }); - it('should be able to get a single reserved name', async () => { - const reservedNames = await io.getArNSReservedNames({ name: 'www ' }); - assert.ok(reservedNames); - }); + it('should be able to get reserved names', async () => { + const reservedNames = await io.getArNSReservedNames(); + assert.ok(reservedNames); + }); - it('should be able to get first page of gateways', async () => { - const gateways = await io.getGateways(); - assert.ok(gateways); - assert(gateways.limit === 100); - assert(gateways.sortOrder === 'desc'); - assert(gateways.sortBy === 'startTimestamp'); - assert(typeof gateways.totalItems === 'number'); - assert(typeof gateways.sortBy === 'string'); - assert(typeof gateways.sortOrder === 'string'); - assert(typeof gateways.limit === 'number'); - assert(typeof gateways.hasMore === 'boolean'); - if (gateways.nextCursor) { - assert(typeof gateways.nextCursor === 'string'); - } - assert(Array.isArray(gateways.items)); - gateways.items.forEach((gateway) => { - assert(typeof gateway.gatewayAddress === 'string'); - assert(typeof gateway.observerAddress === 'string'); - assert(typeof gateway.startTimestamp === 'number'); - assert(typeof gateway.operatorStake === 'number'); - assert(typeof gateway.totalDelegatedStake === 'number'); - assert(typeof gateway.settings === 'object'); - assert(typeof gateway.weights === 'object'); - assert(typeof gateway.weights.normalizedCompositeWeight === 'number'); - assert(typeof gateway.weights.compositeWeight === 'number'); - assert(typeof gateway.weights.stakeWeight === 'number'); - assert(typeof gateway.weights.tenureWeight === 'number'); - assert(typeof gateway.weights.observerRewardRatioWeight === 'number'); - assert(typeof gateway.weights.gatewayRewardRatioWeight === 'number'); + it('should be able to get a single reserved name', async () => { + const reservedNames = await io.getArNSReservedNames({ name: 'www ' }); + assert.ok(reservedNames); }); - }); - it('should be able to get a specific page of gateways', async () => { - const gateways = await io.getGateways({ - cursor: 1000000, - limit: 1, - sortBy: 'operatorStake', - sortOrder: 'desc', + it('should be able to get first page of gateways', async () => { + const gateways = await io.getGateways(); + assert.ok(gateways); + assert(gateways.limit === 100); + assert(gateways.sortOrder === 'desc'); + assert(gateways.sortBy === 'startTimestamp'); + assert(typeof gateways.totalItems === 'number'); + assert(typeof gateways.sortBy === 'string'); + assert(typeof gateways.sortOrder === 'string'); + assert(typeof gateways.limit === 'number'); + assert(typeof gateways.hasMore === 'boolean'); + if (gateways.nextCursor) { + assert(typeof gateways.nextCursor === 'string'); + } + assert(Array.isArray(gateways.items)); + gateways.items.forEach((gateway) => { + assert(typeof gateway.gatewayAddress === 'string'); + assert(typeof gateway.observerAddress === 'string'); + assert(typeof gateway.startTimestamp === 'number'); + assert(typeof gateway.operatorStake === 'number'); + assert(typeof gateway.totalDelegatedStake === 'number'); + assert(typeof gateway.settings === 'object'); + assert(typeof gateway.weights === 'object'); + assert(typeof gateway.weights.normalizedCompositeWeight === 'number'); + assert(typeof gateway.weights.compositeWeight === 'number'); + assert(typeof gateway.weights.stakeWeight === 'number'); + assert(typeof gateway.weights.tenureWeight === 'number'); + assert(typeof gateway.weights.observerRewardRatioWeight === 'number'); + assert(typeof gateway.weights.gatewayRewardRatioWeight === 'number'); + }); }); - assert.ok(gateways); - assert(gateways.limit === 1); - assert(gateways.sortOrder === 'desc'); - assert(gateways.sortBy === 'operatorStake'); - assert(typeof gateways.totalItems === 'number'); - assert(typeof gateways.sortBy === 'string'); - assert(typeof gateways.sortOrder === 'string'); - assert(typeof gateways.limit === 'number'); - assert(typeof gateways.hasMore === 'boolean'); - if (gateways.nextCursor) { - assert(typeof gateways.nextCursor === 'string'); - } - assert(Array.isArray(gateways.items)); - gateways.items.forEach((gateway) => { - assert(typeof gateway.gatewayAddress === 'string'); - assert(typeof gateway.observerAddress === 'string'); - assert(typeof gateway.startTimestamp === 'number'); - assert(typeof gateway.operatorStake === 'number'); - assert(typeof gateway.totalDelegatedStake === 'number'); - assert(typeof gateway.settings === 'object'); - assert(typeof gateway.weights === 'object'); - assert(typeof gateway.weights.normalizedCompositeWeight === 'number'); - assert(typeof gateway.weights.compositeWeight === 'number'); - assert(typeof gateway.weights.stakeWeight === 'number'); - assert(typeof gateway.weights.tenureWeight === 'number'); - assert(typeof gateway.weights.observerRewardRatioWeight === 'number'); - assert(typeof gateway.weights.gatewayRewardRatioWeight === 'number'); + + it('should be able to get a specific page of gateways', async () => { + const gateways = await io.getGateways({ + cursor: 1000000, + limit: 1, + sortBy: 'operatorStake', + sortOrder: 'desc', + }); + assert.ok(gateways); + assert(gateways.limit === 1); + assert(gateways.sortOrder === 'desc'); + assert(gateways.sortBy === 'operatorStake'); + assert(typeof gateways.totalItems === 'number'); + assert(typeof gateways.sortBy === 'string'); + assert(typeof gateways.sortOrder === 'string'); + assert(typeof gateways.limit === 'number'); + assert(typeof gateways.hasMore === 'boolean'); + if (gateways.nextCursor) { + assert(typeof gateways.nextCursor === 'string'); + } + assert(Array.isArray(gateways.items)); + gateways.items.forEach((gateway) => { + assert(typeof gateway.gatewayAddress === 'string'); + assert(typeof gateway.observerAddress === 'string'); + assert(typeof gateway.startTimestamp === 'number'); + assert(typeof gateway.operatorStake === 'number'); + assert(typeof gateway.totalDelegatedStake === 'number'); + assert(typeof gateway.settings === 'object'); + assert(typeof gateway.weights === 'object'); + assert(typeof gateway.weights.normalizedCompositeWeight === 'number'); + assert(typeof gateway.weights.compositeWeight === 'number'); + assert(typeof gateway.weights.stakeWeight === 'number'); + assert(typeof gateway.weights.tenureWeight === 'number'); + assert(typeof gateway.weights.observerRewardRatioWeight === 'number'); + assert(typeof gateway.weights.gatewayRewardRatioWeight === 'number'); + }); }); - }); - it('should be able to get a single gateway', async () => { - const gateways = await io.getGateway({ - address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + it('should be able to get a single gateway', async () => { + const gateways = await io.getGateway({ + address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + }); + assert.ok(gateways); }); - assert.ok(gateways); - }); - it('should be able to get balances, defaulting to first page', async () => { - const balances = await io.getBalances(); - assert.ok(balances); - assert(balances.limit === 100); - assert(balances.sortOrder === 'desc'); - assert(balances.sortBy === 'balance'); - assert(typeof balances.totalItems === 'number'); - assert(typeof balances.sortBy === 'string'); - assert(typeof balances.sortOrder === 'string'); - assert(typeof balances.limit === 'number'); - assert(typeof balances.hasMore === 'boolean'); - if (balances.nextCursor) { - assert(typeof gateways.nextCursor === 'string'); - } - assert(Array.isArray(balances.items)); - balances.items.forEach((wallet) => { - assert(typeof wallet.address === 'string'); - assert(typeof wallet.balance === 'number'); + it('should be able to get balances, defaulting to first page', async () => { + const balances = await io.getBalances(); + assert.ok(balances); + assert(balances.limit === 100); + assert(balances.sortOrder === 'desc'); + assert(balances.sortBy === 'balance'); + assert(typeof balances.totalItems === 'number'); + assert(typeof balances.sortBy === 'string'); + assert(typeof balances.sortOrder === 'string'); + assert(typeof balances.limit === 'number'); + assert(typeof balances.hasMore === 'boolean'); + if (balances.nextCursor) { + assert(typeof gateways.nextCursor === 'string'); + } + assert(Array.isArray(balances.items)); + balances.items.forEach((wallet) => { + assert(typeof wallet.address === 'string'); + assert(typeof wallet.balance === 'number'); + }); }); - }); - it('should be able to get balances of a specific to first page', async () => { - const balances = await io.getBalances({ - cursor: 1000000, - limit: 1, - sortBy: 'address', - sortOrder: 'asc', + it('should be able to get balances of a specific to first page', async () => { + const balances = await io.getBalances({ + cursor: 1000000, + limit: 1, + sortBy: 'address', + sortOrder: 'asc', + }); + assert.ok(balances); + assert(balances.limit === 1); + assert(balances.sortOrder === 'asc'); + assert(balances.sortBy === 'address'); + assert(typeof balances.totalItems === 'number'); + assert(typeof balances.sortBy === 'string'); + assert(typeof balances.sortOrder === 'string'); + assert(typeof balances.limit === 'number'); + assert(typeof balances.hasMore === 'boolean'); + if (balances.nextCursor) { + assert(typeof balances.nextCursor === 'string'); + } + assert(Array.isArray(balances.items)); + balances.items.forEach((wallet) => { + assert(typeof wallet.address === 'string'); + assert(typeof wallet.balance === 'number'); + }); }); - assert.ok(balances); - assert(balances.limit === 1); - assert(balances.sortOrder === 'asc'); - assert(balances.sortBy === 'address'); - assert(typeof balances.totalItems === 'number'); - assert(typeof balances.sortBy === 'string'); - assert(typeof balances.sortOrder === 'string'); - assert(typeof balances.limit === 'number'); - assert(typeof balances.hasMore === 'boolean'); - if (balances.nextCursor) { - assert(typeof balances.nextCursor === 'string'); - } - assert(Array.isArray(balances.items)); - balances.items.forEach((wallet) => { - assert(typeof wallet.address === 'string'); - assert(typeof wallet.balance === 'number'); + + it('should be able to get a single balance', async () => { + const balances = await io.getBalance({ + address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + }); + assert.ok(balances); }); - }); - it('should be able to get a single balance', async () => { - const balances = await io.getBalance({ - address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + it('should be able to get prescribed names', async () => { + const prescribedNames = await io.getPrescribedNames(); + assert.ok(prescribedNames); }); - assert.ok(balances); - }); - it('should be able to get prescribed names', async () => { - const prescribedNames = await io.getPrescribedNames(); - assert.ok(prescribedNames); - }); + it('should return the prescribed observers for a given epoch', async () => { + const observers = await io.getPrescribedObservers(); + assert.ok(observers); + for (const observer of observers) { + assert(typeof observer.gatewayAddress === 'string'); + assert(typeof observer.observerAddress === 'string'); + assert(typeof observer.stake === 'number'); + assert(typeof observer.startTimestamp === 'number'); + assert(typeof observer.stakeWeight === 'number'); + assert(typeof observer.tenureWeight === 'number'); + assert(typeof observer.gatewayRewardRatioWeight === 'number'); + assert(typeof observer.observerRewardRatioWeight === 'number'); + assert(typeof observer.compositeWeight === 'number'); + } + }); - it('should return the prescribed observers for a given epoch', async () => { - const observers = await io.getPrescribedObservers(); - assert.ok(observers); - for (const observer of observers) { - assert(typeof observer.gatewayAddress === 'string'); - assert(typeof observer.observerAddress === 'string'); - assert(typeof observer.stake === 'number'); - assert(typeof observer.startTimestamp === 'number'); - assert(typeof observer.stakeWeight === 'number'); - assert(typeof observer.tenureWeight === 'number'); - assert(typeof observer.gatewayRewardRatioWeight === 'number'); - assert(typeof observer.observerRewardRatioWeight === 'number'); - assert(typeof observer.compositeWeight === 'number'); - } - }); + it('should be able to get token cost for leasing a name', async () => { + const tokenCost = await io.getTokenCost({ + intent: 'Buy-Record', + name: 'new-name', + years: 1, + }); + assert.ok(tokenCost); + }); - it('should be able to get token cost for leasing a name', async () => { - const tokenCost = await io.getTokenCost({ - intent: 'Buy-Record', - name: 'new-name', - years: 1, + it('should be able to get token cost for buying a name name', async () => { + const tokenCost = await io.getTokenCost({ + intent: 'Buy-Record', + name: 'new-name', + type: 'permabuy', + }); + assert.ok(tokenCost); }); - assert.ok(tokenCost); - }); - it('should be able to get token cost for buying a name name', async () => { - const tokenCost = await io.getTokenCost({ - intent: 'Buy-Record', - name: 'new-name', - type: 'permabuy', + it('should be able to get registration fees', async () => { + const registrationFees = await io.getRegistrationFees(); + assert(registrationFees); + assert.equal(Object.keys(registrationFees).length, 51); + for (const nameLength of Object.keys(registrationFees)) { + // assert lease is length of 5 + assert(registrationFees[nameLength]['lease']['1'] > 0); + assert(registrationFees[nameLength]['lease']['2'] > 0); + assert(registrationFees[nameLength]['lease']['3'] > 0); + assert(registrationFees[nameLength]['lease']['4'] > 0); + assert(registrationFees[nameLength]['lease']['5'] > 0); + assert(registrationFees[nameLength]['permabuy'] > 0); + } }); - assert.ok(tokenCost); - }); + it('should be able to create IOWriteable with valid signers', async () => { + for (const signer of signers) { + const io = IO.init({ signer }); - it('should be able to get registration fees', async () => { - const registrationFees = await io.getRegistrationFees(); - assert(registrationFees); - assert.equal(Object.keys(registrationFees).length, 51); - for (const nameLength of Object.keys(registrationFees)) { - // assert lease is length of 5 - assert(registrationFees[nameLength]['lease']['1'] > 0); - assert(registrationFees[nameLength]['lease']['2'] > 0); - assert(registrationFees[nameLength]['lease']['3'] > 0); - assert(registrationFees[nameLength]['lease']['4'] > 0); - assert(registrationFees[nameLength]['lease']['5'] > 0); - assert(registrationFees[nameLength]['permabuy'] > 0); - } - }); - it('should be able to create IOWriteable with valid signers', async () => { - for (const signer of signers) { - const io = IO.init({ signer }); + assert(io instanceof IOWriteable); + } + }); - assert(io instanceof IOWriteable); - } + it('should be able to get paginated vaults', async () => { + const vaults = await io.getVaults(); + assert.ok(vaults); + assert(vaults.limit === 100); + assert(vaults.sortOrder === 'desc'); + assert(vaults.sortBy === 'address'); + assert(typeof vaults.totalItems === 'number'); + assert(typeof vaults.sortBy === 'string'); + assert(typeof vaults.sortOrder === 'string'); + assert(typeof vaults.limit === 'number'); + assert(typeof vaults.hasMore === 'boolean'); + if (vaults.nextCursor) { + assert(typeof vaults.nextCursor === 'string'); + } + assert(Array.isArray(vaults.items)); + vaults.items.forEach( + ({ address, vaultId, balance, endTimestamp, startTimestamp }) => { + assert(typeof address === 'string'); + assert(typeof balance === 'number'); + assert(typeof startTimestamp === 'number'); + assert(typeof endTimestamp === 'number'); + assert(typeof vaultId === 'string'); + }, + ); + }); }); -}); -describe('ANTRegistry', async () => { - const registry = ANTRegistry.init(); - const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; + describe('ANTRegistry', async () => { + const registry = ANTRegistry.init(); + const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; - it('should retrieve ids from registry', async () => { - const affiliatedAnts = await registry.accessControlList({ address }); - assert(Array.isArray(affiliatedAnts.Owned)); - assert(Array.isArray(affiliatedAnts.Controlled)); - }); + it('should retrieve ids from registry', async () => { + const affiliatedAnts = await registry.accessControlList({ address }); + assert(Array.isArray(affiliatedAnts.Owned)); + assert(Array.isArray(affiliatedAnts.Controlled)); + }); - it('should be able to create AoANTRegistryWriteable with valid signers', async () => { - for (const signer of signers) { - const registry = ANTRegistry.init({ - signer, - }); - assert(registry instanceof AoANTRegistryWriteable); - } + it('should be able to create AoANTRegistryWriteable with valid signers', async () => { + for (const signer of signers) { + const registry = ANTRegistry.init({ + signer, + }); + assert(registry instanceof AoANTRegistryWriteable); + } + }); }); -}); -describe('ANT', async () => { - it('should be able to create ANTWriteable with valid signers', async () => { - for (const signer of signers) { - const ant = ANT.init({ - processId: 'aWI_dq1JH7facsulLuas1X3l5dkKuWtixcZDYMw9mpg', - signer, - }); + describe('ANT', async () => { + const processId = 'YcxE5IbqZYK72H64ELoysxiJ-0wb36deYPv55wgl8xo'; + it('should be able to create ANTWriteable with valid signers', async () => { + for (const signer of signers) { + const ant = ANT.init({ + processId, + signer, + strict: true, + ao: aoClient, + }); + const strictAnt = ANT.init({ + processId, + signer, + strict: true, + ao: aoClient, + }); - assert(ant instanceof AoANTWriteable); - } + assert(ant instanceof AoANTWriteable); + assert(strictAnt instanceof AoANTWriteable); + } + }); }); }); diff --git a/tests/e2e/cjs/yarn.lock b/tests/e2e/cjs/yarn.lock index 46d05d90..1d96fcbf 100644 --- a/tests/e2e/cjs/yarn.lock +++ b/tests/e2e/cjs/yarn.lock @@ -785,10 +785,10 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.4: - version "6.5.5" - resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz" - integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== +elliptic@^6.5.7: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -1289,11 +1289,11 @@ scrypt-js@3.0.1: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.0.tgz" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.1.tgz#dc2c86187d48ff2da756f0f7e96417ee03c414b1" + integrity sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.7" node-addon-api "^5.0.0" node-gyp-build "^4.2.0" diff --git a/tests/e2e/esm/index.test.js b/tests/e2e/esm/index.test.js index 24bbdc3d..db0709c9 100644 --- a/tests/e2e/esm/index.test.js +++ b/tests/e2e/esm/index.test.js @@ -1,6 +1,7 @@ import { ANT, ANTRegistry, + ANT_REGISTRY_ID, AOProcess, AoANTRegistryWriteable, AoANTWriteable, @@ -32,16 +33,18 @@ const signers = [ * (simply running npm run test:integration will ensure npm link is ran) */ +const aoClient = connect({ + CU_URL: 'http://localhost:6363', +}); + const io = IO.init({ process: new AOProcess({ processId: ioDevnetProcessId, - ao: connect({ - CU_URL: 'http://localhost:6363', - }), + ao: aoClient, }), }); -describe('IO', async () => { +describe('e2e esm tests', async () => { let compose; before(async () => { compose = await new DockerComposeEnvironment( @@ -57,410 +60,571 @@ describe('IO', async () => { await compose.down(); }); - it('should be able to get the process information', async () => { - const info = await io.getInfo(); - assert.ok(info); - assert(typeof info.Name === 'string'); - assert(typeof info.Ticker === 'string'); - assert(typeof info.Logo === 'string'); - assert(typeof info.Denomination === 'number'); - assert(Array.isArray(info.Handlers)); - assert(typeof info.LastTickedEpochIndex === 'number'); - }); + describe('IO', async () => { + it('should be able to get the process information', async () => { + const info = await io.getInfo(); + assert.ok(info); + assert(typeof info.Name === 'string'); + assert(typeof info.Ticker === 'string'); + assert(typeof info.Logo === 'string'); + assert(typeof info.Denomination === 'number'); + assert(Array.isArray(info.Handlers)); + assert(typeof info.LastTickedEpochIndex === 'number'); + }); - it('should be able to get the total token supply', async () => { - const tokenSupply = await io.getTokenSupply(); - assert.ok(tokenSupply); - assert(typeof tokenSupply.total === 'number'); - assert(typeof tokenSupply.circulating === 'number'); - assert(typeof tokenSupply.locked === 'number'); - assert(typeof tokenSupply.withdrawn === 'number'); - assert(typeof tokenSupply.delegated === 'number'); - assert(typeof tokenSupply.staked === 'number'); - assert(typeof tokenSupply.protocolBalance === 'number'); - }); + it('should be able to return a specific page of arns records', async () => { + const records = await io.getArNSRecords({ + cursor: 'ardrive', + limit: 5, + sortOrder: 'desc', + sortBy: 'name', + }); + assert.ok(records); + assert(records.limit === 5); + assert(records.sortOrder === 'desc'); + assert(records.sortBy === 'name'); + assert(typeof records.totalItems === 'number'); + assert(typeof records.sortBy === 'string'); + assert(typeof records.sortOrder === 'string'); + assert(typeof records.limit === 'number'); + assert(typeof records.hasMore === 'boolean'); + if (records.nextCursor) { + assert(typeof records.nextCursor === 'string'); + } + assert(Array.isArray(records.items)); + records.items.forEach((record) => { + assert(typeof record.processId === 'string'); + assert(typeof record.name === 'string'); + assert(typeof record.startTimestamp === 'number'); + assert(['lease', 'permabuy'].includes(record.type)); + assert(typeof record.undernameLimit === 'number'); + }); + }); + it('should be able to get a single arns record', async () => { + const arns = await io.getArNSRecord({ name: 'ardrive' }); + assert.ok(arns); + }); - it('should be able to get first set of arns records', async () => { - const records = await io.getArNSRecords(); - assert.ok(records); - assert(records.limit === 100); - assert(records.sortOrder === 'desc'); - assert(records.sortBy === 'startTimestamp'); - assert(typeof records.totalItems === 'number'); - assert(typeof records.sortBy === 'string'); - assert(typeof records.sortOrder === 'string'); - assert(typeof records.limit === 'number'); - assert(typeof records.hasMore === 'boolean'); - if (records.nextCursor) { - assert(typeof records.nextCursor === 'string'); - } - assert(Array.isArray(records.items)); - records.items.forEach((record) => { - assert(typeof record.processId === 'string'); - assert(typeof record.name === 'string'); - assert(typeof record.startTimestamp === 'number'); - assert(['lease', 'permabuy'].includes(record.type)); - assert(typeof record.undernameLimit === 'number'); + it('should be able to get the current epoch', async () => { + const epoch = await io.getCurrentEpoch(); + assert.ok(epoch); }); - }); - it('should be able to return a specific page of arns records', async () => { - const records = await io.getArNSRecords({ - cursor: 'ardrive', - limit: 5, - sortOrder: 'desc', - sortBy: 'name', - }); - assert.ok(records); - assert(records.limit === 5); - assert(records.sortOrder === 'desc'); - assert(records.sortBy === 'name'); - assert(typeof records.totalItems === 'number'); - assert(typeof records.sortBy === 'string'); - assert(typeof records.sortOrder === 'string'); - assert(typeof records.limit === 'number'); - assert(typeof records.hasMore === 'boolean'); - if (records.nextCursor) { - assert(typeof records.nextCursor === 'string'); - } - assert(Array.isArray(records.items)); - records.items.forEach((record) => { - assert(typeof record.processId === 'string'); - assert(typeof record.name === 'string'); - assert(typeof record.startTimestamp === 'number'); - assert(['lease', 'permabuy'].includes(record.type)); - assert(typeof record.undernameLimit === 'number'); + it('should be able to get epoch-settings', async () => { + const epochSettings = await io.getEpochSettings(); + assert.ok(epochSettings); }); - }); - it('should be able to get a single arns record', async () => { - const arns = await io.getArNSRecord({ name: 'ardrive' }); - assert.ok(arns); - }); - it('should be able to get the current epoch', async () => { - const epoch = await io.getCurrentEpoch(); - assert.ok(epoch); - }); + it('should be able to get reserved names', async () => { + const reservedNames = await io.getArNSReservedNames(); + assert.ok(reservedNames); + }); - it('should be able to get epoch-settings', async () => { - const epochSettings = await io.getEpochSettings(); - assert.ok(epochSettings); - }); + it('should be able to get a single reserved name', async () => { + const reservedNames = await io.getArNSReservedNames({ name: 'www ' }); + assert.ok(reservedNames); + }); - it('should be able to get reserved names', async () => { - const reservedNames = await io.getArNSReservedNames(); - assert.ok(reservedNames); - }); + it('should be able to get first page of gateways', async () => { + const gateways = await io.getGateways(); + assert.ok(gateways); + assert(gateways.limit === 100); + assert(gateways.sortOrder === 'desc'); + assert(gateways.sortBy === 'startTimestamp'); + assert(typeof gateways.totalItems === 'number'); + assert(typeof gateways.sortBy === 'string'); + assert(typeof gateways.sortOrder === 'string'); + assert(typeof gateways.limit === 'number'); + assert(typeof gateways.hasMore === 'boolean'); + if (gateways.nextCursor) { + assert(typeof gateways.nextCursor === 'string'); + } + assert(Array.isArray(gateways.items)); + gateways.items.forEach((gateway) => { + assert(typeof gateway.gatewayAddress === 'string'); + assert(typeof gateway.observerAddress === 'string'); + assert(typeof gateway.startTimestamp === 'number'); + assert(typeof gateway.operatorStake === 'number'); + assert(typeof gateway.totalDelegatedStake === 'number'); + assert(typeof gateway.settings === 'object'); + assert(typeof gateway.weights === 'object'); + assert(typeof gateway.weights.normalizedCompositeWeight === 'number'); + assert(typeof gateway.weights.compositeWeight === 'number'); + assert(typeof gateway.weights.stakeWeight === 'number'); + assert(typeof gateway.weights.tenureWeight === 'number'); + assert(typeof gateway.weights.observerRewardRatioWeight === 'number'); + assert(typeof gateway.weights.gatewayRewardRatioWeight === 'number'); + if (gateway.vaults?.length > 0) { + gateway.vaults.forEach((vault) => { + assert(typeof vault.balance === 'number'); + assert(typeof vault.startTimestamp === 'number'); + }); + } + }); + }); - it('should be able to get a single reserved name', async () => { - const reservedNames = await io.getArNSReservedNames({ name: 'www ' }); - assert.ok(reservedNames); - }); + it('should be able to get a specific page of gateways', async () => { + const gateways = await io.getGateways({ + cursor: 1000000, + limit: 1, + sortBy: 'operatorStake', + sortOrder: 'desc', + }); + assert.ok(gateways); + assert(gateways.limit === 1); + assert(gateways.sortOrder === 'desc'); + assert(gateways.sortBy === 'operatorStake'); + assert(typeof gateways.totalItems === 'number'); + assert(typeof gateways.sortBy === 'string'); + assert(typeof gateways.sortOrder === 'string'); + assert(typeof gateways.limit === 'number'); + assert(typeof gateways.hasMore === 'boolean'); + if (gateways.nextCursor) { + assert(typeof gateways.nextCursor === 'string'); + } + assert(Array.isArray(gateways.items)); + gateways.items.forEach((gateway) => { + assert(typeof gateway.gatewayAddress === 'string'); + assert(typeof gateway.observerAddress === 'string'); + assert(typeof gateway.startTimestamp === 'number'); + assert(typeof gateway.operatorStake === 'number'); + assert(typeof gateway.totalDelegatedStake === 'number'); + assert(typeof gateway.settings === 'object'); + assert(typeof gateway.weights === 'object'); + assert(typeof gateway.weights.normalizedCompositeWeight === 'number'); + assert(typeof gateway.weights.compositeWeight === 'number'); + assert(typeof gateway.weights.stakeWeight === 'number'); + assert(typeof gateway.weights.tenureWeight === 'number'); + assert(typeof gateway.weights.observerRewardRatioWeight === 'number'); + assert(typeof gateway.weights.gatewayRewardRatioWeight === 'number'); + if (gateway.vaults?.length > 0) { + gateway.vaults.forEach((vault) => { + assert(typeof vault.balance === 'number'); + assert(typeof vault.startTimestamp === 'number'); + }); + } + }); + }); - it('should be able to get first page of gateways', async () => { - const gateways = await io.getGateways(); - assert.ok(gateways); - assert(gateways.limit === 100); - assert(gateways.sortOrder === 'desc'); - assert(gateways.sortBy === 'startTimestamp'); - assert(typeof gateways.totalItems === 'number'); - assert(typeof gateways.sortBy === 'string'); - assert(typeof gateways.sortOrder === 'string'); - assert(typeof gateways.limit === 'number'); - assert(typeof gateways.hasMore === 'boolean'); - if (gateways.nextCursor) { - assert(typeof gateways.nextCursor === 'string'); - } - assert(Array.isArray(gateways.items)); - gateways.items.forEach((gateway) => { - assert(typeof gateway.gatewayAddress === 'string'); - assert(typeof gateway.observerAddress === 'string'); - assert(typeof gateway.startTimestamp === 'number'); - assert(typeof gateway.operatorStake === 'number'); - assert(typeof gateway.totalDelegatedStake === 'number'); - assert(typeof gateway.settings === 'object'); - assert(typeof gateway.weights === 'object'); - assert(typeof gateway.weights.normalizedCompositeWeight === 'number'); - assert(typeof gateway.weights.compositeWeight === 'number'); - assert(typeof gateway.weights.stakeWeight === 'number'); - assert(typeof gateway.weights.tenureWeight === 'number'); - assert(typeof gateway.weights.observerRewardRatioWeight === 'number'); - assert(typeof gateway.weights.gatewayRewardRatioWeight === 'number'); + it('should be able to get a single gateway', async () => { + const gateway = await io.getGateway({ + address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + }); + assert.ok(gateway); }); - }); - it('should be able to get a specific page of gateways', async () => { - const gateways = await io.getGateways({ - cursor: 1000000, - limit: 1, - sortBy: 'operatorStake', - sortOrder: 'desc', - }); - assert.ok(gateways); - assert(gateways.limit === 1); - assert(gateways.sortOrder === 'desc'); - assert(gateways.sortBy === 'operatorStake'); - assert(typeof gateways.totalItems === 'number'); - assert(typeof gateways.sortBy === 'string'); - assert(typeof gateways.sortOrder === 'string'); - assert(typeof gateways.limit === 'number'); - assert(typeof gateways.hasMore === 'boolean'); - if (gateways.nextCursor) { - assert(typeof gateways.nextCursor === 'string'); - } - assert(Array.isArray(gateways.items)); - gateways.items.forEach((gateway) => { - assert(typeof gateway.gatewayAddress === 'string'); - assert(typeof gateway.observerAddress === 'string'); - assert(typeof gateway.startTimestamp === 'number'); - assert(typeof gateway.operatorStake === 'number'); - assert(typeof gateway.totalDelegatedStake === 'number'); - assert(typeof gateway.settings === 'object'); - assert(typeof gateway.weights === 'object'); - assert(typeof gateway.weights.normalizedCompositeWeight === 'number'); - assert(typeof gateway.weights.compositeWeight === 'number'); - assert(typeof gateway.weights.stakeWeight === 'number'); - assert(typeof gateway.weights.tenureWeight === 'number'); - assert(typeof gateway.weights.observerRewardRatioWeight === 'number'); - assert(typeof gateway.weights.gatewayRewardRatioWeight === 'number'); + it('should be able to get gateway delegates', async () => { + const delegates = await io.getGatewayDelegates({ + address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + limit: 1, + sortBy: 'startTimestamp', + sortOrder: 'desc', + }); + assert.ok(delegates); + assert(delegates.limit === 1); + assert(delegates.sortOrder === 'desc'); + assert(delegates.sortBy === 'startTimestamp'); + assert(typeof delegates.totalItems === 'number'); + assert(typeof delegates.sortBy === 'string'); + assert(typeof delegates.sortOrder === 'string'); + assert(typeof delegates.limit === 'number'); + assert(typeof delegates.hasMore === 'boolean'); + if (delegates.nextCursor) { + assert(typeof delegates.nextCursor === 'string'); + } + assert(Array.isArray(delegates.items)); + delegates.items.forEach((delegate) => { + assert(Array.isArray(delegate.vaults)); + assert(typeof delegate.delegatedStake === 'number'); + assert(typeof delegate.startTimestamp === 'number'); + assert(typeof delegate.address === 'string'); + }); }); - }); - it('should be able to get a single gateway', async () => { - const gateways = await io.getGateway({ - address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + it('should be able to get gateway delegate allow list', async () => { + const allowList = await io.getGatewayDelegateAllowList({ + address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + limit: 1, + sortBy: 'startTimestamp', + sortOrder: 'desc', + }); + assert.ok(allowList); + // note: sortBy is omitted because it's not supported for by this contract handler, the result is an array of addresses + assert(allowList.limit === 1); + assert(allowList.sortOrder === 'desc'); + assert(typeof allowList.totalItems === 'number'); + assert(typeof allowList.sortOrder === 'string'); + assert(typeof allowList.limit === 'number'); + assert(typeof allowList.hasMore === 'boolean'); + if (allowList.nextCursor) { + assert(typeof allowList.nextCursor === 'string'); + } + assert(Array.isArray(allowList.items)); + allowList.items.forEach((address) => { + assert(typeof address === 'string'); + }); }); - assert.ok(gateways); - }); - it('should be able to get balances, defaulting to first page', async () => { - const balances = await io.getBalances(); - assert.ok(balances); - assert(balances.limit === 100); - assert(balances.sortOrder === 'desc'); - assert(balances.sortBy === 'balance'); - assert(typeof balances.totalItems === 'number'); - assert(typeof balances.sortBy === 'string'); - assert(typeof balances.sortOrder === 'string'); - assert(typeof balances.limit === 'number'); - assert(typeof balances.hasMore === 'boolean'); - if (balances.nextCursor) { - assert(typeof gateways.nextCursor === 'string'); - } - assert(Array.isArray(balances.items)); - balances.items.forEach((wallet) => { - assert(typeof wallet.address === 'string'); - assert(typeof wallet.balance === 'number'); + it('should be able to get balances, defaulting to first page', async () => { + const balances = await io.getBalances(); + assert.ok(balances); + assert(balances.limit === 100); + assert(balances.sortOrder === 'desc'); + assert(balances.sortBy === 'balance'); + assert(typeof balances.totalItems === 'number'); + assert(typeof balances.sortBy === 'string'); + assert(typeof balances.sortOrder === 'string'); + assert(typeof balances.limit === 'number'); + assert(typeof balances.hasMore === 'boolean'); + if (balances.nextCursor) { + assert(typeof gateways.nextCursor === 'string'); + } + assert(Array.isArray(balances.items)); + balances.items.forEach((wallet) => { + assert(typeof wallet.address === 'string'); + assert(typeof wallet.balance === 'number'); + }); }); - }); - it('should be able to get balances of a specific to first page', async () => { - const balances = await io.getBalances({ - cursor: 1000000, - limit: 1, - sortBy: 'address', - sortOrder: 'asc', - }); - assert.ok(balances); - assert(balances.limit === 1); - assert(balances.sortOrder === 'asc'); - assert(balances.sortBy === 'address'); - assert(typeof balances.totalItems === 'number'); - assert(typeof balances.sortBy === 'string'); - assert(typeof balances.sortOrder === 'string'); - assert(typeof balances.limit === 'number'); - assert(typeof balances.hasMore === 'boolean'); - if (balances.nextCursor) { - assert(typeof balances.nextCursor === 'string'); - } - assert(Array.isArray(balances.items)); - balances.items.forEach((wallet) => { - assert(typeof wallet.address === 'string'); - assert(typeof wallet.balance === 'number'); + it('should be able to get balances of a specific to first page', async () => { + const balances = await io.getBalances({ + cursor: 1000000, + limit: 1, + sortBy: 'address', + sortOrder: 'asc', + }); + assert.ok(balances); + assert(balances.limit === 1); + assert(balances.sortOrder === 'asc'); + assert(balances.sortBy === 'address'); + assert(typeof balances.totalItems === 'number'); + assert(typeof balances.sortBy === 'string'); + assert(typeof balances.sortOrder === 'string'); + assert(typeof balances.limit === 'number'); + assert(typeof balances.hasMore === 'boolean'); + if (balances.nextCursor) { + assert(typeof balances.nextCursor === 'string'); + } + assert(Array.isArray(balances.items)); + balances.items.forEach((wallet) => { + assert(typeof wallet.address === 'string'); + assert(typeof wallet.balance === 'number'); + }); }); - }); - it('should be able to get a single balance', async () => { - const balances = await io.getBalance({ - address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + it('should be able to get a single balance', async () => { + const balances = await io.getBalance({ + address: 'QGWqtJdLLgm2ehFWiiPzMaoFLD50CnGuzZIPEdoDRGQ', + }); + assert.ok(balances); }); - assert.ok(balances); - }); - it('should be able to get prescribed names', async () => { - const prescribedNames = await io.getPrescribedNames(); - assert.ok(prescribedNames); - }); + it('should be able to get prescribed names', async () => { + const prescribedNames = await io.getPrescribedNames(); + assert.ok(prescribedNames); + }); - it('should return the prescribed observers for a given epoch', async () => { - const observers = await io.getPrescribedObservers(); - assert.ok(observers); - for (const observer of observers) { - assert(typeof observer.gatewayAddress === 'string'); - assert(typeof observer.observerAddress === 'string'); - assert(typeof observer.stake === 'number'); - assert(typeof observer.startTimestamp === 'number'); - assert(typeof observer.stakeWeight === 'number'); - assert(typeof observer.tenureWeight === 'number'); - assert(typeof observer.gatewayRewardRatioWeight === 'number'); - assert(typeof observer.observerRewardRatioWeight === 'number'); - assert(typeof observer.compositeWeight === 'number'); - } - }); + it('should return the prescribed observers for a given epoch', async () => { + const observers = await io.getPrescribedObservers(); + assert.ok(observers); + for (const observer of observers) { + assert(typeof observer.gatewayAddress === 'string'); + assert(typeof observer.observerAddress === 'string'); + assert(typeof observer.stake === 'number'); + assert(typeof observer.startTimestamp === 'number'); + assert(typeof observer.stakeWeight === 'number'); + assert(typeof observer.tenureWeight === 'number'); + assert(typeof observer.gatewayRewardRatioWeight === 'number'); + assert(typeof observer.observerRewardRatioWeight === 'number'); + assert(typeof observer.compositeWeight === 'number'); + } + }); - it('should be able to get token cost for leasing a name', async () => { - const tokenCost = await io.getTokenCost({ - intent: 'Buy-Record', - name: 'new-name', - years: 1, + it('should be able to get token cost for leasing a name', async () => { + const tokenCost = await io.getTokenCost({ + intent: 'Buy-Record', + name: 'new-name', + years: 1, + }); + assert.ok(tokenCost); }); - assert.ok(tokenCost); - }); - it('should be able to get token cost for buying a name name', async () => { - const tokenCost = await io.getTokenCost({ - intent: 'Buy-Record', - name: 'new-name', - type: 'permabuy', + it('should be able to get token cost for buying a name name', async () => { + const tokenCost = await io.getTokenCost({ + intent: 'Buy-Record', + name: 'new-name', + type: 'permabuy', + }); + assert.ok(tokenCost); }); - assert.ok(tokenCost); - }); - it('should be able to get registration fees', async () => { - const registrationFees = await io.getRegistrationFees(); - assert(registrationFees); - assert.equal(Object.keys(registrationFees).length, 51); - for (const nameLength of Object.keys(registrationFees)) { - // assert lease is length of 5 - assert(registrationFees[nameLength]['lease']['1'] > 0); - assert(registrationFees[nameLength]['lease']['2'] > 0); - assert(registrationFees[nameLength]['lease']['3'] > 0); - assert(registrationFees[nameLength]['lease']['4'] > 0); - assert(registrationFees[nameLength]['lease']['5'] > 0); - assert(registrationFees[nameLength]['permabuy'] > 0); - } - }); + it('should be able to get registration fees', async () => { + const registrationFees = await io.getRegistrationFees(); + assert(registrationFees); + assert.equal(Object.keys(registrationFees).length, 51); + for (const nameLength of Object.keys(registrationFees)) { + // assert lease is length of 5 + assert(registrationFees[nameLength]['lease']['1'] > 0); + assert(registrationFees[nameLength]['lease']['2'] > 0); + assert(registrationFees[nameLength]['lease']['3'] > 0); + assert(registrationFees[nameLength]['lease']['4'] > 0); + assert(registrationFees[nameLength]['lease']['5'] > 0); + assert(registrationFees[nameLength]['permabuy'] > 0); + } + }); - it('should be able to get current epoch distributions', async () => { - const distributions = await io.getDistributions(); - assert.ok(distributions); - }); + it('should be able to get current epoch distributions', async () => { + const distributions = await io.getDistributions(); + assert.ok(distributions); + }); - it('should be able to get epoch distributions at a specific epoch', async () => { - const distributions = await io.getDistributions({ epochIndex: 0 }); - assert.ok(distributions); - }); + it('should be able to get epoch distributions at a specific epoch', async () => { + const distributions = await io.getDistributions({ epochIndex: 0 }); + assert.ok(distributions); + }); - it('should be able to get current epoch observations', async () => { - const observations = await io.getObservations(); - assert.ok(observations); - }); + it('should be able to get current epoch observations', async () => { + const observations = await io.getObservations(); + assert.ok(observations); + }); - it('should be able to get epoch observations at a specific epoch', async () => { - const observations = await io.getObservations({ epochIndex: 0 }); - assert.ok(observations); - }); + it('should be able to get epoch observations at a specific epoch', async () => { + const observations = await io.getObservations({ epochIndex: 0 }); + assert.ok(observations); + }); - it('should be able to get current demand factor', async () => { - const demandFactor = await io.getDemandFactor(); - assert.ok(demandFactor); - }); + it('should be able to get current demand factor', async () => { + const demandFactor = await io.getDemandFactor(); + assert.ok(demandFactor); + }); - it('should be able to create IOWriteable with valid signers', async () => { - for (const signer of signers) { - const io = IO.init({ signer }); + it('should be able to get current auctions', async () => { + const { items: auctions } = await io.getArNSAuctions(); + assert.ok(auctions); + }); - assert(io instanceof IOWriteable); - } - }); -}); + it('should be able to get a specific auction', async () => { + const { items: auctions } = await io.getArNSAuctions(); + if (auctions.length === 0) { + return; + } + const auction = await io.getArNSAuction({ name: auctions[0].name }); + assert.ok(auction); + }); -describe('ANTRegistry', async () => { - const registry = ANTRegistry.init(); - const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; + it('should be able to get auction prices for an existing auction', async () => { + const { items: auctions } = await io.getArNSAuctions(); + if (auctions.length === 0) { + return; + } + const auctionPrices = await io.getArNSAuctionPrices({ + name: auctions[0].name, + type: 'lease', + years: 1, + }); + assert.ok(auctionPrices); + }); - it('should retrieve ids from registry', async () => { - const affiliatedAnts = await registry.accessControlList({ address }); - assert(Array.isArray(affiliatedAnts.Owned)); - assert(Array.isArray(affiliatedAnts.Controlled)); - }); + it('should be able to create IOWriteable with valid signers', async () => { + for (const signer of signers) { + const io = IO.init({ signer }); + + assert(io instanceof IOWriteable); + } + }); + + // TODO: Make a vault within this test environment's context to cover this + // it('should be able to get a specific vault', async () => { + // const vault = await io.getVault({ + // address: '31LPFYoow2G7j-eSSsrIh8OlNaARZ84-80J-8ba68d8', + // vaultId: 'Dmsrp1YIYUY5hA13euO-pAGbT1QPazfj1bKD9EpiZeo', + // }); + // assert.deepEqual(vault, { + // balance: 1, + // startTimestamp: 1729962428678, + // endTimestamp: 1731172028678, + // }); + // }); + + it('should throw an error when unable to get a specific vault', async () => { + const error = await io + .getVault({ + address: '31LPFYoow2G7j-eSSsrIh8OlNaARZ84-80J-8ba68d8', + vaultId: 'Dmsrp1YIYUY5hA13euO-pAGbT1QPazfj1bKD9EpiZeo', + }) + .catch((e) => e); + assert.ok(error); + assert(error instanceof Error); + assert(error.message === 'Vault-Not-Found'); + }); + + it('should be able to get paginated vaults', async () => { + const vaults = await io.getVaults(); + assert.ok(vaults); + assert(vaults.limit === 100); + assert(vaults.sortOrder === 'desc'); + assert(vaults.sortBy === 'address'); + assert(typeof vaults.totalItems === 'number'); + assert(typeof vaults.sortBy === 'string'); + assert(typeof vaults.sortOrder === 'string'); + assert(typeof vaults.limit === 'number'); + assert(typeof vaults.hasMore === 'boolean'); + if (vaults.nextCursor) { + assert(typeof vaults.nextCursor === 'string'); + } + assert(Array.isArray(vaults.items)); + vaults.items.forEach( + ({ address, vaultId, balance, endTimestamp, startTimestamp }) => { + assert(typeof address === 'string'); + assert(typeof balance === 'number'); + assert(typeof startTimestamp === 'number'); + assert(typeof endTimestamp === 'number'); + assert(typeof vaultId === 'string'); + }, + ); + }); - it('should be able to create AoANTRegistryWriteable with valid signers', async () => { - for (const signer of signers) { - const registry = ANTRegistry.init({ - signer, + it('should be able to get paginated vaults with custom sort', async () => { + const vaults = await io.getVaults({ + sortBy: 'balance', + sortOrder: 'asc', }); - assert(registry instanceof AoANTRegistryWriteable); - } + assert.ok(vaults); + assert(vaults.limit === 100); + assert(vaults.sortOrder === 'asc'); + assert(vaults.sortBy === 'balance'); + assert(typeof vaults.totalItems === 'number'); + assert(typeof vaults.sortBy === 'string'); + assert(typeof vaults.sortOrder === 'string'); + assert(typeof vaults.limit === 'number'); + assert(typeof vaults.hasMore === 'boolean'); + if (vaults.nextCursor) { + assert(typeof vaults.nextCursor === 'string'); + } + assert(Array.isArray(vaults.items)); + vaults.items.forEach( + ({ address, vaultId, balance, endTimestamp, startTimestamp }) => { + assert(typeof address === 'string'); + assert(typeof balance === 'number'); + assert(typeof startTimestamp === 'number'); + assert(typeof endTimestamp === 'number'); + assert(typeof vaultId === 'string'); + }, + ); + }); }); -}); -describe('ANT', async () => { - const processId = 'aWI_dq1JH7facsulLuas1X3l5dkKuWtixcZDYMw9mpg'; - const ant = ANT.init({ - processId, + describe('ANTRegistry', async () => { + const registry = ANTRegistry.init({ + process: new AOProcess({ + processId: ANT_REGISTRY_ID, + ao: aoClient, + }), + }); + const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; + + it('should retrieve ids from registry', async () => { + const affiliatedAnts = await registry.accessControlList({ address }); + assert(Array.isArray(affiliatedAnts.Owned)); + assert(Array.isArray(affiliatedAnts.Controlled)); + }); + + it('should be able to create AoANTRegistryWriteable with valid signers', async () => { + for (const signer of signers) { + const registry = ANTRegistry.init({ + signer, + process: new AOProcess({ + processId: ANT_REGISTRY_ID, + ao: aoClient, + }), + }); + assert(registry instanceof AoANTRegistryWriteable); + } + }); }); - it('should be able to create ANTWriteable with valid signers', async () => { - for (const signer of signers) { - const writeable = ANT.init({ + describe('ANT', async () => { + const processId = 'YcxE5IbqZYK72H64ELoysxiJ-0wb36deYPv55wgl8xo'; + const ant = ANT.init({ + process: new AOProcess({ processId, - signer, - }); + ao: aoClient, + }), + }); - assert(writeable instanceof AoANTWriteable); - } - }); + it('should be able to create ANTWriteable with valid signers', async () => { + for (const signer of signers) { + const nonStrictAnt = ANT.init({ + process: new AOProcess({ + processId, + ao: aoClient, + }), + signer, + }); + const strictAnt = ANT.init({ + process: new AOProcess({ + processId, + ao: aoClient, + }), + signer, + strict: true, + }); + + assert(nonStrictAnt instanceof AoANTWriteable); + assert(strictAnt instanceof AoANTWriteable); + } + }); - it('should be able to get ANT info', async () => { - const info = await ant.getInfo({ processId }); - assert.ok(info); - }); + it('should be able to get ANT info', async () => { + const info = await ant.getInfo(); + assert.ok(info); + }); - it('should be able to get the ANT records', async () => { - const records = await ant.getRecords({ processId }); - assert.ok(records); - }); + it('should be able to get the ANT records', async () => { + const records = await ant.getRecords(); + assert.ok(records); + }); - it('should be able to get a @ record from the ANT', async () => { - const record = await ant.getRecord({ undername: '@' }); - assert.ok(record); - }); + it('should be able to get a @ record from the ANT', async () => { + const record = await ant.getRecord({ undername: '@' }); + assert.ok(record); + }); - it('should be able to get the ANT owner', async () => { - const owner = await ant.getOwner(); - assert.ok(owner); - }); + it('should be able to get the ANT owner', async () => { + const owner = await ant.getOwner(); + assert.ok(owner); + }); - it('should be able to get the ANT name', async () => { - const name = await ant.getName(); - assert.ok(name); - }); + it('should be able to get the ANT name', async () => { + const name = await ant.getName(); + assert.ok(name); + }); - it('should be able to get the ANT ticker', async () => { - const ticker = await ant.getTicker(); - assert.ok(ticker); - }); + it('should be able to get the ANT ticker', async () => { + const ticker = await ant.getTicker(); + assert.ok(ticker); + }); - it('should be able to get the ANT controllers', async () => { - const controllers = await ant.getControllers(); - assert.ok(controllers); - }); + it('should be able to get the ANT controllers', async () => { + const controllers = await ant.getControllers(); + assert.ok(controllers); + }); - it('should be able to get the ANT state', async () => { - const state = await ant.getState(); - assert.ok(state); - }); + it('should be able to get the ANT state', async () => { + const state = await ant.getState(); + assert.ok(state); + }); - it('should be able to get the ANT balance for an address', async () => { - const balance = await ant.getBalance({ - address: '"7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk', + it('should be able to get the ANT balance for an address', async () => { + const balance = await ant.getBalance({ + address: '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk', + }); + assert.notEqual(balance, undefined); }); - assert.notEqual(balance, undefined); - }); - it('should be able to get the ANT balances', async () => { - const balances = await ant.getBalances(); - assert.ok(balances); + it('should be able to get the ANT balances', async () => { + const balances = await ant.getBalances(); + assert.ok(balances); + }); }); }); diff --git a/tests/e2e/esm/yarn.lock b/tests/e2e/esm/yarn.lock index 9a75a12c..f8477428 100644 --- a/tests/e2e/esm/yarn.lock +++ b/tests/e2e/esm/yarn.lock @@ -785,10 +785,10 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.4: - version "6.5.5" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" - integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== +elliptic@^6.5.7: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -1289,11 +1289,11 @@ scrypt-js@3.0.1: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.1.tgz#dc2c86187d48ff2da756f0f7e96417ee03c414b1" + integrity sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.7" node-addon-api "^5.0.0" node-gyp-build "^4.2.0" diff --git a/tests/e2e/web/yarn.lock b/tests/e2e/web/yarn.lock index e3f08cb1..55246d4c 100644 --- a/tests/e2e/web/yarn.lock +++ b/tests/e2e/web/yarn.lock @@ -2327,7 +2327,7 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: +elliptic@^6.5.3, elliptic@^6.5.5: version "6.5.5" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== @@ -2340,6 +2340,19 @@ elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +elliptic@^6.5.7: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -4632,11 +4645,11 @@ scrypt-js@3.0.1: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.1.tgz#dc2c86187d48ff2da756f0f7e96417ee03c414b1" + integrity sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.7" node-addon-api "^5.0.0" node-gyp-build "^4.2.0" diff --git a/tests/unit/ant.test.ts b/tests/unit/ant.test.ts index 97e6ba43..da4910cc 100644 --- a/tests/unit/ant.test.ts +++ b/tests/unit/ant.test.ts @@ -1,36 +1,142 @@ import { strict as assert } from 'node:assert'; import { describe, it } from 'node:test'; +import { z } from 'zod'; -import { isAoANTState } from '../../src/utils/ao.js'; - -const testAoANTState = { - Name: 'TestToken', - Ticker: 'TST', - Denomination: 1, - Owner: ''.padEnd(43, '1'), - Controllers: [''.padEnd(43, '2')], - Records: { - record1: { - transactionId: ''.padEnd(43, '1'), - ttlSeconds: 3600, - }, - }, - Balances: { - [''.padEnd(43, '1')]: 1, - }, - Logo: ''.padEnd(43, '1'), - TotalSupply: 0, - Initialized: true, - ['Source-Code-TX-ID']: ''.padEnd(43, '1'), -}; -describe('ANT', () => { - it('should validate accurate ANT state', () => { - const res = isAoANTState(testAoANTState); - assert.strictEqual(res, true); +import { + AntHandlerNames, + AntInfoSchema, + AntStateSchema, + isAoANTState, +} from '../../src/types/ant.js'; + +const stub_address = 'valid-address'.padEnd(43, '1'); + +describe('ANT Schemas', () => { + it('should validate AntStateSchema', () => { + const validState = { + Name: 'TestToken', + Ticker: 'TST', + Description: 'Test description', + Keywords: ['keyword1', 'keyword2', 'keyword3'], + Denomination: 0, + Owner: stub_address, + Controllers: [stub_address], + Records: { + record1: { + transactionId: stub_address, + ttlSeconds: 3600, + }, + }, + Balances: { + [stub_address]: 1, + }, + Logo: stub_address, + TotalSupply: 1, + Initialized: true, + ['Source-Code-TX-ID']: stub_address, + }; + const invalidState = { + Name: 'TestToken', + Ticker: 'TST', + Description: 'Test description', + Keywords: ['keyword1', 'keyword2', 'keyword3'], + Denomination: 0, + Owner: stub_address, + Controllers: [stub_address], + Records: { + record1: { + transactionId: 'invalid-id', + ttlSeconds: '3600', + }, + }, + Balances: { + [stub_address]: 1, + }, + Logo: stub_address, + TotalSupply: -1, + Initialized: true, + ['Source-Code-TX-ID']: stub_address, + }; + + assert.doesNotThrow(() => AntStateSchema.parse(validState)); + assert.throws(() => AntStateSchema.parse(invalidState), z.ZodError); }); - it('should invalidate inaccurate ANT state', () => { - const res = isAoANTState({ ...testAoANTState, Name: 1 }); - assert.strictEqual(res, false); + it('should validate AntInfoSchema', () => { + const validInfo = { + Name: 'TestToken', + Owner: stub_address, + ['Source-Code-TX-ID']: stub_address, + Ticker: 'TST', + Description: 'Test description', + Keywords: ['keyword1', 'keyword2', 'keyword3'], + ['Total-Supply']: '1', + Logo: stub_address, + Denomination: '0', + Handlers: AntHandlerNames, + }; + const invalidInfo = { + Name: 'TestToken', + Owner: stub_address, + ['Source-Code-TX-ID']: stub_address, + Ticker: 'TST', + ['Total-Supply']: 1000, + Logo: stub_address, + Denomination: '1', + Handlers: AntHandlerNames, + }; + + assert.doesNotThrow(() => AntInfoSchema.parse(validInfo)); + assert.throws(() => AntInfoSchema.parse(invalidInfo), z.ZodError); + }); + + it('should validate isAoANTState', () => { + const validState = { + Name: 'TestToken', + Ticker: 'TST', + Description: 'Test description', + Keywords: ['keyword1', 'keyword2', 'keyword3'], + Denomination: 0, + Owner: stub_address, + Controllers: [stub_address], + Records: { + record1: { + transactionId: stub_address, + ttlSeconds: 3600, + }, + }, + Balances: { + [stub_address]: 1, + }, + Logo: stub_address, + TotalSupply: 0, + Initialized: true, + ['Source-Code-TX-ID']: stub_address, + }; + const invalidState = { + Name: 'TestToken', + Ticker: 'TST', + Description: 'Test description', + Keywords: ['keyword1', 'keyword2', 'keyword3'], + Denomination: 0, + Owner: stub_address, + Controllers: [stub_address], + Records: { + record1: { + transactionId: 'invalid-id', + ttlSeconds: '3600', + }, + }, + Balances: { + [stub_address]: 1, + }, + Logo: stub_address, + TotalSupply: -1, + Initialized: true, + ['Source-Code-TX-ID']: stub_address, + }; + + assert.strictEqual(isAoANTState(validState), true); + assert.strictEqual(isAoANTState(invalidState), false); }); }); diff --git a/tests/unit/token.test.ts b/tests/unit/token.test.ts index 8a4dd88a..b8dc277a 100644 --- a/tests/unit/token.test.ts +++ b/tests/unit/token.test.ts @@ -1,7 +1,7 @@ import { strict as assert } from 'node:assert'; import { describe, it } from 'node:test'; -import { IOToken, mIOToken } from '../../src/token.js'; +import { IOToken, mIOToken } from '../../src/types/token.js'; describe('IOToken', () => { it('should throw an error on invalid input', () => { diff --git a/tests/unit/utils.test.ts b/tests/unit/utils.test.ts new file mode 100644 index 00000000..022329ab --- /dev/null +++ b/tests/unit/utils.test.ts @@ -0,0 +1,66 @@ +import Arweave from 'arweave'; +import { strict as assert } from 'node:assert'; +import { describe, it } from 'node:test'; + +import { + getCurrentBlockUnixTimestampMs, + pruneTags, +} from '../../src/utils/arweave.js'; + +describe('pruneTags', () => { + it('should remove tags with undefined values', () => { + const tags = [ + { name: 'Tag1', value: 'value1' }, + { name: 'Tag2', value: undefined }, + { name: 'Tag3', value: 'value3' }, + { name: 'Tag4', value: undefined }, + ]; + + const prunedTags = pruneTags(tags); + + assert.deepEqual(prunedTags, [ + { name: 'Tag1', value: 'value1' }, + { name: 'Tag3', value: 'value3' }, + ]); + }); + + it('should return empty array when all tags have undefined values', () => { + const tags = [ + { name: 'Tag1', value: undefined }, + { name: 'Tag2', value: undefined }, + ]; + + const prunedTags = pruneTags(tags); + + assert.deepEqual(prunedTags, []); + }); + + it('should return same array when no tags have undefined values', () => { + const tags = [ + { name: 'Tag1', value: 'value1' }, + { name: 'Tag2', value: 'value2' }, + ]; + + const prunedTags = pruneTags(tags); + + assert.deepEqual(prunedTags, tags); + }); + + it('should return empty array with no tags', () => { + const tags: { name: string; value: string | undefined }[] = []; + const prunedTags = pruneTags(tags); + assert.deepEqual(prunedTags, []); + }); +}); + +describe('getCurrentBlockUnixTimestamp', () => { + it('should return the current block timestamp', async () => { + const arweave = Arweave.init({}); + // cheap way to check the returned timestamp is within the boundaries of the async call + const minTimestamp = Date.now(); + const timestamp = await getCurrentBlockUnixTimestampMs(arweave); + const maxTimestamp = Date.now(); + assert.ok(timestamp >= minTimestamp); + assert.ok(timestamp <= maxTimestamp); + }); +}); diff --git a/yarn.lock b/yarn.lock index 3e2df580..d7ea89f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3474,7 +3474,7 @@ elliptic@6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: +elliptic@^6.5.3, elliptic@^6.5.5: version "6.5.5" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== @@ -3487,6 +3487,19 @@ elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +elliptic@^6.5.7: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emoji-regex@^10.3.0: version "10.3.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" @@ -6254,6 +6267,15 @@ nise@^5.1.4: just-extend "^6.2.0" path-to-regexp "^6.2.1" +nock@^13.5.5: + version "13.5.5" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.5.tgz#cd1caaca281d42be17d51946367a3d53a6af3e78" + integrity sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + propagate "^2.0.0" + node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" @@ -7291,6 +7313,11 @@ promzard@^1.0.0: dependencies: read "^3.0.1" +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + proper-lockfile@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" @@ -7753,11 +7780,11 @@ scrypt-js@3.0.1: integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== secp256k1@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.0.tgz#be6f0c8c7722e2481e9773336d351de8cddd12f7" - integrity sha512-TKWX8xvoGHrxVdqbYeZM9w+izTF4b9z3NhSaDkdn81btvuh+ivbIMGT/zQvDtTFWhRlThpoz6LEYTr7n8A5GcA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-5.0.1.tgz#dc2c86187d48ff2da756f0f7e96417ee03c414b1" + integrity sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA== dependencies: - elliptic "^6.5.4" + elliptic "^6.5.7" node-addon-api "^5.0.0" node-gyp-build "^4.2.0"