Skip to content

Commit f556473

Browse files
authored
Merge pull request #10 from lukebuehler/l2
Milestone 3 - Layer 2 Support
2 parents 6a1e91c + 57a2322 commit f556473

38 files changed

+2262
-1442
lines changed

README.md

+54-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# azimuth-cli
2-
The azimuth-cli is "Bridge for the command line." It should allow ship owners and operators to do the same things that they can in [Bridge](https://bridge.urbit.org/) but via the command line, and in batch mode.
32

4-
The primary usage of the cli is to query and modify the [azimuth contracts](https://github.com/urbit/azimuth) on Ethereum.
3+
This is a command line tool to work with Urbit ID, which is the idenity layer behind [Urbit](https://urbit.org/) and is managed as a set of NFTs on the Ethereum blockchain.
4+
5+
The azimuth-cli is "Bridge for the command line." It should allow Urbit ship owners and operators to do the same things that they can in [Bridge](https://bridge.urbit.org/) but via the command line, and in batch mode.
6+
7+
The primary functionality of the cli is to query and modify the [azimuth contracts](https://github.com/urbit/azimuth) on Ethereum.
58

69
## Install
710

@@ -12,6 +15,8 @@ Due to an issues when building the azimuth-js package (as described [here](https
1215
Simply install the npm package globally:
1316
`npm install -g azimuth-cli`
1417

18+
**When Upgrading to the L2 version:** Make sure to delete the `cli-config.json` file in your home directory, usually in a folder called `.azimuth/`.
19+
1520
### Development or Manual Install
1621
1) Clone this [repo](https://github.com/lukebuehler/azimuth-cli)
1722
1) run `npm install`
@@ -24,11 +29,19 @@ To run the tests:
2429

2530

2631
## Usage
32+
33+
*Note: Using the CLI with a L2 roller is WIP, see [here for instructions](docs/roller.md).*
34+
2735
After installing the npm package, just type `azimuth-cli` in the command line to see the options. You can also use `azi` for short.
2836

29-
There are three main types of commands: `list`, `generate`, and `modify`. First, the `list` commands do not make any changes, they just print information to the command line. But `generate` commands usually save something to the current work directory; use this, for example, to generate HD wallets. The `modify` commands actually change the blockchain and usually require the private key of the address that owns the urbit point you are making a change to. The `modify` commands also save data to the work directory, such as Ethereum transaction receipts.
37+
There are three main types of commands: `get`, `generate`, and `modify`. First, the `get` commands do not make any changes, they just print information to the command line. But `generate` commands usually save something to the current work directory; use this, for example, to generate HD wallets.
38+
39+
The `modify` commands actually change the blockchain and usually require the private key of the address that owns the urbit point you are making a change to. The `modify` commands have two versions: `modify-l1` and `modify-l2`. This is because Urbit IDs can either be modified directly through an Ethereum smart contract called Azimuth, aka "L1", or via a Layer 2 solution that is cheaper, which uses a "roller" that gathers transactions that modify Urbit IDs and then submits them as one batch to Ethereum, and is called the "L2" solution. The `modify` commands also save data to the work directory, such as Ethereum or roller transaction receipts.
3040

31-
More complicated operations--such as spawning ships, keying them, and transfering them to master tickets--cannot be executed in a single command. Multiple commands need to be called in order (see the example below).
41+
More complicated operations--such as spawning ships, keying them, and transfering them to master tickets--cannot be executed in a single command. Multiple commands need to be called in order (see the examples below).
42+
43+
### Setting Up Your Own Layer 2 Roller
44+
If you want to modify large batches of points in one go--for example spawn 200 planets--then you need to [run your own roller](docs/roller.md). By default, the CLI points to the official roller run by Urbit. See the `--roller-provider` option in the CLI.
3245

3346
### Work Directory & Idempotent Commands
3447
Many commands, especially the `generate` and `modify` ones, need a work directory to fulfill their function. The reason for this is that a command may be called multiple times, but the end effect needs to always be the same. For example, if you call `azi generate spawn-list --count=10` multiple times, the resulting `spawn-list.txt` file will only be created once, and will not change in subsequent calls (unless the `--force` option is provided). The same goes for the `modify` commands.
@@ -42,35 +55,43 @@ The work directory is the current folder or can be supplied with the `--work-dir
4255
For the full documentation, please install the cli and explore the commands and sub-commands with the `--help` option.
4356

4457
`aimuth-cli`
45-
* `list` - Prints azimuth data to the console.
58+
* `get` - Retrieves data about points, rollers, and azimuth, and prints it to the console. By default, uses a L2 roller to get the information.
4659
* `children <point>` - Lists all child points of a certain Urbit point.
4760
* `owner <address>` - Lists all points owned by that Ethereum address.
4861
* `details <point>` - Prints details about the point.
49-
* `gas-price` - Outputs the current Etherum gas prices. This is helpful if you want to provide a gas limit in the `modify` commands.
62+
* `gas-price` - Outputs the current Etherum gas prices. This is helpful if you want to provide a gas limit in the `modify-l1` commands.
63+
* `roller-info` - Prints details about the L2 roller.
64+
* `roller-info` - Prints pending roller transactions.
5065
* `generate` - Generates various files that can be used in the `modify` commands.
5166
* `spawn-list <point>` - Creates a `spawn-list.txt` file that contains a number of points that can be spawned under the provided point.
5267
* `wallet` - Generates an HD wallet for each provided point and saves each wallet in JSON format in the current work directory. Use this especially if you plan to give the points away. Then, in subsequent commands, supply the `--use-wallet-files` option.
5368
* `network-keys` - Creates the network keyfile for each supplied point, and either creates a JSON file with the private and public network keys or uses the network keys from the walled files.
5469
* `report` - Generates a CSV report for the provided points, containing patp, p, ticket, network keys, addresses, and transactions executed so far.
55-
* `modify` - Modifies the state of one or more points on the Ethereum blockchain (the azimuth contracts). For many of these commands to work, other files will have to have been generated with the `generate` commands.
70+
* `modify-l1` - Modifies the state of one or more points on the Ethereum blockchain (the azimuth contracts). For many of these commands to work, other files will have to have been generated with the `generate` commands.
5671
* `spawn` - Spawns multiple points to the supplied address
5772
* `management-proxy` - Sets the management proxy address for the points.
5873
* `spawn-proxy` - Sets the spawn-proxy address for the points.
5974
* `network-key` - Sets the network keys for the points, which is required to be able to boot the Urbit.
6075
* `transfer` - Transfers the point to a target address or the wallet files.
61-
76+
* `modify-l2` - Modifies the state of one or more points via a L2 roller. The roller then submits the changes to the L2 Ethereum contract. Any point modified via this command, needs to be on L2.
77+
* `spawn` - Spawns multiple points to the supplied address. The galaxy or star that spawns needs to be on L2 or the spawn proxy needs to be on L2.
78+
* `management-proxy` - Sets the management proxy address for the points.
79+
* `network-key` - Sets the network keys for the points, which is required to be able to boot the Urbit.
80+
* `transfer` - Transfers the point to a target address or the wallet files.
6281

6382
### Examples
64-
#### Spawn, Set Network Keys, and Transfer to Master Ticket
83+
#### Spawn, Set Network Keys, and Transfer to Master Ticket on Azimuth (L1)
6584
This is an example of spawning planets and creating a master ticket for them. You would do this if you want to give some planets away to friends. It is similar to what you can do in Bridge, but we do it for 5 planets in one go. Generating a master ticket itself is not enough, though. Ownership needs to be transferred to the owner address that is associated with the master ticket. But for the master ticket to be usable, networking keys need to be set. Hence, we first spawn to a temporary address (usually the same as the owner or spawn-proxy of the star, here ~sardys), then set the keys, and only then move the planet to its own address--that of the HD wallet.
6685

86+
The star you are spawning from needs to be on L1!
87+
6788
```
6889
# create a directory for your work
6990
mkdir spawn-planets
7091
cd spawn-planets
7192
7293
# pick 5 random, unspawned planets under ~sardys and save them in a file
73-
azi generate spawn-list ~sardys --count=5 --pick=random
94+
azi generate spawn-list ~sardys --count=5 --pick=random --use-azimuth
7495
7596
# now, generate HD wallet files based on the previous list
7697
azi generate wallet --points-file=spawn-list.txt
@@ -82,15 +103,35 @@ azi generate network-key --use-wallet-files
82103
83104
# spawn the 5 planets that can be found in the wallet files,
84105
# providing the PK of the wallet that owns ~sardys, or is the spawn proxy
85-
azi modify spawn --use-wallet-files --address=0xSardysOwnershipAddress --private-key=0x1234567890
106+
azi modify-l1 spawn --use-wallet-files --address=0xSardysOwnershipAddress --private-key=0x1234567890
86107
87108
# set the network keys on the blockchain
88-
azi modify network-key --use-wallet-files --private-key=0x1234567890
109+
azi modify-l1 network-key --use-wallet-files --private-key=0x1234567890
89110
90111
# transfer each planet ownership to the address of the wallet
91-
azi modify transfer --use-wallet-files --private-key=0x1234567890
112+
azi modify-l1 transfer --use-wallet-files --private-key=0x1234567890
92113
```
93114

115+
#### Spawn, Set Network Keys, and Transfer to Master Ticket on Azimuth (L2)
94116

117+
This is the same example as above but using a roller to spawn the planets.
118+
119+
The star you are spawning from needs to be on L2 or have the spawn proxy set to L2! Here is more info about the [Layer 2 solution](https://urbit.org/docs/azimuth/l2/layer2).
120+
121+
```
122+
azi generate spawn-list ~sampel --count=2 --pick=random --use-roller
123+
124+
azi generate wallet --points-file=spawn-list.txt
125+
126+
azi generate network-key --use-wallet-files
127+
128+
azi modify-l2 spawn --use-wallet-files --address=0xSpawnProxy --private-key=0xSpawnProxyKey
129+
130+
azi modify-l2 management-proxy --use-wallet-files --address=0xManagementProxy --private-key=0xSpawnProxyKey
131+
132+
azi modify-l2 network-key --use-wallet-files --private-key=0xSpawnProxyKey
133+
134+
azi modify-l2 transfer --use-wallet-files --private-key=0xSpawnProxyKey
135+
```
95136

96137

cli-config.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
2-
"ethProviderGanache": "'http://localhost:8545'",
2+
"ethProviderGanache": "http://localhost:8545",
33
"ethProviderRopsten": "https://ropsten.infura.io/v3/f23d3259814348dbb693726415858db8",
4-
"ethProviderMainnet": "https://mainnet.infura.io/v3/f23d3259814348dbb693726415858db8"
4+
"ethProviderMainnet": "https://mainnet.infura.io/v3/f23d3259814348dbb693726415858db8",
5+
"rollerLocal": "http://localhost:8080/v1/roller",
6+
"rollerUrbit": "https://roller.urbit.org/v1/roller"
57
}

cli.js

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ function getUniversalOptions()
2626
choices: ['ganache', 'ropsten', 'mainnet'],
2727
type: 'string'
2828
},
29+
'roller-provider':{
30+
describe: 'What L2 roller provider to use.',
31+
default: 'urbit',
32+
choices: ['local', 'urbit'],
33+
type: 'string'
34+
},
2935
'config-file':{
3036
describe: 'What config file to use.',
3137
default: files.ensureDefaultConfigFilePath(),

cmds/generate_cmds/report.js

+69-19
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const ob = require('urbit-ob')
22
const kg = require('urbit-key-generation');
33
const ticket = require('up8-ticket');
44
const _ = require('lodash')
5-
const {files, validate, eth, findPoints} = require('../../utils')
5+
const {files, validate, eth, findPoints, rollerApi} = require('../../utils')
66
const ajs = require('azimuth-js')
77

88
exports.command = 'report'
@@ -36,27 +36,47 @@ exports.builder = (yargs) =>{
3636
if (!argv.pointsFile && !argv.points && !argv.useWalletFiles) throw new Error('You must provide either --points-file, --points, or --use-wallet-files')
3737
return true
3838
});
39+
40+
yargs.option('use-roller',{
41+
describe: 'Enforce using the roller (L2) for all data and do not allow fallback to azimuth (L1).',
42+
type: 'boolean',
43+
conflicts: 'use-azimuth'
44+
});
45+
yargs.option('use-azimuth',{
46+
describe: 'Enforce using azimuth (L1) for all data and do not allow fallback to the roller (L2).',
47+
type: 'boolean',
48+
conflicts: 'use-roller'
49+
});
3950
}
4051

4152
exports.handler = async function (argv)
4253
{
43-
const workDir = files.ensureWorkDir(argv.workDir);
44-
const ctx = await eth.createContext(argv);
54+
const source = await rollerApi.selectDataSource(argv);
55+
let ctx = null;
56+
let rollerClient = null;
57+
if(source == 'azimuth'){
58+
ctx = await eth.createContext(argv);
59+
}
60+
else{
61+
rollerClient = rollerApi.createClient(argv);
62+
}
4563

64+
const workDir = files.ensureWorkDir(argv.workDir);
4665
const wallets = argv.useWalletFiles ? findPoints.getWallets(workDir) : null;
4766
const points = findPoints.getPoints(argv, workDir, wallets);
4867

4968
const csvHeader =
50-
'patp,p,ship_type,master_ticket,network_keyfile,' +
51-
'owner_address,proxy_address,' + //management_address is omitted because there is currently no API to retrieve the management proxy address
69+
'patp,p,ship_type,parent_patp,master_ticket,network_keyfile,' +
70+
'dominion,owner_address,spawn_proxy_address,management_proxy_address,' +
5271
'spawn_transaction,management_proxy_transaction,spawn_proxy_transaction,network_key_transaction,transfer_transaction';
5372
let csvLines = [csvHeader];
5473

5574
console.log(`Will process ${points.length} points for the report.`);
5675
for (const p of points) {
5776
const patp = ob.patp(p);
77+
const patpParent = ob.sein(patp);
5878
const shipType = ob.clan(patp);
59-
let csvLine = `${patp},${p},${shipType},`;
79+
let csvLine = `${patp},${p},${shipType},${patpParent},`;
6080

6181
//see if we have a wallet to get the master from
6282
let masterTicket = '';
@@ -73,19 +93,41 @@ exports.handler = async function (argv)
7393
}
7494
csvLine += `${masterTicket},${networkKeyfileContents},`;
7595

76-
//get the addresses
77-
const ownerAddress = await ajs.azimuth.getOwner(ctx.contracts, p);
78-
const spawnProxyAddress = await ajs.azimuth.getSpawnProxy(ctx.contracts, p);
79-
csvLine += `${ownerAddress},${spawnProxyAddress},`;
80-
81-
//try to get the transaction reciepts
82-
let spawnTransaction = tryGetTransactionHash(patp, workDir, 'spawn');
83-
let managementProxyTransaction = tryGetTransactionHash(patp, workDir, 'managementproxy');
84-
let spawnProxyTransaction = tryGetTransactionHash(patp, workDir, 'spawnproxy');
85-
let networkkeyTransaction = tryGetTransactionHash(patp, workDir, 'networkkey');
86-
let transferTransaction = tryGetTransactionHash(patp, workDir, 'transfer');
87-
csvLine += `${spawnTransaction},${managementProxyTransaction},${spawnProxyTransaction},${networkkeyTransaction},${transferTransaction}`;
96+
//use azimuth
97+
if(source == 'azimuth'){
98+
//get the addresses
99+
const dominion = 'L1'; //todo: try to get the dominion via ajs
100+
const ownerAddress = await ajs.azimuth.getOwner(ctx.contracts, p);
101+
const spawnProxyAddress = await ajs.azimuth.getSpawnProxy(ctx.contracts, p);
102+
const managementProxyAddress = ''; //does not work yet: await ajs.azimuth.getManagementProxy(ctx.contracts, p);
103+
csvLine += `${dominion},${ownerAddress},${spawnProxyAddress},${managementProxyAddress},`;
104+
105+
//try to get the transaction receipts
106+
let spawnTransaction = tryGetTransactionHash(patp, workDir, 'spawn');
107+
let managementProxyTransaction = tryGetTransactionHash(patp, workDir, 'managementproxy');
108+
let spawnProxyTransaction = tryGetTransactionHash(patp, workDir, 'spawnproxy');
109+
let networkkeyTransaction = tryGetTransactionHash(patp, workDir, 'networkkey');
110+
let transferTransaction = tryGetTransactionHash(patp, workDir, 'transfer');
111+
csvLine += `${spawnTransaction},${managementProxyTransaction},${spawnProxyTransaction},${networkkeyTransaction},${transferTransaction}`;
112+
}
113+
//L2 roller
114+
else{
115+
//get the addresses
116+
const pointInfo = await rollerApi.getPoint(rollerClient, p);
117+
const dominion = pointInfo.dominion;
118+
const ownerAddress = pointInfo.ownership.owner.address;
119+
const spawnProxyAddress = pointInfo.ownership.spawnProxy.address;
120+
const managementProxyAddress = pointInfo.ownership.managementProxy.address;
121+
csvLine += `${dominion},${ownerAddress},${spawnProxyAddress},${managementProxyAddress},`;
88122

123+
//try to get the transaction receipts
124+
let spawnTransaction = tryGetTransactionHashL2(patp, workDir, 'spawn');
125+
let managementProxyTransaction = tryGetTransactionHashL2(patp, workDir, 'setManagementProxy');
126+
let spawnProxyTransaction = tryGetTransactionHashL2(patp, workDir, 'setSpawnProxy');
127+
let networkkeyTransaction = tryGetTransactionHashL2(patp, workDir, 'configureKeys');
128+
let transferTransaction = tryGetTransactionHashL2(patp, workDir, 'transferPoint');
129+
csvLine += `${spawnTransaction},${managementProxyTransaction},${spawnProxyTransaction},${networkkeyTransaction},${transferTransaction}`;
130+
}
89131
csvLines.push(csvLine);
90132
}
91133

@@ -95,11 +137,19 @@ exports.handler = async function (argv)
95137
}
96138

97139
function tryGetTransactionHash(patp, workDir, operationPostfix){
98-
const transactionFile = `${patp.substring(1)}-reciept-${operationPostfix}.json`;
140+
const transactionFile = `${patp.substring(1)}-receipt-${operationPostfix}.json`;
99141
if(files.fileExists(workDir, transactionFile)){
100142
return files.readJsonObject(workDir, transactionFile).transactionHash;
101143
}
102144
return '';
103145
}
104146

147+
function tryGetTransactionHashL2(patp, workDir, method){
148+
const transactionFile = `${patp.substring(1)}-receipt-L2-${method}.json`;
149+
if(files.fileExists(workDir, transactionFile)){
150+
return files.readJsonObject(workDir, transactionFile).hash;
151+
}
152+
return '';
153+
}
154+
105155
const DEFAULT_REVISION = 1;

cmds/generate_cmds/spawn-list.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
const ob = require('urbit-ob')
22
const ajs = require('azimuth-js')
33
const _ = require('lodash')
4-
5-
const {files, validate, eth} = require('../../utils')
4+
const {files, validate, eth, rollerApi} = require('../../utils')
65

76
exports.command = 'spawn-list <point>'
87
exports.desc = 'Create a list of child points to spawn from <point>. If the file already exists, this command will be a no-op.'
@@ -32,21 +31,40 @@ exports.builder = (yargs) =>{
3231
default: 'random',
3332
type: 'string',
3433
});
34+
35+
yargs.option('use-roller',{
36+
describe: 'Enforce using the roller (L2) for all data and do not allow fallback to azimuth (L1).',
37+
type: 'boolean',
38+
conflicts: 'use-azimuth'
39+
});
40+
yargs.option('use-azimuth',{
41+
describe: 'Enforce using azimuth (L1) for all data and do not allow fallback to the roller (L2).',
42+
type: 'boolean',
43+
conflicts: 'use-roller'
44+
});
3545
}
3646

3747
exports.handler = async function (argv)
3848
{
3949
const point = validate.point(argv.point, true);
50+
4051
const workDir = files.ensureWorkDir(argv.workDir);
41-
4252
if(files.fileExists(workDir, argv.output) && !argv.force)
4353
{
4454
console.log('Spawn list file already exists, will not recreate it.');
4555
return;
4656
}
4757

48-
const ctx = await eth.createContext(argv);
49-
var childPoints = await ajs.azimuth.getUnspawnedChildren(ctx.contracts, point);
58+
const source = await rollerApi.selectDataSource(argv);
59+
let childPoints = [];
60+
if(source == 'azimuth'){
61+
const ctx = await eth.createContext(argv);
62+
childPoints = await ajs.azimuth.getUnspawnedChildren(ctx.contracts, point);
63+
}
64+
else{
65+
const rollerClient = rollerApi.createClient(argv);
66+
childPoints = await rollerApi.getUnspawned(rollerClient, point);
67+
}
5068

5169
var spawnList = pickChildPoints(childPoints, argv.count, argv.pick);
5270
var spawnListPatp = _.map(spawnList, p => ob.patp(p));

cmds/get.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
exports.command = 'get <command>'
2+
exports.desc = 'Retrieve information about Urbit points, L2 rollers, and Ethereum gas prices.'
3+
exports.builder = function (yargs) {
4+
return yargs.commandDir('get_cmds')
5+
}
6+
exports.handler = function (argv) {}

0 commit comments

Comments
 (0)