# Husky (Developers Only) (/ardrive-cli/(build-and-run-from-source)/husky-developers-only) We use husky 6.x to manage the git commit hooks that help to improve the quality of our commits. Please run: ```shell yarn husky install ``` to enable git hooks for your local checkout. Without doing so, you risk committing non-compliant code to the repository. # Install Yarn 3 (/ardrive-cli/(build-and-run-from-source)/install-yarn-3) Both the ArDrive CLI and ArDrive Core JS use Yarn 3 to manage dependencies and initiate workflows, so follow the [yarn installation instructions][yarn-install] in order to get the latest version. In most cases: ```shell brew install yarn npm install -g yarn ``` # Installing and Starting the CLI From Source (/ardrive-cli/(build-and-run-from-source)/installing-and-starting-the-cli-from-source) Now that your runtime and/or development environment is set up, to install the package simply run: ```shell yarn && yarn build ``` And then start the CLI (always from the root of this repository): ```shell yarn ardrive ``` For convenience in the **non-developer case**, you can install the CLI globally on your system by performing the following step: ```shell yarn pack npm install i -g /path/to/package.tgz ardrive ``` # Recommended Visual Studio Code extensions (Developers Only) (/ardrive-cli/(build-and-run-from-source)/recommended-visual-studio-code-extensions-developers-only) To ensure your environment is compatible, we also recommend the following VSCode extensions: - [ES-Lint][eslint-vscode] - [Editor-Config][editor-config-vscode] - [Prettier][prettier-vscode] - [ZipFS][zipfs-vscode] # Using a custom ArDrive-Core-JS (Optional) (/ardrive-cli/(build-and-run-from-source)/using-a-custom-ardrive-core-js-optional) To test a with a custom version of the `ardrive-core-js` library on your local system, change the `"ardrive-core-js"` line in `package.json` to the root of your local `ardrive-core-js` repo: ```diff - "ardrive-core-js": "1.0.0" + "ardrive-core-js": "../ardrive-core-js/" ``` # Dealing With Network Congestion (/ardrive-cli/(other-utility-operations)/dealing-with-network-congestion) Currently, Arweave blocks hold up to 1000 transactions per block. The "mempool", where pending transactions reside until they've been included into a block, will only hold a transaction for 50 blocks (~100-150 minutes) before it's discarded by the network resulting in no fees or data being transacted. During periods of network congestion (i.e. those where the mempool contains 1000 or more pending transactions), it may make sense to either: a) wait for congestion to dissipate before attempting your transactions. b) apply the fee boost multiplier to your transactions rewards with the --boost parameter during write operations in order to front-run some of the congestion. #### Check for network congestion before uploading ```shell ardrive get-mempool ardrive get-mempool | jq 'length' ``` #### Front-run Congestion By Boosting Miner Rewards ```shell ardrive upload-file --wallet-file /path/to/my/wallet.json --parent-folder-id "f0c58c11-430c-4383-8e54-4d864cc7e927" --local-path ./helloworld.txt --boost 1.5 ``` #### Send AR Transactions From a Cold Wallet The best cold wallet storage never exposes your seed phrase and/or private keys to the Internet or a compromised system interface. You can use the ArDrive CLI to facilitate cold storage and transfer of AR. If you need a new cold AR wallet, generate one from an air-gapped machine capable of running the ArDrive CLI by following the instructions in the [Wallet Operations](#wallet-operations) section. Fund your cold wallet from whatever external sources you'd like. NOTE: Your cold wallet won't appear on chain until it has received AR. The workflow to send the AR out from your cold wallet requires you to generate a signed transaction with your cold wallet on your air-gapped machine via the ArDrive CLI, and then to transfer the signed transaction (e.g. by a file on a clean thumb drive) to an Internet-connected machine and send the transaction to the network via the ArDrive CLI. You'll need two inputs from the Internet-connected machine: - the last transaction sent OUT from the cold wallet (or an empty string if none has ever been sent out) - the base fee for an Arweave transaction (i.e. a zero bye transaction). Note that this value could change if a sufficient amount of time passes between the time you fetch this value, create the transaction, and send the transaction. To get the last transaction sent from your cold wallet, use the `last-tx` command and specify your wallet address e.g.: ``` ardrive last-tx -a \ ``` To get the base transaction reward required for an AR transaction, use the `base-reward` function, optionally applying a reward boost multiple if you're looking to front-run network congestion: ``` ardrive base-reward --boost 1.5 ``` Write down or securely copy the values you derived from the Internet-connected machine and run the following commands on the airgapped machine, piping the outputted signed transaction data to a file in the process, e.g. `sendme.json` (if that's your signed transaction transfer medium preference): ``` ardrive create-tx -w /path/to/wallet/file.json -d \ -a \ --last-tx \ --reward "\" > sendme.json ``` Transport your signed transaction to the Internet-connected machine and run the following command to send your transaction to the Arweave network: ``` ardrive send-tx -x /path/to/sendme.json ``` # Monitoring Transactions (/ardrive-cli/(other-utility-operations)/monitoring-transactions) Block time on Arweave is typically between 2-3 minutes in duration, so transactions can be mined within that time frame when [network congestion](#dealing-with-network-congestion) is low. Transactions, in the general case, proceed through the following set of states: - Pending: the transaction is waiting the "mempool" to be mined - Confirming: the transaction was mined on an Arweave Node, but has not yet been confirmed by at least 15 total nodes on the network - Confirmed: the transaction was mined on an Arweave Node and confirmed by at least 15 total nodes on the network - Not Found: the transaction is not available for any of the following reasons: - Insufficient reward to join the mempool - Insufficient reward to be mined within 50 blocks during a period of network congestion - Transaction is transitioning between states - Transaction ID is invalid Monitor any Arweave transaction's status via its transaction ID by performing: ```shell ardrive tx-status -t "ekSMckikdRJ8RGIkFa-X3xq3427tvM7J9adv8HP3Bzs" ``` Example output: ```shell ekSMckikdRJ8RGIkFa-X3xq3427tvM7J9adv8HP3Bzs: Mined at block height 775810 with 22439 confirmations ``` ```shell watch -n 10 ardrive tx-status -t "ekSMckikdRJ8RGIkFa-X3xq3427tvM7J9adv8HP3Bzs" ``` # Persistent Caching of ArFS Entity Metadata (/ardrive-cli/(other-utility-operations)/persistent-caching-of-arfs-entity-metadata) To avoid redundant requests to the Arweave network for immutable ArFS entity metadata, a persistent file cache is created and maintained at: ``` Windows: /ardrive-caches/metadata Non-Windows: /.ardrive/caches/metadata ``` The `XDG_CACHE_HOME` environment variable is honored, where applicable, and will be used in place of `os.homedir()` in the scenarios described above. Metadata cache logging to stderr can be enabled by setting the `ARDRIVE_CACHE_LOG` environment variable to `1`. Cache performance is UNDEFINED for multi-process scenarios, but is presumed to be generally usable. The cache can be manually cleared safely at any time that any integrating app is not in operation. ```shell █████╗ ██████╗ ██████╗ ██████╗ ██╗██╗ ██╗███████╗ ██╔══██╗██╔══██╗██╔══██╗██╔══██╗██║██║ ██║██╔════╝ ███████║██████╔╝██║ ██║██████╔╝██║██║ ██║█████╗ ██╔══██║██╔══██╗██║ ██║██╔══██╗██║╚██╗ ██╔╝██╔══╝ ██║ ██║██║ ██║██████╔╝██║ ██║██║ ╚████╔╝ ███████╗ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚══════╝ ██████╗██╗ ██╗ ██╔════╝██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║ ╚██████╗███████╗██║ ╚═════╝╚══════╝╚═╝ Write ArFS =========== create-drive create-folder upload-file create-manifest pin-file create-snapshot hide-file unhide-file hide-folder unhide-folder move-file move-folder retry-tx Read ArFS =========== file-info folder-info drive-info list-folder list-drive list-all-drives download-file download-folder download-drive Wallet Ops =========== generate-seedphrase generate-wallet get-address get-balance send-ar get-drive-key get-file-key last-tx Arweave Ops =========== base-reward get-mempool create-tx send-tx tx-status ardrive \ --help ``` [ArDrive Community Discord][ardrive-discord] [ardrive]: https://ardrive.io [arweave]: https://ardrive.io/what-is-arweave/ [ardrive-github]: https://github.com/ardriveapp/ [arfs]: https://ardrive.atlassian.net/l/c/m6P1vJDo [ardrive-web-app]: https://app.ardrive.io [ardrive-core]: https://github.com/ardriveapp/ardrive-core-js [yarn-install]: https://yarnpkg.com/getting-started/install [nvm-install]: https://github.com/nvm-sh/nvm#installing-and-updating [wsl-install]: https://code.visualstudio.com/docs/remote/wsl [editor-config-vscode]: https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig [prettier-vscode]: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode [zipfs-vscode]: https://marketplace.visualstudio.com/items?itemName=arcanis.vscode-zipfs [eslint-vscode]: https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint [viewblock blockchain explorer]: https://viewblock.io/arweave/ [ardrive-discord]: https://discord.com/invite/ya4hf2H [arconnect]: https://arconnect.io/ [kb-wallets]: https://ardrive.atlassian.net/l/c/FpK8FuoQ [arweave-manifests]: https://github.com/ArweaveTeam/arweave/wiki/Path-Manifests [example-manifest-webpage]: https://arweave.net/qozq9YIUPEHfZhoTp9DkBpJuA_KNULBnfLiMroj5pZI [arlocal]: https://github.com/textury/arlocal [mozilla-mime-types]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types [viewblock]: https://viewblock.io/arweave/ [tx_anchors]: https://docs.arweave.org/developers/server/http-api#field-definitions [gql-guide]: https://gql-guide.vercel.app/#owners [ardrive-turbo]: https://ardrive.io/turbo/ # Using a Custom Arweave Gateway (/ardrive-cli/(other-utility-operations)/using-a-custom-arweave-gateway) On each command that uses a gateway, it is possible to supply your own custom Arweave gateway using the flag `--gateway` or by setting an environment variable named `ARWEAVE_GATEWAY`. For example, you could test out that your ArFS transactions are working as expected on a local test network such as [ArLocal] with this flow: ```shell npx arlocal curl http://localhost:1984/mint/{ your public wallet address }/99999999999999 ardrive create-drive --gateway http://127.0.0.1:1984 -w /path/to/wallet -n 'my-test-drive' curl "$ARWEAVE_GATEWAY/mine" ardrive upload-file -F { root folder id from create drive } -l /path/to/file -w /path/to/wallet curl "$ARWEAVE_GATEWAY/mine" ardrive list-drive -d { drive id from create drive } ardrive download-file -f { file id from upload file } ``` # Git (/ardrive-cli/(prerequisites)/git) Some of ArDrive's dependencies are transitively installed via Git. Install it, if necessary, and ensure that it's available within your terminal environment: [Download Git](https://git-scm.com/downloads) # NVM (Optional - Recommended) (/ardrive-cli/(prerequisites)/nvm-optional-recommended) This project uses the Node Version Manager (NVM) and an `.nvmrc` file to lock the recommended Node version used by the latest version of `ardrive-core-js`. **Note for Windows: We recommend using WSL for setting up NVM on Windows using the [instructions described here][wsl-install]** Follow these steps to get NVM up and running on your system: 1. Install NVM using [these installation instructions][nvm-install]. 2. Navigate to this project's root directory 3. Ensure that the correct version of Node is installed by performing: `nvm install` 4. Use the correct version of Node, by performing: `nvm use` **IT IS STRONGLY RECOMMENDED THAT YOU AVOID GENERATING WALLETS VIA SEED PHRASE WITH THE CLI USING ANY NODE VERSION OTHER THAN THE ONE SPECIFIED IN `.nvmrc`.** # Creating Drives (/ardrive-cli/(working-with-drives)/creating-drives) ```shell ardrive create-drive --wallet-file /path/to/my/wallet.json --drive-name "My Public Archive" ardrive create-drive --wallet-file /path/to/my/wallet.json --drive-name "Teenage Love Poetry" -P ``` # List Drive Pipeline Examples (/ardrive-cli/(working-with-drives)/list-drive-pipeline-examples) You can utilize `jq` and the list commands to reshape the commands' output data into useful forms and stats for many use cases. Here are a few examples: ```shell ardrive list-drive -d a44482fd-592e-45fa-a08a-e526c31b87f1 | jq '.[] | select(.entityType == "file") | "https://app.ardrive.io/#/file/" + .entityId + "/view"' ``` Example output: ```shell "https://app.ardrive.io/#/file/1337babe-f000-dead-beef-ffffffffffff/view" "https://app.ardrive.io/#/file/cdbc9ddd-1cab-41d9-acbd-fd4328929de3/view" "https://app.ardrive.io/#/file/f19bc712-b57a-4e0d-8e5c-b7f1786b34a1/view" "https://app.ardrive.io/#/file/4f8e081b-42f2-442d-be41-57f6f906e1c8/view" "https://app.ardrive.io/#/file/0e02d254-c853-4ff0-9b6e-c4d23d2a95f5/view" "https://app.ardrive.io/#/file/c098b869-29d1-4a86-960f-a9e10433f0b0/view" "https://app.ardrive.io/#/file/4afc8cdf-4d27-408a-bfb9-0a2ec21eebf8/view" "https://app.ardrive.io/#/file/85fe488d-fcf7-48ca-9df8-2b39958bbf15/view" ... ``` ```shell ardrive list-drive -d 13c3c232-6687-4d11-8ac1-35284102c7db | jq ' map(select(.entityType == "file") | .size) | add' ``` ```shell ardrive list-drive -d 01ea6ba3-9e58-42e7-899d-622fd110211c | jq '[ .[] | select(.entityType == "file") ] | length' ``` # Listing Drives for an Address (/ardrive-cli/(working-with-drives)/listing-drives-for-an-address) You can list all the drives associated with any Arweave wallet address, though the details of private drives will be obfuscated from you unless you provide the necessary decryption data. ```shell ardrive list-all-drives -w /path/to/my/wallet.json -P ardrive list-all-drives --address "HTTn8F92tR32N8wuo-NIDkjmqPknrbl10JWo5MZ9x2k" ``` # Listing Every Entity in a Drive (/ardrive-cli/(working-with-drives)/listing-every-entity-in-a-drive) Useful notes on listing the contents of drives: - Listing a drive is effectively the same as listing its root folder. - You can control the tree depth of the data returned. - path, txPath, and entityIdPath properties on entities can provide useful handholds for other forms of data navigation ```shell ardrive list-drive -d "c7f87712-b54e-4491-bc96-1c5fa7b1da50" -w /path/to/my/wallet.json -P ardrive list-drive -d "c7f87712-b54e-4491-bc96-1c5fa7b1da50" -w /path/to/my/wallet.json -P --with-keys ardrive list-drive -d "c7f87712-b54e-4491-bc96-1c5fa7b1da50" --max-depth 2 ``` # Managing Drive Passwords (/ardrive-cli/(working-with-drives)/managing-drive-passwords) The ArDrive CLI's private drive and folder functions all require either a drive password OR a drive key. Private file functions require either the drive password or the file key. **Keys and passwords are sensitive data, so manage the entry, display, storage, and transmission of them very carefully.** Drive passwords are the most portable, and fundamental, encryption facet, so a few options are available during private drive operations for supplying them: - Environment Variable - STDIN - Secure Prompt #### Supplying Your Password: Environment Variable ```shell read -rs TMP_ARDRIVE_PW ardrive \ -w /path/to/wallet.json -P ``` #### Supplying Your Password: STDIN ```shell cat /path/to/my/drive/password.txt | ardrive \ -w /path/to/wallet.json -P ardrive \ -w /path/to/wallet.json -P -w /path/to/wallet.json -P ? Enter drive password: › ******** ``` # Understanding Drive and File Keys (/ardrive-cli/(working-with-drives)/understanding-drive-and-file-keys) Private Drives achieve privacy via end-to-end encryption facilitated by hash-derived "Keys". Drive Keys encrypt/decrypt Drive and Folder data, and File Keys encrypt/decrypt File Data. The relationships among your data and their keys is as follows: - Drive Key = functionOf(Wallet Signature, Randomly Generated Drive ID, User-specified Drive Password) - File Key = functionOf(Randomly Generated File ID, Drive Key) When you create private entities, the returned JSON data from the ArDrive CLI will contain the keys needed to decrypt the encrypted representation of your entity that is now securely and permanently stored on the blockweave. To derive the drive key again for a drive, perform the following: ```shell ardrive get-drive-key -w /path/to/my/wallet.json -d "6939b9e0-cc98-42cb-bae0-5888eca78885" -P ``` To derive the file key again for a file, perform the following: ```shell ardrive get-file-key --file-id "bd2ce978-6ede-4b0d-8f79-2d7bc235a0e0" --drive-id "6939b9e0-cc98-42cb-bae0-5888eca78885" --drive-key "yHdCjpCK3EcuhQcKNx2d/NN5ReEjoKfZVqKunlCnPEo" ``` # Understanding Drive Hierarchies (/ardrive-cli/(working-with-drives)/understanding-drive-hierarchies) At the root of every data tree is a "Drive" entity. When a drive is created, a Root Folder is also created for it. The entity IDs for both are generated and returned when you create a new drive: ```shell ardrive create-drive --wallet-file /path/to/my/wallet.json --drive-name "Teenage Love Poetry" | tee created_drive.json | jq '[.created[] | del(.metadataTxId, .entityName, .bundledIn)]' [ { "type": "drive", "entityId": "6939b9e0-cc98-42cb-bae0-5888eca78885" } { "type": "folder", "entityId": "d1535126-fded-4990-809f-83a06f2a1118" } ] ``` The relationship between the drive and its root folder is clearly visible when retrieving the drive's info: ```shell ardrive drive-info -d "6939b9e0-cc98-42cb-bae0-5888eca78885" | jq '{driveId, rootFolderId}' { "driveId": "6939b9e0-cc98-42cb-bae0-5888eca78885", "rootFolderId": "d1535126-fded-4990-809f-83a06f2a1118" } ``` All file and folder entities in the drive will be anchored to it by a "Drive-ID" GQL Tag. And they'll each be anchored to a parent folder ID, tracked via the "Parent-Folder-ID" GQL tag, forming a tree structure whose base terminates at the Root Folder. # Dry Run (/ardrive-cli/(working-with-entities)/dry-run) An important feature of the ArDrive CLI is the `--dry-run` flag. On each command that would write an ArFS entity, there is the option to run it as a "dry run". This will run all of the steps and print the outputs of a regular ArFS write, but will skip sending the actual transaction: ```shell ardrive \ \ --dry-run ``` This can be very useful for gathering price estimations or to confirm that you've copy-pasted your entity IDs correctly before committing to an upload. # Uploading to Turbo (BETA) (/ardrive-cli/(working-with-entities)/uploading-to-turbo-beta) Users can optionally choose to send each ArFS entities created to [ArDrive Turbo][ardrive-turbo] using the `--turbo` flag. Instead of using AR from an Arweave wallet, you can use Turbo Credits or take advantage of free/discounted upload promotions. ```shell ardrive \ \ --turbo ``` This flag will skip any balance check on the CLI side. Turbo will check a user's balance and accept/reject a data item at the time of upload. The `--turbo` flag by default will send your files to `upload.ardrive.io` to be bundled. To change the Turbo destination, users can use the `--turbo-url` flag. # Creating a Snapshot (/ardrive-cli/(working-with-files)/creating-a-snapshot) A **snapshot** is a single Arweave transaction, tagged `Entity-Type: snapshot`, `Drive-Id`, `Block-Start`, and `Block-End`, whose body is a JSON index of every ArFS entity metadata transaction (drive, folder, and file revisions) mined for that drive across the block range it covers. It exists purely as a read-path optimization: a client that wants to list a drive's full entity history can read the snapshot's JSON body directly instead of paginating through and re-fetching every individual metadata transaction the drive has ever produced. `create-snapshot` builds this snapshot for you and posts it to Arweave. Some important things to know: - **Costs to post, like any other data transaction.** For a drive with a long entity history the snapshot body can be large, so posting it is not free -- `create-snapshot` estimates the cost up front, asserts your wallet can cover it, and prints the cost before sending. - **When to use it.** Snapshotting is most useful for drives with a large number of files/folders/revisions, where clients that support snapshot-accelerated listing would otherwise have to replay a long transaction history on every listing. It's a maintenance operation you run occasionally (e.g. periodically, or before publishing a drive expected to see heavy read traffic) -- not something every drive needs. - **Public drives only (for now).** Private drive snapshots are not yet supported. - **Idempotent-ish, not automatic.** Each run creates a NEW snapshot transaction covering the drive's entity history at that point in time; it does not update or replace a previous snapshot. ```shell ardrive create-snapshot --drive-id "bc9af866-6421-40f1-ac89-202bddb5c487" -w "/path/to/wallet" ``` Use `--dry-run` to see the block range, entity count, byte size, and estimated cost without posting anything: ```shell ardrive create-snapshot --drive-id "bc9af866-6421-40f1-ac89-202bddb5c487" -w "/path/to/wallet" --dry-run ``` Like other write commands, `create-snapshot` supports `--boost`, `--turbo`/`--turbo-url`, and `--gateway`. See `ardrive create-snapshot --help` for the full flag list. # Download a Single file (BETA) (/ardrive-cli/(working-with-files)/download-a-single-file-beta) By using the `download-file` command you can download a file on chain to a folder in your local storage specified by --local-path (or to your current working directory if not specified): ```shell ardrive download-file -w /path/to/wallet.json --file-id "ff450770-a9cb-46a5-9234-89cbd9796610" --local-path /my_ardrive_downloads/ ``` Specify a filename in the --local-path if you'd like to use a different name than the one that's used in your drive: ```shell ardrive download-file -w /path/to/wallet.json --file-id "ff450770-a9cb-46a5-9234-89cbd9796610" --local-path /my_ardrive_downloads/my_pic.png ``` # Downloading a Drive (/ardrive-cli/(working-with-files)/downloading-a-drive) To download the whole drive you can use the `download-drive` command. ```shell ardrive download-drive -d "c0c8ba1c-efc5-420d-a07c-a755dc67f6b2" ``` This is equivalent to running the `download-folder` command against the root folder of the drive. # Downloading a Folder with Files (/ardrive-cli/(working-with-files)/downloading-a-folder-with-files) You can download a folder from ArDrive to your local machine with the `download-folder` command. In the following examples, assume that a folder with ID "47f5bde9-61ba-49c7-b409-1aa4a9e250f6" exists in your drive and is named "MyArDriveFolder". ```shell ardrive download-folder -f "47f5bde9-61ba-49c7-b409-1aa4a9e250f6" ``` By specifying the `--local-path` option, you can choose the local parent folder into which the on-chain folder will be downloaded. When the parameter is omitted, its value defaults to the current working directory (i.e. `./`). ```shell ardrive download-folder -f "47f5bde9-61ba-49c7-b409-1aa4a9e250f6" --local-path /my_ardrive_downloads/ ``` The `--max-depth` parameter lets you to choose a custom folder depth to download. When omitted, the entire subtree of the folder will be downloaded. In the following example, only the immediate children of the folder will be downloaded: ```shell ardrive download-folder -f "47f5bde9-61ba-49c7-b409-1aa4a9e250f6" --max-depth 0 ``` The behaviors of `--local-path` are similar to those of `cp` and `mv` in Unix systems, e.g.: ```shell ardrive download-folder -f "47f5bde9-61ba-49c7-b409-1aa4a9e250f6" --local-path "/existing_folder" ardrive download-folder -f "47f5bde9-61ba-49c7-b409-1aa4a9e250f6" --local-path "/existing_folder/MyArDriveFolder" ardrive download-folder -f "47f5bde9-61ba-49c7-b409-1aa4a9e250f6" --local-path "/existing_folder/non_existent_folder" ardrive download-folder -f "47f5bde9-61ba-49c7-b409-1aa4a9e250f6" --local-path "/non_existent_folder_1/non_existent_folder_2" ``` # Fetching the Metadata of a File Entity (/ardrive-cli/(working-with-files)/fetching-the-metadata-of-a-file-entity) Simply perform the file-info command to retrieve the metadata of a file: ```shell ardrive file-info --file-id "e5ebc14c-5b2d-4462-8f59-7f4a62e7770f" ``` Example output: ```shell { "appName": "ArDrive-Web", "appVersion": "0.1.0", "arFS": "0.11", "contentType": "application/json", "driveId": "51062487-2e8b-4af7-bd81-4345dc28ea5d", "entityType": "file", "name": "2_depth.png", "txId": "CZKdjqwnmxbWchGA1hjSO5ZH--4OYodIGWzI-FmX28U", "unixTime": 1633625081, "size": 41946, "lastModifiedDate": 1605157729000, "parentFolderId": "a2c8a0cb-0ca7-4dbb-8bf8-93f75f308e63", "entityId": "e5ebc14c-5b2d-4462-8f59-7f4a62e7770f", "fileId": "e5ebc14c-5b2d-4462-8f59-7f4a62e7770f", "dataTxId": "Jz0WsWyAGVc0aE3UzACo-YJqG8OPrN3UucmDdt8Fbjc", "dataContentType": "image/png" } ``` # Hiding and Unhiding a File or Folder (/ardrive-cli/(working-with-files)/hiding-and-unhiding-a-file-or-folder) The `hide-file`, `unhide-file`, `hide-folder`, and `unhide-folder` commands let you toggle whether a file or folder entity is flagged as hidden, without touching its data or metadata otherwise. Hiding writes a new metadata revision with an `isHidden` flag set to `true`; clients that respect this flag (e.g. the ArDrive web/desktop apps) omit the entity from their normal drive listings, while it remains fully present on-chain. Unhiding writes another revision flipping the flag back to `false`. Some important things to know: - **Reversible.** Hiding never deletes or re-uploads data -- it's a metadata-only toggle, and `unhide-file`/`unhide-folder` fully restores visibility at any time. - **Works on both public and private entities.** Pass `--drive-key` or (`--wallet-file`/`--seed-phrase` plus `--unsafe-drive-password`) to target a private file/folder; omit them to target a public one, exactly like `rename-file`/`rename-folder`. - **Costs a small metadata fee.** Like a rename, hiding/unhiding writes a new metadata revision to Arweave, so it isn't free, but it's the same tiny metadata-only cost as any other rename/move operation -- no file data is re-uploaded. - **Not recursive.** Hiding a folder flags only that folder's own metadata; it does not walk its contents and hide child files/folders individually. ```shell ardrive hide-file --file-id "290a3f9a-37b2-4f0f-a899-6fac983833b3" -w "/path/to/wallet.json" ardrive unhide-file --file-id "290a3f9a-37b2-4f0f-a899-6fac983833b3" -w "/path/to/wallet.json" ardrive hide-file --file-id "290a3f9a-37b2-4f0f-a899-6fac983833b3" -w "/path/to/wallet.json" --unsafe-drive-password "p4ssw0rd" ardrive hide-folder --folder-id "568d5eba-dbf3-4a49-8129-1c58f7fd35bc" -w "/path/to/wallet.json" ardrive unhide-folder --folder-id "568d5eba-dbf3-4a49-8129-1c58f7fd35bc" -w "/path/to/wallet.json" --drive-key "base64EncodedDriveKey" ``` Like other write commands, `hide-file`/`unhide-file`/`hide-folder`/`unhide-folder` support `--dry-run`, `--boost`, `--turbo`/`--turbo-url`, and `--gateway`. See `ardrive hide-file --help` (and `unhide-file`/`hide-folder`/`unhide-folder --help`) for the full flag list. # IPFS CID Tagging (/ardrive-cli/(working-with-files)/ipfs-cid-tagging) Certain nodes on the Arweave network may be running the [IPFS+Arweave bridge](https://arweave.medium.com/arweave-ipfs-persistence-for-the-interplanetary-file-system-9f12981c36c3). Tagging your file upload transaction with its IPFS v1 CID value in the 'IPFS-Add' tag may allow you to take advantage of this system. It can also be helpful for finding data on Arweave via GQL based on its CID. To include the CID tag on your **PUBLIC** file uploads, you may use the '--add-ipfs-tag' flag: ```shell ardrive upload-file --add-ipfs-tag --local-path /path/to/file.txt --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json ``` # Moving Files (/ardrive-cli/(working-with-files)/moving-files) Files can be moved from one folder to another within the same drive. Moving a file is simply the process of uploading a new file metadata revision with an updated File ID Parent Folder ID relationship. The following command will move a file from its current location in a public drive to a new parent folder in that drive: ```shell ardrive move-file --file-id "e5ebc14c-5b2d-4462-8f59-7f4a62e7770f" --parent-folder-id "a2c8a0cb-0ca7-4dbb-8bf8-93f75f308e63" ``` # Name Conflict Resolution on Upload (/ardrive-cli/(working-with-files)/name-conflict-resolution-on-upload) By default, the `upload-file` command will use the upsert behavior if existing entities are encountered in the destination folder tree that would cause naming conflicts. Expect the behaviors from the following table for each of these resolution settings: | Source Type | Conflict at Dest | `skip` | `replace` | `upsert` (default) | | ----------- | ---------------- | ------ | --------- | ------------------ | | File | None | Insert | Insert | Insert | | File | Matching File | Skip | Update | Skip | | File | Different File | Skip | Update | Update | | File | Folder | Skip | Fail | Fail | | Folder | None | Insert | Insert | Insert | | Folder | File | Skip | Fail | Fail | | Folder | Folder | Re-use | Re-use | Re-use | The default upsert behavior will check the destination folder for a file with a conflicting name. If no conflicts are found, it will insert (upload) the file. In the case that there is a FILE to FILE name conflict found, it will only update it if necessary. To determine if an update is necessary, upsert will compare the last modified dates of conflicting file and the file being uploaded. When they are matching, the upload will be skipped. Otherwise the file will be updated as a new revision. To override the upsert behavior, use the `--replace` option to always make new revisions of a file or the `--skip` option to always skip the upload on name conflicts: ```shell ardrive upload-file --replace --local-path /path/to/file.txt --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json ``` ```shell ardrive upload-file --skip --local-path /path/to/file.txt --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json ``` Alternatively, the upload-file commands now also supports the `--ask` conflict resolution option. This setting will always provide an interactive prompt on name conflicts that allows users to decide how to resolve each conflict found: ```shell ardrive upload-file --ask --local-file-path /path/to/file.txt --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json Destination folder has a file to file name conflict! File name: 2.png File ID: efbc0370-b69f-44d9-812c-0d272b019027 This file has a DIFFERENT last modified date Please select how to proceed: › - Use arrow-keys. Return to submit. ❯ Replace as new file revision Upload with a different file name Skip this file upload ``` # Pinning a File (/ardrive-cli/(working-with-files)/pinning-a-file) Pinning lets you reference an **existing** Arweave data transaction as a new file entity in one of your PUBLIC drives, without re-uploading any data. This is useful for adopting data that already lives permanently on Arweave (e.g. a transaction uploaded outside of ArDrive, or one belonging to someone else) into your drive's folder structure, so it shows up alongside your other files with its own name, metadata, and location. Because a pinned file's metadata transaction only references the existing `--tx-id` (it doesn't touch the underlying data bytes), pinning a small file costs the same tiny metadata-only fee as any other file operation -- there is no data-upload cost, regardless of the size of the original file. Some important constraints: - **Public drives only.** Pinning writes a plaintext ArFS metadata transaction that points at the referenced data. Private drives are not supported -- targeting a private `--parent-folder-id` fails with a clear error. - **The referenced transaction is never re-uploaded or modified.** Only a new file metadata entity is created; `--tx-id` is reused as-is as the new file's data transaction. - **Name conflicts throw by default.** If `--dest-file-name` already exists in the destination folder, the command fails unless `--skip` is provided, in which case the command exits successfully having made no changes. ```shell ardrive pin-file --parent-folder-id "a2c8a0cb-0ca7-4dbb-8bf8-93f75f308e63" --tx-id "Y7GFF8r9y0MEU_oi1aZeD87vrmai97JdRQ2L0cbGJ68" --dest-file-name "hello_world.txt" -w "/path/to/wallet" ``` `--drive-id` is optional -- the destination drive is normally resolved automatically from `--parent-folder-id`. Supply it only if you want the command to assert that the folder belongs to the drive you expect (the command fails if it doesn't): ```shell ardrive pin-file --parent-folder-id "a2c8a0cb-0ca7-4dbb-8bf8-93f75f308e63" --drive-id "bc9af866-6421-40f1-ac89-202bddb5c487" --tx-id "Y7GFF8r9y0MEU_oi1aZeD87vrmai97JdRQ2L0cbGJ68" --dest-file-name "hello_world.txt" -w "/path/to/wallet" ``` Like other write commands, `pin-file` supports `--dry-run`, `--boost`, `--turbo`/`--turbo-url`, and `--gateway`. See `ardrive pin-file --help` for the full flag list. # Progress Logging of Transaction Uploads (/ardrive-cli/(working-with-files)/progress-logging-of-transaction-uploads) Progress logging of transaction uploads to stderr can be enabled by setting the `ARDRIVE_PROGRESS_LOG` environment variable to `1`: ```shell Uploading file transaction 1 of total 2 transactions... Transaction _GKQasQX194a364Hph8Oe-oku1AdfHwxWOw9_JC1yjc Upload Progress: 0% Transaction _GKQasQX194a364Hph8Oe-oku1AdfHwxWOw9_JC1yjc Upload Progress: 35% Transaction _GKQasQX194a364Hph8Oe-oku1AdfHwxWOw9_JC1yjc Upload Progress: 66% Transaction _GKQasQX194a364Hph8Oe-oku1AdfHwxWOw9_JC1yjc Upload Progress: 100% Uploading file transaction 2 of total 2 transactions... Transaction nA1stCdTkuf290k0qsqvmJ78isEC0bwgrAi3D8Cl1LU Upload Progress: 0% Transaction nA1stCdTkuf290k0qsqvmJ78isEC0bwgrAi3D8Cl1LU Upload Progress: 13% Transaction nA1stCdTkuf290k0qsqvmJ78isEC0bwgrAi3D8Cl1LU Upload Progress: 28% Transaction nA1stCdTkuf290k0qsqvmJ78isEC0bwgrAi3D8Cl1LU Upload Progress: 42% Transaction nA1stCdTkuf290k0qsqvmJ78isEC0bwgrAi3D8Cl1LU Upload Progress: 60% Transaction nA1stCdTkuf290k0qsqvmJ78isEC0bwgrAi3D8Cl1LU Upload Progress: 76% Transaction nA1stCdTkuf290k0qsqvmJ78isEC0bwgrAi3D8Cl1LU Upload Progress: 91% Transaction nA1stCdTkuf290k0qsqvmJ78isEC0bwgrAi3D8Cl1LU Upload Progress: 100% ``` # Rename a Single File (/ardrive-cli/(working-with-files)/rename-a-single-file) To rename an on-chain file you can make use of the `rename-file` command. The required parameters are the file ID and the new name, as well as the owner wallet or seed phrase. ```shell ardrive rename-file --file-id "290a3f9a-37b2-4f0f-a899-6fac983833b3" --file-name "My custom file name.txt" --wallet-file "wallet.json" ``` # Retrying a Failed File Data Transaction (Public Unbundled Files Only) (/ardrive-cli/(working-with-files)/retrying-a-failed-file-data-transaction-public-unbundled-files-only) Arweave data upload transactions are split into two phases: transaction posting and chunks uploading. Once the transaction post phase has been completed, you've effectively "paid" the network for storage of the data chunks that you'll send in the next stage. If your system encounters an error while posting the transaction, you can retry posting the transaction for as long as your tx_anchor is valid ([learn more about tx_anchors here][tx_anchors]). You may retry and/or resume posting chunks at any time after your transaction has posted. The ArDrive CLI allows you to take advantage of this Arweave protocol capability. Using the CLI, when the transaction post has succeeded but the chunk upload step fails, the data transaction's ID could be lost. There are a few options to recover this ID. If the failed transaction is the most recent one sent from a wallet, the transaction ID can be recovered with the `ardrive last-tx -w /path/to/wallet` command AFTER the transaction's headers have been mined (It can take 5-10 minutes for the tx-id to become available with the last-tx approach). Other options for finding the partially uploaded transaction's ID include: - Using an Arweave gateway GQL http endpoint to search for transactions that belong to the wallet. See this [Arweave GQL Guide][gql-guide] for more info. - Browse the recent transactions associated with the wallet via a block explorer tool like [ViewBlock][viewblock]. In order to re-seed the chunks for an unbundled ArFS data transaction, a user must have the data transaction ID, the original file data, and either a destination folder ID or a valid file ID for the file. Supply that information to the `retry-tx` command like so: ```shell ardrive retry-tx --tx-id { Data Transaction ID } --parent-folder-id { Destination Folder ID } --local-path /path/to/file --wallet-file /path/to/wallet ``` **Note: Retry feature is currently only available for PUBLIC unbundled file transactions. It is also perfectly safe to mistakenly re-seed the chunks of a healthy transaction, the transaction will remain stable and the wallet balance will not be affected.** # Understanding Bundled Transactions (/ardrive-cli/(working-with-files)/understanding-bundled-transactions) The ArDrive CLI currently uses two different methods for uploading transactions to the Arweave network: standard transactions and Direct to Network (D2N) bundled transactions. By default, the CLI will send a D2N bundled transaction for any action that would result in multiple transactions. This bundling functionality is currently used on the `upload-file` and `create-drive` commands. D2N bundled transactions come with several benefits and implications: - Bundling saves AR and enhances ArFS reliability by sending associated ArFS transactions up as one atomic bundle. - Bundled transactions are treated as a single data transaction by the Arweave network, but can be presented as separate transactions by the Arweave Gateway once they have been "unbundled". - Un-bundling can take anywhere from a few minutes up to an hour. During that time, the files in the bundle will neither appear in list- commands nor be downloadable. Similarly, they will not appear in the web app after syncs until un-bundling is complete. **This can negatively affect the accuracy of upsert operations**, so it's best to wait before retrying bulk uploads. - Bundling reliability on the gateway side degrades once bundles reach either 500 data items (or ~250 files) or 500 MiB, so the CLI will create and upload multiple bundles as necessary, or will send files that are simply too large for reliable bundling as unbundled txs. # Uploading a Custom Manifest (/ardrive-cli/(working-with-files)/uploading-a-custom-manifest) Using the custom content type feature, it is possible for users to upload their own custom manifests. The Arweave gateways use this special content type in order to identify an uploaded file as a manifest: ```shell application/x.arweave-manifest+json ``` In addition to this content type, the manifest must also adhere to the [correct JSON structure](#manifest-json) of an Arweave manifest. A user can create their own manifest from scratch, or start by piping a generated manifest to a JSON file and editing it to their specifications: ```shell ardrive create-manifest -w /path/to/wallet -f "6c312b3e-4778-4a18-8243-f2b346f5e7cb" --dry-run | jq '{manifest}.manifest' > my-custom-manifest.json ``` After editing the generated manifest, simply perform an `upload-file` command with the custom Arweave manifest content type to any PUBLIC folder: ```shell ardrive upload-file --content-type "application/x.arweave-manifest+json" --local-path my-custom-manifest.json --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json ``` The returned `dataTxId` field on the created `file` entity will be the endpoint that the manifest can be found on Arweave, just as explained in the [manifest sections](#uploading-manifests) above: ```shell https://arweave.net/{dataTxId} https://arweave.net/{dataTxId}/custom-file-1 https://arweave.net/{dataTxId}/custom-file-2 ``` # Uploading a Folder with Files (Bulk Upload) (/ardrive-cli/(working-with-files)/uploading-a-folder-with-files-bulk-upload) Users can perform a bulk upload by using the upload-file command on a target folder. The command will reconstruct the folder hierarchy on local disk as ArFS folders on the permaweb and upload each file into their corresponding folders: ```shell ardrive upload-file --local-path /path/to/folder --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json ``` # Uploading a Non-Bundled Transaction (NOT RECOMMENDED) (/ardrive-cli/(working-with-files)/uploading-a-non-bundled-transaction-not-recommended) While not recommended, the CLI does provide the option to forcibly send all transactions as standard transactions rather than attempting to bundle them together. To do this, simply add the `--no-bundle` flag to the `upload-file` or `create-drive` command: ```shell ardrive upload-file --no-bundle --local-path /path/to/file --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json ``` # Uploading a Single File (/ardrive-cli/(working-with-files)/uploading-a-single-file) To upload a file, you'll need a parent folder id, the file to upload's file path, and the path to your wallet: ```shell ardrive upload-file --local-path /path/to/file.txt --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json ``` Example output: ```shell { "created": [ { "type": "file", "entityName": "file.txt" "entityId": "6613395a-cf19-4420-846a-f88b7b765c05" "dataTxId": "l4iNWyBapfAIj7OU-nB8z9XrBhawyqzs5O9qhk-3EnI", "metadataTxId": "YfdDXUyerPCpBbGTm_gv_x5hR3tu5fnz8bM-jPL__JE", "bundledIn": "1zwdfZAIV8E26YjBs2ZQ4xjjP_1ewalvRgD_GyYw7f8", "sourceUri": "file:///path/to/file.txt" }, { "type": "bundle", "bundleTxId": "1zwdfZAIV8E26YjBs2ZQ4xjjP_1ewalvRgD_GyYw7f8" } ], "tips": [ { "txId": "1zwdfZAIV8E26YjBs2ZQ4xjjP_1ewalvRgD_GyYw7f8", "recipient": { "address": "3mxGJ4xLcQQNv6_TiKx0F0d5XVE0mNvONQI5GZXJXkt" }, "winston": "10000000" } ], "fees": { "1zwdfZAIV8E26YjBs2ZQ4xjjP_1ewalvRgD_GyYw7f8": 42819829 } } ``` NOTE: To upload to the root of a drive, specify its root folder ID as the parent folder ID for the upload destination. You can retrieve it like so: ```shell ardrive drive-info -d "c7f87712-b54e-4491-bc96-1c5fa7b1da50" | jq -r '.rootFolderId' ``` # Uploading Files with Custom MetaData (/ardrive-cli/(working-with-files)/uploading-files-with-custom-metadata) ArDrive CLI has the capability of attaching custom metadata to ArFS File and Folder MetaData Transactions during the `upload-file` command. This metadata can be applied to either the GQL tags on the MetaData Transaction and/or into the MetaData Transaction's Data JSON. All custom metadata applied must ultimately adhere to the following JSON shapes: ```ts // GQL Tags type CustomMetaDataGqlTags = Record; // Data JSON Fields type CustomMetaDataJsonFields = Record; | string | number | boolean | null | { [member: string]: JsonSerializable } | JsonSerializable[]; ``` e.g: ```shell { IPFS-Add: 'MY_HASH' } { 'Custom Name': ['Val 1', 'Val 2'] } ``` When the custom metadata is attached to the MetaData Transaction's GQL tags, they will become visible on any Arweave GQL gateway and also third party tools that read GQL data. When these tags are added to the MetaData Transaction's Data JSON they can be read by downloading the JSON data directly from `https://arweave.net/METADATA_TX_ID`. To add this custom metadata to your file metadata transactions, CLI users can pass custom metadata these parameters: - `--metadata-file path/to/json/schema` - `--metadata-json '{"key": "val", "key-2": true, "key-3": 420, "key-4": ["more", 1337]}'` - `--metadata-gql-tags "Tag-Name" "Tag Val"` The `--metadata-file` will accept a file path to JSON file containing custom metadata: ```shell ardrive upload-file --metadata-file path/to/metadata/json # ... ``` This JSON schema object must contain instructions on where to put this metadata with the `metaDataJson` and `metaDataGqlTags` keys. e.g: ```json { "metaDataJson": { "Tag-Name": ["Value-1", "Value-2"] }, "metaDataGqlTags": { "GQL Tag Name": "Tag Value" } } ``` The `--metadata-gql-tags` parameter accepts an array of string values to be applied to the MetaData Tx GQL Tags. This method of CLI input does not support multiple tag values for a given tag name and the input must be an EVEN number of string values. (Known bug: String values starting with the `"-"` character are currently not supported. Use --metadata-file parameter instead.) e.g: ```shell upload-file --metadata-gql-tags "Custom Tag Name" "Custom Value" # ... ``` And the `--metadata-json` parameter will accept a stringified JSON input. It will apply all declared JSON fields directly to the MetaData Tx's Data JSON. e.g: ```shell upload-file --metadata-json ' { "json field": "value", "another fields": false } ' # ... ``` Custom metadata applied to files and/or folders during the `upload-file` command will be read back through all existing read commands. e.g: ```shell ardrive file-info -f 067c4008-9cbe-422e-b697-05442f73da2b { "appName": "ArDrive-CLI", "appVersion": "1.17.0", "arFS": "0.11", "contentType": "application/json", "driveId": "967215ca-a489-494b-97ec-0dd428d7be34", "entityType": "file", "name": "unique-name-9718", "txId": "sxg8bNu6_bbaHkJTxAINVVoz_F-LiFe6s7OnxzoJJk4", "unixTime": 1657655070, "size": 262148, "lastModifiedDate": 1655409872705, "dataTxId": "ublZcIff77ejl3m0uEA8lXEfnTWmSBOFoz-HibqKeyk", "dataContentType": "text/plain", "parentFolderId": "97bc4fb5-aca4-4ffe-938f-1285153d98ca", "entityId": "067c4008-9cbe-422e-b697-05442f73da2b", "fileId": "067c4008-9cbe-422e-b697-05442f73da2b", "IPFS-Add": "MY_HASH", "Tag-1": "Val", "Tag-2": "Val", "Tag-3": "Val", "Boost": "1.05" } ``` #### Applying Unique Custom MetaData During Bulk Workflows With some custom scripting and the `--metadata-file` parameter, the ArDrive CLI can be used to apply custom metadata to each file individually in a bulk workflow. For example, if you choose a numbered file naming pattern you can make use of a `for` loop: ```shell for i in {1..5} do ardrive upload-file -F f0c58c11-430c-4383-8e54-4d864cc7e927 --local-path "../uploads/test-file-$i.txt" -w "/path/to/wallet.json" --metadata-file "../custom/metadata-$i.json" --dry-run > "file-result-$i.json" done ``` # Uploading From a Remote URL (/ardrive-cli/(working-with-files)/uploading-from-a-remote-url) You can upload a file from an existing url using the `--remote-path` flag. This must be used in conjunction with `--dest-file-name`. You can use a custom content type using the `--content-type` flag, but if this isn't used the app will use the content type from the response header of the request for the remote data. ```shell ardrive upload-file --remote-path "https://url/to/file" --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -d "example.jpg" -w /path/to/wallet.json ``` # Uploading Manifests (/ardrive-cli/(working-with-files)/uploading-manifests) [Arweave Path Manifests][arweave-manifests] are are special `.json` files that instruct Arweave Gateways to map file data associated with specific, unique transaction IDs to customized, hosted paths relative to that of the manifest file itself. So if, for example, your manifest file had an arweave.net URL like: ```shell https://arweave.net/{manifest tx id} ``` Then, all the mapped transactions and paths in the manifest file would be addressable at URLs like: ```shell https://arweave.net/{manifest tx id}/foo.txt https://arweave.net/{manifest tx id}/bar/baz.png ``` ArDrive supports the creation of these Arweave manifests using any of your PUBLIC folders. The generated manifest paths will be links to each of the file entities within the specified folder. The manifest file entity will be created at the root of the folder. To create a manifest of an entire public drive, specify the root folder of that drive: ```shell ardrive create-manifest -f "bc9af866-6421-40f1-ac89-202bddb5c487" -w "/path/to/wallet" ``` You can also create a manifest of a folder's file entities at a custom depth by using the `--max-depth` option: ```shell ardrive create-manifest --max-depth 0 -f "867228d8-4413-4c0e-a499-e1decbf2ea38" -w "/path/to/wallet" ``` Creating a `.json` file of your manifest links output can be accomplished here with some `jq` parsing and piping to a file: ```shell ardrive create-manifest -w /path/to/wallet -f "6c312b3e-4778-4a18-8243-f2b346f5e7cb" | jq '{links}' > links.json ``` If you'd like to preview the contents of your manifest before uploading, you can perform a dry run and do some lightweight post processing to isolate the data: ```shell ardrive create-manifest -w /path/to/wallet -f "6c312b3e-4778-4a18-8243-f2b346f5e7cb" --dry-run | jq '{manifest}.manifest' ``` ```json { "manifest": "arweave/paths", "version": "0.1.0", "index": { "path": "index.html" }, "paths": { "hello_world.txt": { "id": "Y7GFF8r9y0MEU_oi1aZeD87vrmai97JdRQ2L0cbGJ68" }, "index.html": { "id": "pELonjVebHyBsdxVymvxbGTmHD96v9PuuUXj8GUHGoY" } } } ``` The manifest data transaction is tagged with a unique content-type, `application/x.arweave-manifest+json`, which tells the gateway to treat this file as a manifest. The manifest file itself is a `.json` file that holds the paths (the data transaction ids) to each file within the specified folder. When your folder is later changed by adding files or updating them with new revisions, the original manifest will NOT be updated on its own. A manifest is a permanent record of your files in their current state. However, creating a subsequent manifest with the same manifest name will create a new revision of that manifest in its new current state. Manifests follow the same name conflict resolution as outlined for files above (upsert by default). #### Hosting a Webpage with Manifest When creating a manifest, it is possible to host a webpage or web app. You can do this by creating a manifest on a folder that has an `index.html` file in its root. Using generated build folders from popular frameworks works as well. One requirement here to note is that the `href=` paths from your generated `index.html` file must not have leading a `/`. This means that the manifest will not resolve a path of `/dist/index.js` but it will resolve `dist/index.js` or `./dist/index.js`. As an example, here is a flow of creating a React app and hosting it with an ArDrive Manifest. First, generate a React app: ```shell yarn create react-app my-app ``` Next, add this field to the generated `package.json` so that the paths will resolve correctly: ```json "homepage": ".", ``` Then, create an optimized production build from within the app's directory: ```shell yarn build ``` Now, we can create and upload that produced build folder on ArDrive to any of your existing ArFS folder entities: ```shell ardrive upload-file -l "/build" -w "/path/to/wallet" --parent-folder-id "bc9af866-6421-40f1-ac89-202bddb5c487" ``` And finally, create the manifest using the generated Folder ID from the build folder creation: ```shell ardrive create-manifest -f "41759f05-614d-45ad-846b-63f3767504a4" -w "/path/to/wallet" ``` In the return output, the top link will be a link to the deployed web app: ```shell "links": [ "https://arweave.net/0MK68J8TqGhaaOpPe713Zn0jdpczMt2NGS2CtRYiuAg", "https://arweave.net/0MK68J8TqGhaaOpPe713Zn0jdpczMt2NGS2CtRYiuAg/asset-manifest.json", "https://arweave.net/0MK68J8TqGhaaOpPe713Zn0jdpczMt2NGS2CtRYiuAg/favicon.ico", "https://arweave.net/0MK68J8TqGhaaOpPe713Zn0jdpczMt2NGS2CtRYiuAg/index.html", # ... ``` This is effectively hosting a web app with ArDrive. Check out the ArDrive Price Calculator React App hosted as an [ArDrive Manifest][example-manifest-webpage]. # Uploading Multiple Files (/ardrive-cli/(working-with-files)/uploading-multiple-files) To upload an arbitrary number of files or folders, pass a space-separated list of paths to `--local-paths`: ```shell ardrive upload-file -w wallet.json -F "6939b9e0-cc98-42cb-bae0-5888eca78885" --local-paths ./image.png ~/backups/ ../another_file.txt ardrive upload-file -w wallet.json -F "6939b9e0-cc98-42cb-bae0-5888eca78885" --local-paths ./*.json ``` # Uploading With a Custom Content Type (/ardrive-cli/(working-with-files)/uploading-with-a-custom-content-type) Each file uploaded to the Arweave network receives a `"Content-Type"` GraphQL tag that contains the MIME type for the file. The gateway will use this content type to determine how to serve that file's data transaction at the `arweave.net/{data tx id}` endpoint. By default, the CLI will attempt to derive this content type from the file extension of the provided file. In most cases, the content type that is derived will be correct and the gateway will properly serve the file. The CLI also provides the option for users to upload files with a custom content type using the `--content-type` flag: ```shell ardrive upload-file --content-type "application/json" --local-path /path/to/file --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" -w /path/to/wallet.json ``` It is currently possible to set this value to any given string, but the gateway will still only serve valid content types. Check out this list of commonly used MIME types to ensure you're providing a valid content type: [Common MIME types][mozilla-mime-types]. Note: In the case of multi-file uploads or recursive folder uploads, setting this `--content-type` flag will set the provided custom content type on EVERY file entity within a given upload. # Creating Folders (/ardrive-cli/(working-with-folders)/creating-folders) Creating folders manually is straightforward: ```shell ardrive create-folder --parent-folder-id "63153bb3-2ca9-4d42-9106-0ce82e793321" --folder-name "My Awesome Folder" -w /path/to/wallet.json ``` Example output: ```shell { "created": [ { "type": "folder", "metadataTxId": "AYFMBVmwqhbg9y5Fbj3Iasy5oxUqhauOW7PcS1sl4Dk", "entityId": "d1b7c514-fb12-4603-aad8-002cf63015d3", "key": "yHdCjpCKD2cuhQcKNx2d/XF5ReEjoKfZVqKunlCnPEk", "entityName": "My Awesome Folder" } ], "tips": [], "fees": { "AYFMBVmwqhbg9y5Fbj3Iasy5oxUqhauOW7PcS1sl4Dk": 1378052 } } ``` Note: Folders can also be created by supplying a folder as the --local-path of an upload-file command. In this case, the folder hierarchy on the local disk will be reconstructed on chain during the course of the recursive bulk upload. # Listing Contents of a Folder (/ardrive-cli/(working-with-folders)/listing-contents-of-a-folder) Similar to drives, the `list-folder` command can be used to fetch the metadata of each entity within a folder. But by default, the command will fetch only the immediate children of that folder (`--max-depth 0`): ```shell ardrive list-folder --parent-folder-id "29850ab7-56d4-4e1f-a5be-cb86d5513940" ``` Example output: ```shell [ { "appName": "ArDrive-CLI", "appVersion": "2.0", "arFS": "0.11", "contentType": "application/json", "driveId": "01ea6ba3-9e58-42e7-899d-622fd110211a", "entityType": "folder", "name": "mytestfolder", "txId": "HYiKyfLwY7PT9NleTQoTiM_-qPVUwf4ClDhx1sjUAEU", "unixTime": 1635102772, "parentFolderId": "29850ab7-56d4-4e1f-a5be-cb86d5513940", "entityId": "03df2929-1440-4ab4-bbf0-9dc776e1ed96", "path": "/My Public Folder/mytestfolder", "txIdPath": "/09_x0X2eZ3flXXLS72WdTDq6uaa5g2LjsT-QH1m0zhU/HYiKyfLwY7PT9NleTQoTiM_-qPVUwf4ClDhx1sjUAEU", "entityIdPath": "/29850ab7-56d4-4e1f-a5be-cb86d5513940/03df2929-1440-4ab4-bbf0-9dc776e1ed96" }, { "appName": "ArDrive-CLI", "appVersion": "2.0", "arFS": "0.11", "contentType": "application/json", "driveId": "01ea6ba3-9e58-42e7-899d-622fd110211a", "entityType": "folder", "name": "Super sonic public folder", "txId": "VUk1B_vo1va2-EHLtqjsotzy0Rdn6lU4hQo3RD2xoTI", "unixTime": 1631283259, "parentFolderId": "29850ab7-56d4-4e1f-a5be-cb86d5513940", "entityId": "452c6aec-43dc-4015-9abd-20083068d432", "path": "/My Public Folder/Super sonic sub folder", "txIdPath": "/09_x0X2eZ3flXXLS72WdTDq6uaa5g2LjsT-QH1m0zhU/VUk1B_vo1va2-EHLtqjsotzy0Rdn6lU4hQo3RD2xoTI", "entityIdPath": "/29850ab7-56d4-4e1f-a5be-cb86d5513940/452c6aec-43dc-4015-9abd-20083068d432" }, { "appName": "ArDrive-CLI", "appVersion": "2.0", "arFS": "0.11", "contentType": "application/json", "driveId": "01ea6ba3-9e58-42e7-899d-622fd110211a", "entityType": "file", "name": "test-number-twelve.txt", "txId": "429zBqnd7ZBNzgukaix26RYz3g5SeXCCo_oIY6CPZLg", "unixTime": 1631722234, "size": 47, "lastModifiedDate": 1631722217028, "dataTxId": "vA-BxAS7I6n90cH4Fzsk4cWS3EOPb1KOhj8yeI88dj0", "dataContentType": "text/plain", "parentFolderId": "29850ab7-56d4-4e1f-a5be-cb86d5513940", "entityId": "e5948327-d6de-4acf-a6fe-e091ecf78d71", "path": "/My Public Folder/test-number-twelve.txt", "txIdPath": "/09_x0X2eZ3flXXLS72WdTDq6uaa5g2LjsT-QH1m0zhU/429zBqnd7ZBNzgukaix26RYz3g5SeXCCo_oIY6CPZLg", "entityIdPath": "/29850ab7-56d4-4e1f-a5be-cb86d5513940/e5948327-d6de-4acf-a6fe-e091ecf78d71" }, { "appName": "ArDrive-CLI", "appVersion": "2.0", "arFS": "0.11", "contentType": "application/json", "driveId": "01ea6ba3-9e58-42e7-899d-622fd110211a", "entityType": "file", "name": "wonderful-test-file.txt", "txId": "6CokwlzB81Fx7dq-lB654VM0XQykdU6eYohDmEJ2gk4", "unixTime": 1631671275, "size": 23, "lastModifiedDate": 1631283389232, "dataTxId": "UP8THwA_1gvyRqNRqYmTpWvU4-UzNWBN7SiX_AIihg4", "dataContentType": "text/plain", "parentFolderId": "29850ab7-56d4-4e1f-a5be-cb86d5513940", "entityId": "3274dae9-3487-41eb-94d5-8d5d3d8bc343", "path": "/My Public Folder/wonderful-test-file.txt", "txIdPath": "/09_x0X2eZ3flXXLS72WdTDq6uaa5g2LjsT-QH1m0zhU/6CokwlzB81Fx7dq-lB654VM0XQykdU6eYohDmEJ2gk4", "entityIdPath": "/29850ab7-56d4-4e1f-a5be-cb86d5513940/3274dae9-3487-41eb-94d5-8d5d3d8bc343" } ] ``` To list further than the immediate children, you can make use of the flags: `--all` and `--max-depth`. ```shell ardrive list-folder --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" --all ardrive list-folder --parent-folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" --max-depth 2 ``` In the case of private entitites, the `--with-keys` flag will make the command to include the keys in the output. ```shell ardrive list-folder --parent-folder-id "1b027047-4cfc-4eee-88a8-9af694f660c0" -w /my/wallet.json --with-keys ``` # Moving Folders (/ardrive-cli/(working-with-folders)/moving-folders) Moving a folder is as simple as supplying a new parent folder ID. Note that naming collisions among entities within a folder are not allowed. ```shell ardrive move-folder --folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" --parent-folder-id "29850ab7-56d4-4e1f-a5be-cb86d5513921" -w /path/to/wallet.json ``` # Renaming Folders (/ardrive-cli/(working-with-folders)/renaming-folders) In order to rename a folder you must provide a name different from its current one, and it must not create naming conflicts with its sibling entities. ```shell ardrive rename-folder --folder-id "568d5eba-dbf3-4a49-8129-1c58f7fd35bc" --folder-name "Folder with cool stuff" -w "./wallet.json" ``` # Viewing Folder Metadata (/ardrive-cli/(working-with-folders)/viewing-folder-metadata) To view the metadata of a folder, users can use the `folder-info` command: ```shell ardrive folder-info --folder-id "9af694f6-4cfc-4eee-88a8-1b02704760c0" ``` # ArFS (/ardrive-cli/arfs) [ArFS] is a data modeling, storage, and retrieval protocol designed to emulate common file system operations and to provide aspects of mutability to your data hierarchy on [Arweave]'s otherwise permanent, immutable data storage blockweave. # CLI Help (/ardrive-cli/cli-help) Learn to use any command: ```shell ardrive --help ``` # CLI Version (/ardrive-cli/cli-version) You can print out the version by running any of: ```shell ardrive --version ardrive -V ``` # Data Portability (/ardrive-cli/data-portability) Data uploaded via the ArDrive CLI, once indexed by Arweave's Gateways and sufficiently seeded across enough nodes on the network, can be accessed via all other ArDrive applications including the [ArDrive Web application][ardrive-web-app] at https://app.ardrive.io. All transactions successfully executed by ArDrive can always be inspected in the [Viewblock blockchain explorer]. # ArDrive CLI (/ardrive-cli) **For AI and LLM users**: Access the complete ArDrive CLI documentation in plain text format at llm.txt for easy consumption by AI agents and language models. # ArDrive CLI Please refer to the [source code](https://github.com/ardriveapp/ardrive-cli) for SDK details. # Intended Audience (/ardrive-cli/intended-audience) This tool is intended for use by: - ArDrive power users with advanced workflows and resource efficiency in mind: bulk uploaders, those with larger storage demand, game developers, nft creators, storage/db admins, etc. - Automation tools - Services - Terminal aficionados - Extant and aspiring cypherpunks For deeper integrations with the [ArDrive] platform, consider using the [ArDrive Core][ardrive-core] (Node) library's configurable and intuitive class interfaces directly within your application. To simply install the latest version of the CLI to your local system and get started, follow the [Quick Start](#quick-start) instructions. To build and/or develop the CLI from source, follow the [Build and Run from Source](#build-and-run-from-source) instructions. In either case, be sure to satisfy the requirements in the [Prerequisites](#prerequisites) section. # Limitations (/ardrive-cli/limitations) **Number of files in a bulk upload:** Theoretically unlimited **Max individual file size**: 2GB (Node.js limitation) **Max file name length**: 255 bytes **Max ANS-104 bundled transaction size:** 500 MiB per bundle. App will handle creating multiple bundles. **Max ANS-104 data item counts per bundled transaction:** 250 Files per bundle (500 Data Items). # Using the CLI # Wallet Operations (/ardrive-cli/wallet-operations) Browsing of ArDrive public data is possible without the need for an [Arweave wallet][kb-wallets]. However, for all write operations, or read operations without encryption/decryption keys, you'll need a wallet. As you utilize the CLI, you can use either your wallet file or your seed phrase interchangeably. Consider the security implications of each approach for your particular use case carefully. If at any time you'd like to generate a new wallet altogether, start by generating a new seed phase. And if you'd like to use that seed phrase in the form of a wallet file, or if you'd like to recover an existing wallet via its seed phrase, use either or both of the following commands: ```shell ardrive generate-seedphrase "this is an example twelve word seed phrase that you could use" ardrive generate-wallet -s "this is an example twelve word seed phrase that you could use" > /path/to/wallet/file.json ``` Public attributes of Arweave wallets can be retrieved via their 43-character Arweave wallet address. You can retrieve the wallet address associated with [your wallet file or 12-word seed phrase][kb-wallets] (e.g. wallets generated by [ArConnect][arconnect]) like so: ```shell ardrive get-address -w /path/to/wallet/file.json ardrive get-address -s "this is an example twelve word seed phrase that you could use" HTTn8F92tR32N8wuo-NIDkjmqPknrbl10JWo5MZ9x2k ``` You'll need AR in your wallet for any write operations you perform in ArDrive. You can always check your wallet balance (in both AR and Winston units) by performing: ```shell ardrive get-balance -w /path/to/wallet/file.json ardrive get-balance -a "HTTn8F92tR32N8wuo-NIDkjmqPknrbl10JWo5MZ9x2k" 1500000000000 Winston 1.5 AR ``` If, at any time, you need to send AR out of your wallet to another wallet address, you may perform: ```shell ardrive send-ar -w /path/to/wallet/file.json --dest-address "HTTn8F92tR32N8wuo-NIDkjmqPknrbl10JWo5MZ9x2k" --ar-amount 2.12345 ``` # Add the Skill to Your Project (/ario-deploy/(claude-code-integration)/add-the-skill-to-your-project) ```bash mkdir -p .claude/skills curl -o .claude/skills/deploy.md https://raw.githubusercontent.com/ar-io/ar-io-deploy/main/examples/claude-skill/deploy.md ``` Then in Claude Code, say: - "deploy to ar.io" - "deploy my app to arweave" - "set up CI/CD for ar.io deployment" Claude will build your project, detect the output folder, and run the deploy with the right flags. # What the Skill Does (/ario-deploy/(claude-code-integration)/what-the-skill-does) 1. **Detects your build folder** (`./dist`, `./build`, `./out`) 2. **Checks for credentials** (`DEPLOY_KEY` env var or wallet file) 3. **Installs `@ar.io/deploy`** if not already available 4. **Runs the deployment** with appropriate flags 5. **Reports results** — transaction ID, Arweave URL, ArNS URL See [`examples/claude-skill/`](https://github.com/ar-io/ar-io-deploy/tree/main/examples/claude-skill) for the full skill file and customization options. --- # Advanced Usage (/ario-deploy/(commands)/advanced-usage) Deploy to an undername (subdomain) — the ArNS authority key is a Solana wallet: ```bash ario-deploy deploy --use-arns --arns-name my-app --wallet ./wallet.json --arns-wallet ./arns-id.json --undername staging ``` Deploy with a custom TTL: ```bash ario-deploy deploy --use-arns --arns-name my-app --wallet ./wallet.json --arns-wallet ./arns-id.json --ttl-seconds 7200 ``` Update ArNS on devnet (or against a custom RPC): ```bash ario-deploy deploy --use-arns --arns-name my-app --wallet ./wallet.json --arns-wallet ./arns-id.json --cluster devnet ario-deploy deploy --use-arns --arns-name my-app --wallet ./wallet.json --arns-wallet ./arns-id.json --rpc-url https://my-rpc.example.com ``` Upload using an Ethereum wallet (file): ```bash ario-deploy deploy --sig-type ethereum --wallet ./private-key.txt ``` Upload using a Solana wallet (base58 private key): ```bash ario-deploy deploy --sig-type solana --private-key "\" ``` # Direct Commands (/ario-deploy/(commands)/direct-commands) Use flags for faster, scriptable deployments: ```bash ario-deploy deploy --wallet ./wallet.json ario-deploy deploy --use-arns --arns-name my-app --wallet ./wallet.json --arns-wallet ./arns-id.json ``` Deploy using private key directly: ```bash ario-deploy deploy --private-key "$(cat wallet.json)" ``` Deploy using environment variable: ```bash DEPLOY_KEY=$(base64 -i wallet.json) ario-deploy deploy --deploy-folder ./dist ``` Deploy a specific folder: ```bash ario-deploy deploy --wallet ./wallet.json --deploy-folder ./build ``` Deploy a single file: ```bash ario-deploy deploy --wallet ./wallet.json --deploy-file ./path/to/file.txt ``` `--deploy-file` overrides `--deploy-folder`, and the file is uploaded as one transaction with **no manifest** — an ArNS name pointed at it resolves straight to that file, served with its own content type. Useful for a PDF, a dataset, or a single page. Manifest-only options such as `--fallback-file` do not apply. # Interactive Mode (Easiest) (/ario-deploy/(commands)/interactive-mode-easiest) Run the deploy command without arguments to be guided through all deployment options: ```bash ario-deploy deploy ``` When ArNS details aren't supplied via flags, `deploy` asks whether you want to update an ArNS name (defaulting to yes) and, if so, prompts for the details. It will guide you through: - Whether to update an ArNS name (and which one) - Wallet method (file, string, or environment variable) - What to deploy (folder or file) - Advanced options (optional: undername, TTL, Solana cluster) Pass `--arns-name` (or `--use-arns`) to skip the ArNS confirmation, or use the `upload` command for an upload-only run. In a non-interactive environment (CI, or no TTY) `deploy` does not prompt — supply everything via flags or `DEPLOY_KEY`. # Single-page apps (/ario-deploy/(commands)/single-page-apps) An Arweave path manifest maps each path to a transaction, and a gateway returns 404 for any path the manifest does not list. That is correct for static files but wrong for a single-page app, whose routes are not files — `/settings` is invented by the router and exists nowhere on disk. Without a fallback the root loads and every deep link 404s. Manifests have a `fallback` for exactly this, and `ario-deploy` sets it automatically when the build emits a `404.html`: ```bash ario-deploy deploy --deploy-folder ./dist ``` Most SPA builds do not emit one. Either copy your entry point before deploying: ```bash cp dist/index.html dist/404.html ``` …or name the fallback directly: ```bash ario-deploy deploy --deploy-folder ./dist --fallback-file index.html ``` The file must exist in the deploy folder; a path that is not there fails before anything is uploaded, so a typo costs nothing. > Deep links can appear broken for up to a minute after a redeploy while > gateways serve cached 404s from the previous manifest. Confirm with a > cache-busting query string (`/settings?x=1`) before assuming the deploy failed. # Upload/deploy without ArNS (/ario-deploy/(commands)/upload-deploy-without-arns) `deploy` uploads without updating ArNS by default. You can also use the `upload` command explicitly for the same Turbo upload, dedupe cache, and payment options as deploy, minus ArNS flags: ```bash ario-deploy deploy --wallet ./wallet.json --deploy-folder ./dist ario-deploy upload --wallet ./wallet.json --deploy-folder ./dist ario-deploy upload --wallet ./wallet.json --deploy-file ./dist/index.html DEPLOY_KEY=$(base64 -i wallet.json) ario-deploy upload --deploy-folder ./dist ``` # Basic Usage (/ario-deploy/(github-action)/basic-usage) ```yaml - uses: ar-io/ar-io-deploy@v2.0.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} # upload key (pays for the upload) arns-key: ${{ secrets.ARNS_KEY }} # Solana ArNS authority key arns-name: myapp deploy-folder: ./dist ``` # Disabling Deduplication (/ario-deploy/(github-action)/disabling-deduplication) By default, the action caches transaction IDs to avoid re-uploading unchanged files. To disable this: ```yaml - name: Deploy without dedupe uses: ar-io/ar-io-deploy@v2.0.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} deploy-folder: ./dist no-dedupe: 'true' ``` You can also limit the cache size: ```yaml - name: Deploy with limited cache uses: ar-io/ar-io-deploy@v2.0.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} deploy-folder: ./dist dedupe-cache-max-entries: '1000' ``` # Incremental Uploads (/ario-deploy/(github-action)/github-action-incremental-uploads) A CI job runs from a fresh checkout, so the restored transaction cache is often missing or stale — and then every redeploy pays for the whole bundle again. `incremental: 'true'` recovers those transaction ids from your wallet's own past uploads on chain, so only the files that actually changed are paid for. See [Incremental uploads](#incremental-uploads). Requires v1.2.0 or later; pin the version, since the floating v1` tag may lag. ```yaml - name: Deploy only what changed uses: ar-io/ar-io-deploy@v1.2.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} deploy-folder: ./dist incremental: 'true' ``` --- # PR Preview Deployments (/ario-deploy/(github-action)/pr-preview-deployments) Automatically deploy preview builds for each pull request. The `preview` mode auto-generates an undername from the PR number and posts a comment with the preview URL: ```yaml name: Deploy PR Preview on: pull_request: types: [opened, synchronize] jobs: deploy-preview: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Deploy Preview uses: ar-io/ar-io-deploy@v2.0.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} arns-key: ${{ secrets.ARNS_KEY }} arns-name: myapp preview: 'true' github-token: ${{ secrets.GITHUB_TOKEN }} deploy-folder: ./dist ``` When `preview` is enabled, the action will: - Auto-generate an undername like `myapp-repo-pr-123` from the repository name and PR number - Post a comment on the PR with the preview URL (the token needs `pull-requests: write`) - Update the comment on subsequent pushes instead of creating new ones Preview undernames are not removed when the PR closes; each costs one of the ArNS name's undername slots until you remove it. The action skips every step on a `closed` event, so subscribing to it costs nothing. # Production Deployment (/ario-deploy/(github-action)/production-deployment) Deploy to your base ArNS name when pushing to main: ```yaml name: Deploy to Production on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Deploy to Permaweb uses: ar-io/ar-io-deploy@v2.0.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} arns-key: ${{ secrets.ARNS_KEY }} arns-name: myapp deploy-folder: ./dist ``` # Updating ArNS (Solana) (/ario-deploy/(github-action)/updating-arns-solana) ArNS updates run against the Solana ARIO programs. Provide the Solana ArNS authority key via `arns-key` (a base58 Solana secret key); the upload is still paid for by `deploy-key`. Use `cluster` to target `mainnet` (default) or `devnet`, and `rpc-url` for a custom RPC endpoint. ```yaml - name: Deploy and update ArNS uses: ar-io/ar-io-deploy@v2.0.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} # upload key arns-key: ${{ secrets.ARNS_KEY }} # Solana ArNS authority key arns-name: myapp deploy-folder: ./dist cluster: mainnet ``` # With On-Demand Payment (/ario-deploy/(github-action)/with-on-demand-payment) ```yaml - name: Deploy with ARIO on-demand uses: ar-io/ar-io-deploy@v2.0.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} arns-key: ${{ secrets.ARNS_KEY }} arns-name: myapp deploy-folder: ./dist sig-type: solana # ARIO is a Solana token, so the upload key must be Solana on-demand: ario max-token-amount: '2.0' ``` # ArNS authority key (ARNS_KEY) (/ario-deploy/(prerequisites)/arns-authority-key-arns-key) Set a base58-encoded **Solana** secret key as `ARNS_KEY`, or pass a `solana-keygen` `id.json` file via `--arns-wallet` (or a base58 string via `--arns-private-key`). This key must control the ArNS name being updated. ⚠️ **Important:** Use dedicated wallets for deployments to minimize security risks. Ensure your upload wallet has sufficient Turbo Credits for uploads. # Upload key (DEPLOY_KEY) (/ario-deploy/(prerequisites)/upload-key-deploy-key) 1. **Arweave signer (default):** Encode your Arweave wallet key in base64 and set it as `DEPLOY_KEY`: ```bash base64 -i wallet.json | pbcopy ``` 2. **Ethereum/Polygon signers:** Use your raw private key (no encoding needed) as `DEPLOY_KEY`. 3. **Solana signer:** Use a base58-encoded secret key as `DEPLOY_KEY`, or a `solana-keygen` `id.json` byte-array wallet file via `--wallet`. To make a new one, run `ario-deploy keygen`. #### Create a wallet with keygen` ```bash ario-deploy keygen # writes ~/.ar.io/wallets/\.json ario-deploy keygen --out ~/wallets/my-wallet.json ``` `keygen` writes a new Solana key in `solana-keygen` `id.json` format. By default the file goes in `~/.ar.io/wallets/`, a folder in your home directory outside any project, named after the wallet's address. It prints the file path, the public address, the wallet's free upload allowance and the exact `deploy` command to run next. It never prints the secret key, and it never overwrites an existing file. Add `--dev` to look up the allowance on the Turbo sandbox. Who can read the file: - **Linux and macOS:** the file has mode `0600` and the wallets folder `0700`, so only your account can read them. - **Windows:** `keygen` removes inherited permissions with `icacls` and grants access to your account only. When that fails it prints a warning, and other accounts on the computer might be able to read the file. **Never put the wallet inside the folder you deploy.** An upload is permanent and public, and anyone who reads the file controls the wallet. `deploy` and `upload` refuse to publish any key they were given, including `DEPLOY_KEY` and `ARNS_KEY`, and any file that can be proved to be a private key (see [Files that are never uploaded](#files-that-are-never-uploaded)). --out` accepts any path, but `keygen` warns when the path is inside the current folder. When the file is inside a git repository, `keygen` adds it to the repository's `.gitignore` and then asks git to confirm that it is ignored and not tracked. If git does not confirm both, it prints a warning instead. Back up the wallet file. It is the only copy, anyone who has it controls the wallet, and nobody can recover it for you. Never paste its contents anywhere. # Bundler service (/ario-deploy/bundler-service) Uploads go through Turbo: an upload service that accepts signed data items, and a payment service that answers balance, price and top-up questions. The two belong to the same network, and ario-deploy configures them together. | When to use | Flags | | ---------------------------------- | ------------------------------------------------------------------ | | **Default** (production) | none: `https://upload.ardrive.io` and `https://payment.ardrive.io` | | **Development sandbox** | `--dev` | | **Custom or self-hosted services** | `--uploader \` and `--payment-url \` | `--dev` selects both sandbox services (`https://upload.services.ar-io.dev` and `https://payment.services.ar-io.dev`) and testnet RPCs for `--on-demand`. Passing the sandbox URL to `--uploader` alone does the same. A custom `--uploader` without `--payment-url` keeps the production payment service and prints a warning, since balance checks and top-ups go there. ```bash ario-deploy upload --wallet ./wallet.json --deploy-folder ./dist --dev ``` The free upload limit is read from the upload service, so it follows the network: 105 KiB per item in production, 5 MiB in the sandbox. **A `--dev` upload is not permanent.** It goes to the Turbo sandbox for testing, production gateways do not serve it, and the result output says so. Never switch to `--dev` to get past an error on production: the URL it prints does not work as a permanent site. # CLI in GitHub Actions (/ario-deploy/cli-in-github-actions) You can also use the CLI directly in your workflows: **Basic Workflow:** ```yaml name: Deploy to Permaweb on: push: branches: - main jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v3 with: version: 9 - uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - run: pnpm install - run: pnpm run deploy env: DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} ``` **With On-Demand Payment:** ```yaml name: Deploy to Permaweb with On-Demand Payment on: push: branches: - main jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v3 with: version: 9 - uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - run: pnpm install - run: pnpm build - name: Deploy with ARIO on-demand run: ario-deploy deploy --arns-name my-app --sig-type solana --on-demand ario --max-token-amount 2.0 env: DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} # upload key (pays for the upload) ARNS_KEY: ${{ secrets.ARNS_KEY }} # Solana ArNS authority key # Or upload with Ethereum and Base-ETH on-demand payment (upload only; ArNS requires Solana): # - name: Upload with Base-ETH on-demand # run: | # ario-deploy upload \ # --sig-type ethereum \ # --on-demand base-eth \ # --max-token-amount 0.2 # env: # DEPLOY_KEY: ${{ secrets.ETH_PRIVATE_KEY }} ``` # Command Options (/ario-deploy/command-options) **`deploy`** (upload by default, optional ArNS update): - `--use-arns`: Update an ArNS/ANT record after upload. When ArNS details aren't supplied and you're in a TTY, `deploy` asks by default. - `--arns-name, -n`: The ArNS name to update. Required when using `--use-arns`; also implies ArNS mode. - `--cluster, -p`: Solana cluster for ArNS updates. Choices: `mainnet`, `devnet`. Default: `mainnet` - `--rpc-url`: Optional Solana RPC URL override for ArNS updates - `--deploy-folder, -d`: Folder to deploy. Default: `./dist` - `--deploy-file, -f`: Deploy a single file instead of a folder (no manifest is created) - `--fallback-file`: Path, relative to the deploy folder, served for routes the manifest does not list. Defaults to `404.html` when the build emits one. See [Single-page apps](#single-page-apps). - `--undername, -u`: ANT undername to update. Default: `@` - `--ttl-seconds, -t`: TTL in seconds for the ANT record (60-86400). Default: `60` - `--skip-arns-check`: Update the record even if the ArNS key does not appear to own or control the name. Without it, a deploy whose key cannot update the name is refused before anything is uploaded. Use it only right after the name changed hands, when the ANT's recorded owner can lag. Upload key (pays for the upload): - `--sig-type, -s`: Signer type for the upload key. Choices: `arweave`, `ethereum`, `polygon`, `solana`. Default: `arweave` - `--wallet, -w`: Path to the upload wallet file (JWK for Arweave, private key for Ethereum/Polygon, `solana-keygen` `id.json` for Solana). Falls back to `DEPLOY_KEY`. - `--private-key, -k`: Upload private-key string (alternative to `--wallet`). JWK JSON for Arweave, hex for EVM chains, base58 secret key for Solana. ArNS authority key (controls the name, signs the update — always Solana): - `--arns-wallet`: Path to the Solana `solana-keygen` `id.json` wallet that controls the ArNS name. Falls back to `ARNS_KEY`. - `--arns-private-key`: Base58 Solana secret key for the ArNS authority (alternative to `--arns-wallet`). Falls back to `ARNS_KEY`. Payment: - `--on-demand`: Top up with this token if the credits cannot cover the upload. Choices: `ario`, `solana`, `solana-usdc` (Solana keys), `base-eth`, `base-usdc` (EVM keys). Requires `--max-token-amount`. See [On-Demand Payment](#on-demand-payment). - `--max-token-amount`: Most the top-up may spend, in whole tokens (e.g. `0.5`). Caps the whole deploy. - `--paid-by`, `--ignore-approvals`, `--use-signer-balance-first`: who pays. See [Shared credits](#shared-credits). - `--dev`: Use Turbo's development sandbox for both upload and payment. - `--uploader` (alias `--upload-url`), `--payment-url`: Custom Turbo services. See [Bundler service](#bundler-service). Upload behaviour: - `--no-dedupe`: Disable deduplication (do not cache or reuse previous uploads) - `--dedupe-cache-max-entries`: Maximum number of entries to keep in the dedupe cache (LRU). Default: `10000` - `--incremental`: Reuse files already on Arweave, including on a machine with no local cache. Off by default. Cannot be combined with `--no-dedupe` or `--dedupe-cache-max-entries 0`. See [Incremental uploads](#incremental-uploads). - `--incremental-gateway`: Gateway whose GraphQL endpoint is queried for past uploads when `--incremental` is set. Default: `https://turbo-gateway.com` - `--compress`: Compress files before upload and tag them with `Content-Encoding`. Choices: `gzip`, `br`, `none` (default). See [Compression](#compression). - `--compress-exclude`: Comma-separated globs of files to upload uncompressed, e.g. `"llms*.txt,*.md"` **`upload`** (explicit upload without ArNS): accepts `--deploy-folder`, `--deploy-file`, `--fallback-file`, wallet/signer flags, the payment flags, the dedupe and incremental flags, and `--compress` / `--compress-exclude` only. # Compression (/ario-deploy/compression) Arweave storage is priced per byte, and HTML, JavaScript, CSS and JSON typically shrink 5-8x when compressed (a 169 MB static docs site uploads as 22 MiB). `--compress` compresses each file before upload and tags it with `Content-Encoding`; gateways return that header, and browsers decompress transparently. ```bash ario-deploy deploy --wallet ./wallet.json --deploy-folder ./out --compress gzip ``` In the GitHub Action (`compress` needs v1.1.0 or later; pin the version, since the floating `v1` tag is moved by hand and may lag): ```yaml - uses: ar-io/ar-io-deploy@v1.1.0 with: deploy-key: ${{ secrets.DEPLOY_KEY }} deploy-folder: ./dist compress: gzip compress-exclude: 'llms*.txt,*.md' ``` - **Prefer `gzip`.** Gateways send the encoded bytes to every client, whether or not it asked for compression. Every browser and HTTP library understands gzip; `br` was ~17% smaller than gzip on a static docs site, but some non-browser clients cannot decode it. - **Formats that are already compressed** are uploaded as-is: already-compressed formats (JPEG, PNG, GIF, WebP, AVIF, HEIC, WOFF/WOFF2, MP3, M4A, Ogg/Opus, MP4, WebM, and zip/gz/br/bz2/xz/zst/7z/rar archives). Other images and fonts (`.svg`, `.ico`, `.ttf`, `.otf`) are compressed. Every other file is compressed, even a tiny one gzip makes a few bytes larger, so its tags always match how it was planned. - **Exclude files meant for non-browser clients** with `--compress-exclude`, e.g. text files that tools fetch with `curl`: `--compress-exclude "llms*.txt,*.md"`. A pattern without `/` matches the file name in any directory. - **Gateways must label items they have not indexed yet.** Right after a deploy, a gateway may serve a data item before it has indexed the item's tags. An ar-io-node without the fix for that (ar-io-node #964/#966) sends the gzip bytes with no Content-Encoding` header, and browsers render garbage until the item is indexed -- or indefinitely, on a gateway that never indexes the bundle. The ar.io and Turbo gateways (`turbo-gateway.com`, `ardrive.net`, and those serving `*.ar.io`) have the fix; other operators get it by upgrading. Deploy to a test undername first and load it through each gateway that matters, including through Wayfinder, which may pick any gateway. - **Deduplication still works**, including `--incremental`. Compressed uploads are cached (and found on chain) under their own key, so turning compression on re-uploads each file once, and later deploys skip unchanged files as usual. # Deduplication (/ario-deploy/deduplication) By default, ario-deploy caches your deployment log to prevent uploading duplicate (unchanged) files. This saves both time and upload costs by reusing existing data on Arweave. **How it works:** 1. When you deploy, ario-deploy hashes each file in your build 2. It checks the local cache for matching hashes from previous uploads 3. Files that haven't changed are skipped - the existing transaction ID is reused 4. Files identical to another file in the same deploy are uploaded once and share its transaction (static exports often write the same payload under several names) 5. Only new or modified files are uploaded to Arweave, and each id is written to the cache the moment it lands, so a deploy that fails or is interrupted part-way does not pay for those files again 6. The cache is stored locally in `.ario-deploy/transaction-cache.json`, with a separate file per Turbo network (`--dev` uploads never stand in for production ones) Entries are keyed on the file's content and content type (plus encoding when compressed), so byte-identical files served as different types are never confused. Caches written by 1.x, keyed on the hash alone, are still honoured, except for empty files, whose hash says nothing about their type. Symlinks inside the deploy folder are followed only while they point inside it; a link to a file outside the folder stops the deploy, since uploading it would publish that file permanently. #### Files that are never uploaded The check is built to stop a key from being published by accident. It cannot promise to find a key that someone disguises on purpose (reversed, split across files, or in an encoding of their own), so keep keys outside the project folder. Before any request is made, `deploy` and `upload` list the files once, read every one of them and refuse to publish a private key. Only the files that were checked are uploaded. The error names the file and never prints the key. There is no flag to override this. **Keys the run holds.** Every key the command was given (`--wallet`, `--arns-wallet`, `--private-key`, `--arns-private-key`, `DEPLOY_KEY` and `ARNS_KEY`, whichever are set) is searched for in every file and every file name. The search covers: - the key's raw bytes, hex in any case (also as `0x0c, 0x22`, `\x0c\x22` or `0c:22`), a decimal byte list such as `[12, 34, ...]`, base58, and base64 or base64url at any alignment - an Arweave key's private JWK fields, the base64 JWK that `DEPLOY_KEY` holds, and each key file base64-encoded, as in a `data:` URI - text with spaces, line breaks, string concatenation, `\u`, `\x` and percent escapes removed, UTF-16 text, every string of a JSON file, and printable text inside binary files Gzip, brotli (`.br`), zip and tar files are opened and searched, including archives inside archives up to three levels deep. A compressed file is refused as one that could not be checked when it expands to more than 1 GiB, is nested deeper, is damaged or encrypted, or is a 7z, RAR, xz, bzip2, Zstandard or cabinet archive. The run also stops when a wallet file, or a hard link or symlink to one, is inside the deploy folder or is the `--deploy-file`. **Keys the run does not hold.** The run also stops on what can be proved to be a private key, in files and inside the archives above: - an environment file: `.env`, `.env.local`, `.env.example`, `prod.env`, in any case (scripts and pages such as `env.js` are not refused for their name) - a PEM private key block (`-----BEGIN PRIVATE KEY-----` and the RSA, EC, DSA, OPENSSH and ENCRYPTED forms); public keys and certificates are not refused - a Solana keypair written as a byte array, base58 or hex, checked by deriving its public half from its seed - an object with a private exponent `d` and an RSA-sized modulus `n` (2048 bits or more) together, as JSON, inside a string or base64-encoded What is not detected for a key the run does not hold: a 32-byte seed or an Ethereum key on its own in any form (it cannot be told from a hash), brotli data without a `.br` name, and keys inside compressed parts of other formats, such as PNG text chunks or PDF streams. `.git` folders are left out of folder uploads, with a one-line note. The Turbo credit check runs after this planning step, so it prices only what will actually be uploaded, not the whole folder. **Disable deduplication:** If you need to force a fresh upload of all files (e.g., for debugging or to ensure a completely new deployment). Files that are identical within the same deploy are still uploaded once and share a transaction, since that reuses nothing from earlier deploys: ```bash ario-deploy deploy --wallet ./wallet.json --no-dedupe ``` **Limit cache size:** The dedupe cache uses an LRU (Least Recently Used) eviction strategy. By default, it keeps up to 10,000 entries. You can adjust this limit: ```bash ario-deploy deploy --wallet ./wallet.json --dedupe-cache-max-entries 1000 ``` **Cache location:** The cache files are stored in `.ario-deploy/` in your project root. You can: - Add it to `.gitignore` if you don't want to share cache across team members - Commit it to share cached transaction IDs with your team (reduces duplicate uploads) - Delete it to start fresh: `rm -rf .ario-deploy/` # Dependencies (/ario-deploy/dependencies) - **@ar.io/sdk** - For ANT operations and ArNS management on Solana - **@ardrive/turbo-sdk** - For fast file uploads to Arweave (and signer types) - **@solana/kit** - Solana RPC clients and transaction signers for ArNS updates - **bs58** - Base58 encoding/decoding for Solana keys - **@oclif/core** - CLI framework - **mime-types** - MIME type detection # Features (/ario-deploy/features) - **Turbo SDK Integration:** Uses Turbo SDK for fast, reliable file uploads to Arweave - **On-Demand Payment:** Top up Turbo credits with ARIO, SOL, USDC or Base ETH when a deploy needs them - **Shared credits:** Spend credits other wallets have shared with your upload key - **Arweave Manifest v0.2.0:** Creates manifests with fallback support for SPAs - **Optional ArNS Updates:** Updates ArNS records via ANT with new transaction IDs - **Automated Workflow:** Integrates with GitHub Actions for continuous deployment - **Git Hash Tagging:** In CI (GitHub Actions), tags uploaded data items with the deploying commit SHA. Under `--incremental` the tag moves to the manifest only — see [Incremental uploads](#incremental-uploads) for why a per-deploy tag on a file cannot be allowed. - **Incremental Uploads (opt-in):** `--incremental` pays only for the files that actually changed, recovering the rest from your own past uploads even on a machine with no local cache. See [Incremental uploads](#incremental-uploads). - **404 Fallback Detection:** Automatically sets `404.html` as the manifest fallback when present, so deep links into a single-page app resolve instead of 404ing. Override with `--fallback-file \` — an SPA that only builds `index.html` can point at that instead. - **Network Support:** ArNS updates run against the Solana ARIO programs on `mainnet` or `devnet`, with an optional custom RPC URL - **Flexible Deployment:** Supports deploying a folder or a single file - **Modern CLI:** Built with oclif for a robust command-line experience - **TypeScript:** Fully typed for better developer experience # Free tier (/ario-deploy/free-tier) Turbo uploads small files for free. The limits are: - **105 KiB per file** (per data item). A larger file is billed. - **10 MiB over the lifetime of a wallet**, and **10 MiB over the lifetime of an IP range**. Turbo meters both, and an upload is free only while both have allowance left. `ario-deploy` can check the wallet's allowance before it uploads. It cannot check the IP range, so a deploy can pass the credit check ("within this wallet's free tier") and still be refused at upload time with HTTP 402 when other people on the same network have used the range's allowance. See [402 Payment Required](#troubleshooting). To go past the free tier, add [Turbo credits](https://turbo.ardrive.io), use [`--on-demand`](#on-demand-payment), or have credits [shared](#shared-credits) to your wallet. The sandbox (`--dev`) has its own, larger limit and is for testing only: see [Bundler service](#bundler-service). # Incremental uploads (/ario-deploy/incremental-uploads) `--incremental` makes a redeploy pay only for the files that actually changed. Arweave storage is permanent, so re-uploading byte-identical files buys nothing. Build tools content-hash their output, so between two deploys of a real site only a couple of entry chunks change — everything else is already on chain and can be referenced by its existing transaction id in the path manifest. ```bash ario-deploy deploy --wallet ./wallet.json --incremental ``` Measured on a 1,229-file static docs site, redeployed from a fresh CI runner with no local cache and `--compress gzip`: 1,228 files were found on chain and one was uploaded (2.5 MiB), where a cold deploy uploaded 33 MiB. **How it works:** 1. Every file in the folder is hashed (SHA-256). 2. Each file is looked up in the local dedupe cache, and then — for anything the cache cannot answer — among your own past uploads on chain. 3. Only the remainder is uploaded, and each transaction id reaches the cache as it lands — on the leading edge, then coalesced onto a 500 ms trailing timer, and flushed on `SIGINT`/`SIGTERM` so Ctrl-C does not lose files you have already paid for. `SIGHUP` and `SIGBREAK` are not handled, so a closed terminal or a dropped SSH session can still lose the current batch; CI is covered, since GitHub Actions cancels with `SIGINT` then `SIGTERM`. 4. The manifest is assembled from the remembered ids plus the new ones. **Why the on-chain lookup matters:** every uploaded file carries a `File-SHA256` tag, which makes it findable again from nothing but the bytes on disk. That is what a CI job needs. CI runs from a fresh checkout, so `.ario-deploy/transaction-cache.json` is often missing or stale even with `actions/cache` restoring it — and without the on-chain lookup every redeploy pays for the whole bundle again. **The tag invariant:** a data item's id covers its tags, so a tag whose value changes between deploys — a commit SHA above all — moves every file's id on every deploy and defeats deduplication. The failure is silent: the upload succeeds, the manifest is correct, and the bill doubles. In incremental mode files therefore carry only deploy-invariant tags (`App-Name`, `Content-Type`, `File-SHA256`, plus `Content-Encoding` when compressed), and the `GIT-HASH` provenance tag rides on the manifest instead, which is rewritten every deploy anyway. The tag set is asserted in code, so a future addition fails loudly rather than quietly costing money. **Reuse is keyed on content type as well as content.** Two files with identical bytes served under different types — `a.json` and `b.txt` — stay two uploads, because a gateway serves whatever `Content-Type` the data item carries and collapsing them would serve one of them as the other. Cache entries are therefore keyed `\|\` (plus `|\` when compressed) in every mode. Incremental mode never falls back to a 1.x hash-only entry, so switching a project to `--incremental` may re-upload once and is cheap from then on. **What it trusts:** only your own wallet's past transactions, matched on the 43-character address a gateway indexes an owner as — derived locally as `base64url(sha256(publicKey))`, which is correct for all four signer types. Every result is then re-checked against the owner and content type in the gateway's own response, which catches a buggy or misconfigured gateway. It cannot catch a malicious one, since the owner, tags and id all come from that same response: point `--incremental-gateway` only at a gateway you trust, because a wrong id would land in both the permanent manifest and the local cache. **Limits and caveats:** - **Lookups are batched.** Hashes are sent 100 per GraphQL request, because gateways cap the size of a query (an ar.io gateway refuses ~1,100 hashes with "Max query size exceeded"). A site of any size is covered; each batch is paged until its files are accounted for, up to 20 pages. - **The credits pre-flight prices only what will be sent**: the files still to upload plus an estimate of the manifest, which is uploaded on every deploy. A fully reused redeploy is priced at the manifest alone. - **Gateway GraphQL indexing lags an upload by a few minutes.** Two machines deploying the same _new_ file at the same moment can each pay for it. It costs a fraction of a cent and never produces a wrong manifest. - **A gateway that is slow, unreachable or erroring costs reuse, not correctness.** Requests that fail transiently (HTTP 429 or 5xx, a timeout, a network error) are retried twice with a short backoff. A batch that still fails costs only its own files, which are uploaded again, and the run says how many batches it could not look up. If no batch can be looked up at all, the run warns and uploads everything the local cache does not already hold. - **A doomed deploy takes longer to say so.** Every queued upload settles before a failure is reported, so a systemic failure (bad credentials, exhausted credits) on a very large folder surfaces at the end rather than immediately. The same uploads were always attempted, so the bill is unchanged; the alternative stranded ids that had been paid for and never written down. - **Ignored for `--deploy-file`.** Reuse works through the manifest, and a single file has no manifest. The run warns rather than silently doing nothing. - **Cache entries are keyed differently in each mode**, so a project that toggles `--incremental` on and off stores up to two entries per file against the shared `--dedupe-cache-max-entries` cap: `\` (or `gzip:\` when compressed) without it, and `\|\` (or `\|\|gzip`) with it. **Notes:** - Off by default. Nothing changes for an existing pipeline until you pass the flag. - Refused alongside `--no-dedupe` or `--dedupe-cache-max-entries 0`, which ask for the opposite. - Works with `--compress`: each file's `File-SHA256` is the hash of the file on disk, and a compressed upload also carries `Content-Encoding`, so a lookup only ever reuses an upload made with the same encoding. Turning compression on or off uploads each file once more, then reuse resumes. - The lookup uses `https://turbo-gateway.com/graphql` by default, where uploads made through Turbo are indexed within minutes (about 5-7 in our measurements), before they are bundled into a block. Override it with `--incremental-gateway` — for example when uploading through another bundler with `--uploader`. # On-Demand Payment (/ario-deploy/on-demand-payment) With `--on-demand`, a deploy whose credits cannot cover the upload buys what it is short, once, before the first file uploads. `--max-token-amount` is required and caps that purchase for the whole deploy. The token has to be one your upload key can pay with: | Upload key (`--sig-type`) | `--on-demand` tokens | | ------------------------- | ------------------------------------- | | `solana` | `ario`, `solana`, `solana-usdc` | | `ethereum`, `polygon` | `base-eth`, `base-usdc` | | `arweave` | none: top up Turbo credits in advance | ```bash ario-deploy deploy --sig-type solana --wallet ./id.json --deploy-folder ./dist --on-demand ario --max-token-amount 1.5 ario-deploy deploy --sig-type ethereum --private-key "0x..." --on-demand base-eth --max-token-amount 0.1 ``` **How it works:** 1. Each file the deploy will actually upload is priced through Turbo. A file within the upload service's free size limit is free only while your wallet's free-tier allowance lasts, so once that is spent small files are priced too. 2. If the credits you can spend (see [Shared credits](#shared-credits)) cover it, nothing is bought. 3. Otherwise the shortfall plus a 10% buffer is converted at Turbo's quoted rate. If that exceeds `--max-token-amount`, the deploy stops before paying anything. 4. The top-up is paid once, and the deploy waits up to two minutes for Turbo to credit it before uploading anything. If Turbo has not credited the top-up by then, the deploy stops without uploading and records the transfer in `.ario-deploy/`. Re-run once it confirms: the next run waits for that transfer instead of buying another. A transfer the payment service rejects is reported as such, and nothing is uploaded. # Package.json Scripts (/ario-deploy/package-json-scripts) Add deployment scripts to your `package.json`: ```json { "scripts": { "build": "vite build", "deploy": "pnpm build && ario-deploy deploy --arns-name \", "deploy:staging": "pnpm build && ario-deploy deploy --arns-name \ --undername staging", "deploy:devnet": "pnpm build && ario-deploy deploy --arns-name \ --cluster devnet", "deploy:on-demand": "pnpm build && ario-deploy deploy --arns-name \ --sig-type solana --on-demand ario --max-token-amount 1.5" } } ``` These read the upload key from `DEPLOY_KEY` and the Solana ArNS authority key from `ARNS_KEY`. Deploy with: ```bash DEPLOY_KEY=$(base64 -i wallet.json) ARNS_KEY=\ pnpm run deploy ``` Or with on-demand payment in ARIO, which needs a Solana upload key (here the same key does both jobs): ```bash DEPLOY_KEY=\ ARNS_KEY=\ pnpm deploy:on-demand ``` # Security & Best Practices (/ario-deploy/security-best-practices) - **Dedicated Wallet:** Always use a dedicated wallet for deployments to minimize security risks - **Wallet Encoding:** Arweave wallets must be base64 encoded to be used in the deployment script - **ArNS Name:** Required only when updating an ANT/ArNS target undername or root record - **Turbo Credits:** Ensure your wallet has sufficient Turbo Credits, or use on-demand payment for automatic funding - **On-Demand Limits:** Set reasonable `--max-token-amount` limits to prevent unexpected costs - **Secret Management:** Keep your `DEPLOY_KEY` secret secure and never commit it to your repository - **Wallet Location:** Never keep a wallet file inside the folder you deploy. `ario-deploy` refuses to upload one it recognizes (see [Files that are never uploaded](#files-that-are-never-uploaded)), but other tools that publish the folder do not - **Build Security:** Always check your build for exposed environmental secrets before deployment, as data on Arweave is permanent # Shared credits (/ario-deploy/shared-credits) Credits another wallet has shared with your upload key ([Turbo credit sharing](https://docs.ardrive.io/docs/turbo/)) are used automatically: the credit check counts them, and every data item names the sharing wallets as payers, which is what the bundler needs to charge them. Your own balance covers whatever they do not. - `--paid-by \`: use only these wallets' shared credits (comma-separated), then your own balance. - `--ignore-approvals`: ignore shared credits; pay only from the upload key's own balance. - `--use-signer-balance-first`: spend the upload key's own balance before shared credits. # Troubleshooting (/ario-deploy/troubleshooting) - **Error: "DEPLOY_KEY environment variable not set":** Verify your base64 encoded wallet is set as the `DEPLOY_KEY` environment variable - **Error: "deploy-folder does not exist":** Check that your build folder exists and the path is correct - **Error: "deploy-file does not exist":** Check that your build file exists and the path is correct - **Error: "ArNS name does not exist":** Verify the ArNS name is correct and exists in the specified network - **Upload timeouts:** Files have a timeout for upload. Large files may fail and require optimization - **"402 Payment Required" (or "Turbo refused the upload as unpaid"):** The upload service will not take the files for free and no credits cover them. Free uploads are up to 105 KiB per file and 10 MiB over the lifetime of a wallet and of an IP range, so a wallet with allowance left can still be refused when its IP range has used up its own. Add Turbo credits at https://turbo.ardrive.io, re-run with `--on-demand` and `--max-token-amount`, or have credits shared to the wallet. Do not use `--dev` to get around it: a sandbox upload is not permanent. Files that uploaded before the failure are cached, so a re-run does not pay for them again - **Insufficient Turbo Credits:** Use `--on-demand` with `--max-token-amount` to automatically fund uploads when balance is low - **On-demand payment fails:** Ensure the upload wallet holds the token, and that the token matches the key: `ario`, `solana` or `solana-usdc` with `--sig-type solana`; `base-eth` or `base-usdc` with an Ethereum or Polygon key - **"Insufficient Turbo credits" on the sandbox with valid sandbox credits:** Use `--dev`, or pass `--payment-url https://payment.services.ar-io.dev` with a custom `--uploader`, so the balance is read from the sandbox - **Credits shared with you are not used:** They are used automatically unless `--ignore-approvals` is set; with `--paid-by`, only the listed wallets count - **Deep links 404 but the homepage loads:** The manifest has no `fallback`. Emit a `404.html` or pass `--fallback-file index.html` — see [Single-page apps](#single-page-apps) - **Deep links still 404 right after a redeploy:** Gateways cache the previous manifest's 404s for around a minute. Retry with a cache-busting query string before assuming the deploy failed - **Error: "Fallback file not found in folder":** `--fallback-file` takes a path relative to the deploy folder, e.g. `index.html`, not `./dist/index.html`