Skip to content

Commit

Permalink
Merge pull request #141 from ardriveapp/PE-661_send_signed_tx
Browse files Browse the repository at this point in the history
PE-661: send signed tx
  • Loading branch information
arielmelendez authored Nov 29, 2021
2 parents 1cbd7dd + 56b9776 commit e172900
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .pnp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ ardrive upload-file --wallet-file /path/to/my/wallet.json --parent-folder-id "f0
2. [Dealing With Network Congestion](#dealing-with-network-congestion)
3. [Check for network congestion before uploading](#check-congestion)
4. [Front-run Congestion By Boosting Miner Rewards](#boost)
5. [Send AR Transactions From a Cold Wallet](#cold-tx)
4. [All ArDrive CLI Commands](#all-ardrive-cli-commands)
5. [Getting Help](#getting-help)

Expand Down Expand Up @@ -835,6 +836,40 @@ ardrive get-mempool | jq 'length'
ardrive upload-file --wallet-file /path/to/my/wallet.json --parent-folder-id "f0c58c11-430c-4383-8e54-4d864cc7e927" --local-file-path ./helloworld.txt --boost 1.5
```

#### Send AR Transactions From a Cold Wallet<a id="cold-tx"></a>

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 airgapped 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 airgapped 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 Internect-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 <Arweave address of cold wallet>
```

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 <dest Arweave address> -a <AR amount to send> --last-tx <from previous steps> --reward "<from previous steps>" > 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
```

# All ArDrive CLI Commands

```shell
Expand Down Expand Up @@ -885,12 +920,16 @@ send-ar
get-drive-key
get-file-key

last-tx


Arweave Ops
===========
tx-status
base-reward
get-mempool

create-tx
send-tx
tx-status

# Learn more about a command:
ardrive <command> --help
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"dependencies": {
"ardrive-core-js": "1.0.4",
"arweave": "^1.10.16",
"axios": "^0.21.1",
"commander": "^8.2.0",
"lodash": "^4.17.21",
"prompts": "^2.4.0"
Expand Down
27 changes: 27 additions & 0 deletions src/commands/base_reward.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ByteCount } from 'ardrive-core-js';
import { CLICommand, ParametersHelper } from '../CLICommand';
import { CLIAction } from '../CLICommand/action';
import { SUCCESS_EXIT_CODE } from '../CLICommand/error_codes';
import { BoostParameter } from '../parameter_declarations';
import axios, { AxiosResponse } from 'axios';

async function getBaseReward(byteCount?: ByteCount): Promise<string> {
const response: AxiosResponse = await axios.get(`https://arweave.net/price/${byteCount ?? 0}`);
return `${response.data}`;
}

new CLICommand({
name: 'base-reward',
parameters: [BoostParameter],
action: new CLIAction(async function action(options) {
const parameters = new ParametersHelper(options);
let baseRewardStr = await getBaseReward();
const multiple = parameters.getOptionalBoostSetting();
if (multiple) {
baseRewardStr = multiple.boostReward(baseRewardStr);
}

console.log(baseRewardStr);
return SUCCESS_EXIT_CODE;
})
});
52 changes: 52 additions & 0 deletions src/commands/create_tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ADDR, AR, JWKWallet, TxID, W, Winston } from 'ardrive-core-js';
import { CreateTransactionInterface } from 'arweave/node/common';
import { cliArweave, CLI_APP_NAME, CLI_APP_VERSION } from '..';
import { CLICommand } from '../CLICommand';
import { ParametersHelper } from '../CLICommand';
import { CLIAction } from '../CLICommand/action';
import { SUCCESS_EXIT_CODE } from '../CLICommand/error_codes';
import {
ArAmountParameter,
DestinationAddressParameter,
LastTxParameter,
RewardParameter,
WalletTypeParameters
} from '../parameter_declarations';

new CLICommand({
name: 'create-tx',
parameters: [
ArAmountParameter,
DestinationAddressParameter,
RewardParameter,
LastTxParameter,
...WalletTypeParameters
],
action: new CLIAction(async function action(options) {
const parameters = new ParametersHelper(options);
const arAmount = parameters.getRequiredParameterValue(ArAmountParameter, AR.from);
const winston: Winston = arAmount.toWinston();
const destAddress = parameters.getRequiredParameterValue(DestinationAddressParameter, ADDR);
const jwkWallet = (await parameters.getRequiredWallet()) as JWKWallet;
const lastTxParam = parameters.getParameterValue(LastTxParameter); // Can be provided as a txID or empty string
const last_tx = lastTxParam && lastTxParam.length ? `${TxID(lastTxParam)}` : undefined;

// Create and sign transaction
const trxAttributes: Partial<CreateTransactionInterface> = {
target: destAddress.toString(),
quantity: winston.toString(),
reward: `${parameters.getRequiredParameterValue(RewardParameter, W)}`,
last_tx
};
const transaction = await cliArweave.createTransaction(trxAttributes, jwkWallet.getPrivateKey());
transaction.addTag('App-Name', CLI_APP_NAME);
transaction.addTag('App-Version', CLI_APP_VERSION);
transaction.addTag('Type', 'transfer');

await cliArweave.transactions.sign(transaction, jwkWallet.getPrivateKey());

console.log(JSON.stringify(transaction));

return SUCCESS_EXIT_CODE;
})
});
4 changes: 2 additions & 2 deletions src/commands/get_address.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { CLICommand, ParametersHelper } from '../CLICommand';
import { CLIAction } from '../CLICommand/action';
import { SUCCESS_EXIT_CODE } from '../CLICommand/error_codes';
import { SeedPhraseParameter, WalletFileParameter } from '../parameter_declarations';
import { WalletTypeParameters } from '../parameter_declarations';

new CLICommand({
name: 'get-address',
parameters: [WalletFileParameter, SeedPhraseParameter],
parameters: [...WalletTypeParameters],
action: new CLIAction(async function action(options) {
const parameters = new ParametersHelper(options);
const address = await parameters.getWalletAddress();
Expand Down
4 changes: 2 additions & 2 deletions src/commands/get_balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { cliWalletDao } from '..';
import { CLICommand, ParametersHelper } from '../CLICommand';
import { CLIAction } from '../CLICommand/action';
import { SUCCESS_EXIT_CODE } from '../CLICommand/error_codes';
import { AddressParameter, SeedPhraseParameter, WalletFileParameter } from '../parameter_declarations';
import { AddressParameter, WalletTypeParameters } from '../parameter_declarations';

new CLICommand({
name: 'get-balance',
parameters: [WalletFileParameter, SeedPhraseParameter, AddressParameter],
parameters: [...WalletTypeParameters, AddressParameter],
action: new CLIAction(async function action(options) {
const parameters = new ParametersHelper(options);
const address = await parameters.getWalletAddress();
Expand Down
30 changes: 17 additions & 13 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import '../parameter_declarations';
import './base_reward';
import './create_drive';
import './create_folder';
import './create_tx';
import './drive_info';
import './upload_file';
import './tx_status';
import './get_mempool';
import './send_ar';
import './get_balance';
import './get_address';
import './file_info';
import './folder_info';
import './generate_seedphrase';
import './generate_wallet';
import './list_folder';
import './list_drive';
import './get_address';
import './get_balance';
import './get_drive_key';
import './get_file_key';
import './get_mempool';
import './last_tx';
import './list_all_drives';
import './folder_info';
import './create_folder';
import './file_info';
import './list_drive';
import './list_folder';
import './move_file';
import './move_folder';
import './get_drive_key';
import './get_file_key';
import './send_ar';
import './send_tx';
import './tx_status';
import './upload_file';
23 changes: 23 additions & 0 deletions src/commands/last_tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ArweaveAddress } from 'ardrive-core-js';
import { CLICommand, ParametersHelper } from '../CLICommand';
import { CLIAction } from '../CLICommand/action';
import { SUCCESS_EXIT_CODE } from '../CLICommand/error_codes';
import { AddressParameter, WalletTypeParameters } from '../parameter_declarations';
import axios, { AxiosResponse } from 'axios';

async function lastTxForAddress(address: ArweaveAddress): Promise<string> {
const response: AxiosResponse = await axios.get(`https://arweave.net/wallet/${address}/last_tx`);
return `${response.data}`;
}

new CLICommand({
name: 'last-tx',
parameters: [...WalletTypeParameters, AddressParameter],
action: new CLIAction(async function action(options) {
const parameters = new ParametersHelper(options);
const walletAddress = await parameters.getWalletAddress();
const lastTx = await lastTxForAddress(walletAddress);
console.log(lastTx);
return SUCCESS_EXIT_CODE;
})
});
10 changes: 8 additions & 2 deletions src/commands/send_ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import {
BoostParameter,
DestinationAddressParameter,
DryRunParameter,
WalletFileParameter
WalletTypeParameters
} from '../parameter_declarations';

new CLICommand({
name: 'send-ar',
parameters: [ArAmountParameter, DestinationAddressParameter, WalletFileParameter, BoostParameter, DryRunParameter],
parameters: [
ArAmountParameter,
DestinationAddressParameter,
BoostParameter,
DryRunParameter,
...WalletTypeParameters
],
action: new CLIAction(async function action(options) {
const parameters = new ParametersHelper(options);
const arAmount = parameters.getRequiredParameterValue(ArAmountParameter, AR.from);
Expand Down
54 changes: 54 additions & 0 deletions src/commands/send_tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { cliArweave } from '..';
import { CLICommand, ParametersHelper } from '../CLICommand';
import { DryRunParameter, TxFilePathParameter } from '../parameter_declarations';
import * as fs from 'fs';
import Transaction from 'arweave/node/lib/transaction';
import * as crypto from 'crypto';
import { ERROR_EXIT_CODE, SUCCESS_EXIT_CODE } from '../CLICommand/error_codes';
import { CLIAction } from '../CLICommand/action';
import { b64UrlToBuffer, bufferTob64Url } from 'ardrive-core-js';

new CLICommand({
name: 'send-tx',
parameters: [TxFilePathParameter, DryRunParameter],
action: new CLIAction(async function action(options) {
const parameters = new ParametersHelper(options);
const transaction = new Transaction(
JSON.parse(fs.readFileSync(parameters.getRequiredParameterValue(TxFilePathParameter)).toString())
);
const srcAddress = bufferTob64Url(
crypto.createHash('sha256').update(b64UrlToBuffer(transaction.owner)).digest()
);

console.log(`Source address: ${srcAddress}`);
console.log(`AR amount sent: ${cliArweave.ar.winstonToAr(transaction.quantity)}`);
console.log(`Destination address: ${transaction.target}`);

const response = await (async () => {
if (options.dryRun) {
return { status: 200, statusText: 'OK', data: '' };
} else {
return await cliArweave.transactions.post(transaction);
}
})();
if (response.status === 200 || response.status === 202) {
console.log(
JSON.stringify(
{
txID: transaction.id,
winston: transaction.quantity,
reward: transaction.reward
},
null,
4
)
);

return SUCCESS_EXIT_CODE;
} else {
console.log(`Failed to send tx with error: ${response.statusText}`);
}

return ERROR_EXIT_CODE;
})
});
Loading

0 comments on commit e172900

Please sign in to comment.