azimuth-cli
Version:
Urbit Bridge for the command line
376 lines (331 loc) • 11.3 kB
JavaScript
const ob = require('urbit-ob')
const ajsUtils = require('azimuth-js').utils;
const axios = require('axios')
const {JSONRPCClient } = require('json-rpc-2.0')
const validate = require('./validate')
const { hexToBytes } = require('web3-utils');
const { ecdsaSign } = require('secp256k1');
const CRYPTO_SUITE_VERSION = 1;
let requestCounter = 0;
async function selectDataSource(argv){
if(argv.useRoller){
try{
await getRollerConfig(createClient(argv)); //will throw a connection refused error if not available
return 'roller';
}
catch(error){
throw 'Roller not available. You required the usage of the roller, please ensure it is running and can be reached.'
}
}
else if(argv.useAzimuth){
return 'azimuth';
}
else{
//check if the roller client is available;
// if yes, use the roller, otherwise use azimuth
try{
await getRollerConfig(createClient(argv)); //will throw a connection refused error if not available
return 'roller';
}
catch(error){
console.log('Roller not available, falling back to using azimuth, information might not be correct.');
return 'azimuth';
}
}
throw 'could not determine data source';
}
function nextId(){
requestCounter++;
return requestCounter.toString(); //the roller rpc need a string ID, otherwise it does it does not work
}
function createClient(argv){
let rollerUrl =
argv.rollerProvider == 'local'
? argv.rollerLocal
: argv.rollerProvider == 'urbit'
? argv.rollerUrbit
: null;
//console.log(rollerUrl);
if(rollerUrl == null){
rollerUrl = "http://localhost:8080/v1/roller";
}
var client = new JSONRPCClient(async function (jsonRPCRequest)
{
//TODO: add try catch, check for 200 response (see https://www.npmjs.com/package/json-rpc-2.0), show error codes
//console.log(JSON.stringify(jsonRPCRequest));
var response = await axios.post(rollerUrl, JSON.stringify(jsonRPCRequest));
const jsonRPCResponse = response.data;
//console.log(jsonRPCResponse)
client.receive(jsonRPCResponse)
}, nextId);
return client;
}
function signTransactionHash(msg, pk) {
const pkBuffer = Buffer.from(pk, 'hex');
if(!ajsUtils.isValidPrivate(pkBuffer))
throw 'pk is not valid';
// msg is a keccak-256 hash
//
const hashed = Buffer.from(hexToBytes(msg));
const { signature, recid } = ecdsaSign(hashed, pkBuffer);
// add key recovery parameter
const ethSignature = new Uint8Array(65);
ethSignature.set(signature);
ethSignature[64] = recid;
return `0x${Buffer.from(ethSignature).toString('hex')}`;
}
function getNonce(client, params){
const ship = params.from.ship;
const nonceParams = {
from: params.from
};
return client.request("getNonce", nonceParams);
}
//use this if signing via metamask or wallet connect
async function prepareForSigning(client, method, params){
const nonce = await getNonce(client, params);
const hashParams = {
tx: method,
nonce: nonce,
from: params.from,
data: params.data
}
return await client.request("prepareForSigning", hashParams);
}
//use this if singing directly with pk
async function getUnsignedTx(client, method, params){
const nonce = await getNonce(client, params);
const hashParams = {
tx: method,
nonce: nonce,
from: params.from,
data: params.data
}
var res = await client.request("getUnsignedTx", hashParams);
//console.log("RESULT: "+res);
return res;
}
async function addSignature(client, method, params, privateKey){
const hash = await getUnsignedTx(client, method, params);
const sig = signTransactionHash(hash, privateKey);
params["sig"] = sig;
return params;
}
function createTransactionReceipt(method, params, txHash){
return {
sig: params.sig,
hash: txHash,
type: method
}
}
//============================================
// API
//============================================
// Roller JSON-RPC API documentation:
// https://documenter.getpostman.com/view/16338962/Tzm3nx7x
function getRollerConfig(client){
return client.request("getRollerConfig", { });
}
function getAllPending(client){
return client.request("getAllPending", { });
}
function getPendingByAddress(client, address){
return client.request("getPendingByAddress", { address: address });
}
function whenNextBatch(client){
return client.request("whenNextBatch", { });
}
function getPoint(client, point){
const patp = ob.patp(validate.point(point, true));
return client.request("getPoint", { ship: patp });
}
function getShips(client, address){
return client.request("getShips", { address: address });
}
function getSpawned(client, point){
const patp = ob.patp(validate.point(point, true));
return client.request("getSpawned", { ship: patp });
}
function getUnspawned(client, point){
const patp = ob.patp(validate.point(point, true));
return client.request("getUnspawned", { ship: patp });
}
async function spawn(client, parentPoint, spawnPoint, newOwnerAddress, signingAddress, privateKey){
const parentPatp = ob.patp(validate.point(parentPoint, true));
const spawnPatp = ob.patp(validate.point(spawnPoint, true));
const newOwnerAddressValid = validate.address(newOwnerAddress, true);
const signingAddressValid = validate.address(signingAddress, true);
const proxy = await getSpawnProxyType(client, parentPatp, signingAddress);
//console.log("Proxy: "+proxy);
let params = {
address: signingAddressValid,
from: {
ship: parentPatp,
proxy: proxy
},
data: {
ship: spawnPatp,
address: newOwnerAddressValid,
}
};
const method = "spawn";
params = await addSignature(client, method, params, privateKey);
var tx = await client.request(method, params);
return createTransactionReceipt(method, params, tx);
}
async function transferPoint(client, point, reset, newOwnerAddress, signingAddress, privateKey){
const patp = ob.patp(validate.point(point, true));
const newOwnerAddressValid = validate.address(newOwnerAddress, true);
const signingAddressValid = validate.address(signingAddress, true);
const proxy = await getTransferProxyType(client, patp, signingAddress);
let params = {
address: signingAddressValid,
from: {
ship: patp,
proxy: proxy
},
data: {
reset: reset,
address: newOwnerAddressValid,
}
};
const method = "transferPoint";
params = await addSignature(client, method, params, privateKey);
var tx = await client.request(method, params);
return createTransactionReceipt(method, params, tx);
}
async function setManagementProxy(client, point, managementProxyAddress, signingAddress, privateKey){
const patp = ob.patp(validate.point(point, true));
const targetAddress = validate.address(managementProxyAddress, true);
const signingAddressValid = validate.address(signingAddress, true);
const proxy = "own"; //only the owner can set the management proxy
let params = {
address: signingAddressValid,
from: {
ship: patp,
proxy: proxy
},
data: {
address: targetAddress,
}
};
const method = "setManagementProxy";
params = await addSignature(client, method, params, privateKey);
var tx = await client.request(method, params);
return createTransactionReceipt(method, params, tx);
}
async function setSpawnProxy(client, point, spawnProxyAddress, signingAddress, privateKey){
const patp = ob.patp(validate.point(point, true));
const targetAddress = validate.address(spawnProxyAddress, true);
const signingAddressValid = validate.address(signingAddress, true);
const proxy = "own"; //only the owner can set the spawn proxy
let params = {
address: signingAddressValid,
from: {
ship: patp,
proxy: proxy
},
data: {
address: targetAddress,
}
};
const method = "setSpawnProxy";
params = await addSignature(client, method, params, privateKey);
var tx = await client.request(method, params);
return createTransactionReceipt(method, params, tx);
}
async function setTransferProxy(client, point, transferProxyAddress, signingAddress, privateKey){
const patp = ob.patp(validate.point(point, true));
const targetAddress = validate.address(transferProxyAddress, true);
const signingAddressValid = validate.address(signingAddress, true);
const proxy = "own"; //only the owner can set the transfer proxy
let params = {
address: signingAddressValid,
from: {
ship: patp,
proxy: proxy
},
data: {
address: targetAddress,
}
};
const method = "setTransferProxy";
params = await addSignature(client, method, params, privateKey);
var tx = await client.request(method, params);
return createTransactionReceipt(method, params, tx);
}
async function configureKeys(client, point, encryptPublic, authPublic, breach, signingAddress, privateKey){
const patp = ob.patp(validate.point(point, true));
const signingAddressValid = validate.address(signingAddress, true);
const proxy = await getManagementProxyType(client, patp, signingAddress); //either the owner or the manage proxy can set the keys
let params = {
address: signingAddressValid,
from: {
ship: patp,
proxy: proxy
},
data: {
encrypt: encryptPublic,
auth: authPublic,
cryptoSuite: CRYPTO_SUITE_VERSION.toString(),
breach: breach
}
};
const method = "configureKeys";
params = await addSignature(client, method, params, privateKey);
var tx = await client.request(method, params);
return createTransactionReceipt(method, params, tx);
}
async function getManagementProxyType(client, point, signingAddress){
const pointInfo = await getPoint(client, point);
if(ajsUtils.addressEquals(pointInfo.ownership.owner.address, signingAddress))
return 'own';
else if(ajsUtils.addressEquals(pointInfo.ownership.managementProxy.address, signingAddress))
return 'manage';
return undefined;
}
async function getSpawnProxyType(client, point, signingAddress){
const pointInfo = await getPoint(client, point);
if(ajsUtils.addressEquals(pointInfo.ownership.owner.address, signingAddress))
return 'own';
else if(ajsUtils.addressEquals(pointInfo.ownership.spawnProxy.address, signingAddress))
return 'spawn';
return undefined;
}
async function getTransferProxyType(client, point, signingAddress){
const pointInfo = await getPoint(client, point);
if(ajsUtils.addressEquals(pointInfo.ownership.owner.address, signingAddress))
return 'own';
else if(ajsUtils.addressEquals(pointInfo.ownership.transferProxy.address, signingAddress))
return 'transfer';
return undefined;
}
//TODO: add
// isOwner
// isManagementProxy
// isSpawnProxy
// isTransferProxy
// canConfigureKeys (either owner or management proxy)
// canTransfer (either owner or transfer proxy)
// [-> then replace checks in modify-l2 commands to use these functions]
module.exports = {
selectDataSource,
createClient,
getRollerConfig,
getAllPending,
getPendingByAddress,
whenNextBatch,
getPoint,
getShips,
getSpawned,
getUnspawned,
spawn,
transferPoint,
setManagementProxy,
setTransferProxy,
setSpawnProxy,
configureKeys,
getManagementProxyType,
getSpawnProxyType,
getTransferProxyType
}