Skip to content
This repository has been archived by the owner on Oct 4, 2024. It is now read-only.

Commit

Permalink
fix: bring back ledger support
Browse files Browse the repository at this point in the history
  • Loading branch information
gagdiez committed Feb 29, 2024
1 parent 1a0d738 commit 7ef8c10
Show file tree
Hide file tree
Showing 14 changed files with 572 additions and 18 deletions.
31 changes: 28 additions & 3 deletions commands/account/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const inspectResponse = require('../../utils/inspect-response');
const { assertCredentials, storeCredentials } = require('../../utils/credentials');
const { DEFAULT_NETWORK } = require('../../config');
const chalk = require('chalk');
const { getPublicKeyForPath } = require('../../utils/ledger');

module.exports = {
command: 'create-account <new-account-id>',
Expand All @@ -28,6 +29,23 @@ module.exports = {
type: 'string',
default: '1'
})
.option('signWithLedger', {
alias: ['useLedgerKey'],
desc: 'Use Ledger for signing',
type: 'boolean',
default: false
})
.option('ledgerPath', {
desc: 'HD key path',
type: 'string',
default: "44'/397'/0'/0'/1'"
})
.option('ledgerPK', {
alias: ['newLedgerKey'],
desc: 'Initialize the account using the public key from the Ledger',
type: 'string',
default: "44'/397'/0'/0'/1'"
})
.option('publicKey', {
desc: 'Public key to initialize the account with',
type: 'string',
Expand Down Expand Up @@ -64,22 +82,26 @@ async function create(options) {
throw new Error(chalk`Please specify if you want the account to be fund by a faucet (--useFaucet) or through an existing --accountId)`);
}

if (options.ledgerPK && options.publicKey) {
throw new Error('Please specify only one of --publicKeyFromLedger or --publicKey');
}

if (options.useFaucet) {
if (options.networkId === 'mainnet') throw new Error('Pre-funding accounts is not possible on mainnet');
} else {
if (!options.useAccount) throw new Error('Please specify an account to sign the transaction (--useAccount)');
await assertCredentials(options.useAccount, options.networkId, options.keyStore);
await assertCredentials(options);
}

// assert account being created does not exist
const newAccountId = options.newAccountId;
await assertAccountDoesNotExist(newAccountId, near);

// If no public key is specified, create a random one
let keyPair;
let publicKey = options.publicKey;
let publicKey = options.ledgerPK ? await getPublicKeyForPath(options.ledgerPK) : options.publicKey;

if (!publicKey) {
// If no public key is specified, create a random one
keyPair = KeyPair.fromRandom('ed25519');
publicKey = keyPair.getPublicKey();
}
Expand Down Expand Up @@ -120,6 +142,9 @@ async function create(options) {

if (keyPair) {
storeCredentials(newAccountId, options.networkId, options.keyStore, keyPair, true);
} else {
console.log('Public key was provided, so we are not storing credentials (since we don\'t have the private key)');
console.log('If you have the private key, you can import it using `near add-credentials`');
}

// The faucet does not throw on error, so we force it here
Expand Down
13 changes: 12 additions & 1 deletion commands/account/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ module.exports = {
aliases: ['delete'],
desc: 'Delete account, sending remaining NEAR to a beneficiary',
builder: (yargs) => yargs
.option('signWithLedger', {
alias: ['useLedgerKey'],
desc: 'Use Ledger for signing',
type: 'boolean',
default: false
})
.option('ledgerPath', {
desc: 'HD key path',
type: 'string',
default: "44'/397'/0'/0'/1'"
})
.option('networkId', {
desc: 'Which network to use. Supports: mainnet, testnet, custom',
type: 'string',
Expand All @@ -30,7 +41,7 @@ const confirmDelete = function (accountId, beneficiaryId) {
};

async function deleteAccount(options) {
await assertCredentials(options.accountId, options.networkId, options.keyStore);
await assertCredentials(options);
const near = await connect(options);
const beneficiaryAccount = await near.account(options.beneficiaryId);

Expand Down
13 changes: 12 additions & 1 deletion commands/contract/call.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ module.exports = {
desc: 'Account that will execute the actions',
type: 'string'
})
.option('signWithLedger', {
alias: ['useLedgerKey'],
desc: 'Use Ledger for signing',
type: 'boolean',
default: false
})
.option('ledgerPath', {
desc: 'HD key path',
type: 'string',
default: "44'/397'/0'/0'/1'"
})
.option('networkId', {
desc: 'Which network to use. Supports: mainnet, testnet, custom',
type: 'string',
Expand Down Expand Up @@ -58,7 +69,7 @@ async function scheduleFunctionCall(options) {
options.keyStore.setKey(options.networkId, options.accountId, keyPair);
}

await assertCredentials(options.accountId, options.networkId, options.keyStore);
await assertCredentials(options);

const deposit = options.depositYocto != null ? options.depositYocto : utils.format.parseNearAmount(options.deposit);
console.log(`Scheduling a call: ${options.contractName}.${options.methodName}(${options.args || ''})` +
Expand Down
2 changes: 1 addition & 1 deletion commands/contract/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const checkExistingContract = async function (prevCodeHash) {
};

async function deploy(options) {
await assertCredentials(options.accountId, options.networkId, options.keyStore);
await assertCredentials(options);

const near = await connect(options);
const account = await near.account(options.accountId);
Expand Down
13 changes: 12 additions & 1 deletion commands/keys/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ module.exports = {
required: false,
default: '0'
})
.option('signWithLedger', {
alias: ['useLedgerKey'],
desc: 'Use Ledger for signing',
type: 'boolean',
default: false
})
.option('ledgerPath', {
desc: 'HD key path',
type: 'string',
default: "44'/397'/0'/0'/1'"
})
.option('networkId', {
desc: 'Which network to use. Supports: mainnet, testnet, custom',
type: 'string',
Expand All @@ -33,7 +44,7 @@ module.exports = {
};

async function addAccessKey(options) {
await assertCredentials(options.accountId, options.networkId, options.keyStore);
await assertCredentials(options);

console.log(`Adding ${options.contractId ? 'function call access' : 'full access'} key ${options.publicKey} to ${options.accountId}.`);
if (options.contractId) console.log(`Limited to: ${options.allowance} $NEAR and methods: ${options.methodNames.join(' ')}.`);
Expand Down
13 changes: 12 additions & 1 deletion commands/keys/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ module.exports = {
command: 'delete-key <account-id> <access-key>',
desc: 'Delete access key',
builder: (yargs) => yargs
.option('signWithLedger', {
alias: ['useLedgerKey'],
desc: 'Use Ledger for signing',
type: 'boolean',
default: false
})
.option('ledgerPath', {
desc: 'HD key path',
type: 'string',
default: "44'/397'/0'/0'/1'"
})
.option('networkId', {
desc: 'Which network to use. Supports: mainnet, testnet, custom',
type: 'string',
Expand All @@ -23,7 +34,7 @@ module.exports = {
};

async function deleteAccessKey(options) {
await assertCredentials(options.accountId, options.networkId, options.keyStore);
await assertCredentials(options);
const near = await connect(options);
const account = await near.account(options.accountId);
const approval = await isAllApprovalsGranted(account, options.accessKey);
Expand Down
14 changes: 13 additions & 1 deletion commands/transactions/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ module.exports = {
desc: 'Amount of NEAR tokens to send',
type: 'string',
})
.option('signWithLedger', {
alias: ['useLedgerKey'],
desc: 'Use Ledger for signing',
type: 'boolean',
default: false
})
.option('ledgerPath', {
desc: 'HD key path',
type: 'string',
default: "44'/397'/0'/0'/1'"
})
.option('networkId', {
desc: 'Which network to use. Supports: mainnet, testnet, custom',
type: 'string',
Expand All @@ -23,10 +34,11 @@ module.exports = {
};

async function sendMoney(options) {
await assertCredentials(options.sender, options.networkId, options.keyStore);
await assertCredentials(options);

const near = await connect(options);
const account = await near.account(options.sender);

try {
console.log(`Sending ${options.amount} NEAR to ${options.receiver} from ${options.sender}`);
const result = await account.sendMoney(options.receiver, utils.format.parseNearAmount(options.amount));
Expand Down
13 changes: 12 additions & 1 deletion commands/validators/stake.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ module.exports = {
type: 'string',
required: true,
})
.option('signWithLedger', {
alias: ['useLedgerKey'],
desc: 'Use Ledger for signing',
type: 'boolean',
default: false
})
.option('ledgerPath', {
desc: 'HD key path',
type: 'string',
default: "44'/397'/0'/0'/1'"
})
.option('networkId', {
desc: 'Which network to use. Supports: mainnet, testnet, custom',
type: 'string',
Expand All @@ -36,7 +47,7 @@ module.exports = {


async function stake(options) {
await assertCredentials(options.accountId, options.networkId, options.keyStore);
await assertCredentials(options);
console.log(`Staking ${options.amount} (${utils.format.parseNearAmount(options.amount)}N) on ${options.accountId} with public key = ${qs.unescape(options.stakingKey)}.`);
const near = await connect(options);
const account = await near.account(options.accountId);
Expand Down
24 changes: 24 additions & 0 deletions ledger-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# CREATE WITH FAUCET
yarn start create-account miralos-como-sonrien.testnet --ledgerPK --useFaucet

# CREATE WITH ACCOUNT
yarn start create-account ya.miralos-como-sonrien.testnet --ledgerPK --useAccount miralos-como-sonrien.testnet --useLedgerKey

# SEND-NEAR
yarn start send-near miralos-como-sonrien.testnet influencer.testnet 0.01 --useLedgerKey

# CALL
yarn start call hello.near-examples.testnet set_greeting '{"greeting":"Hola"}' --accountId miralos-como-sonrien.testnet --useLedgerKey

# DEPLOY
yarn start deploy miralos-como-sonrien.testnet ./contract.wasm --signWithLedger

## ADD KEY
yarn start add-key miralos-como-sonrien.testnet ed25519:GnsdHdSrhe8v3MMAQi2bnXR59xMDwdkSRAFZ961ydxWZ --signWithLedger

## DELETE KEY
yarn start delete-key miralos-como-sonrien.testnet ed25519:GnsdHdSrhe8v3MMAQi2bnXR59xMDwdkSRAFZ961ydxWZ --signWithLedger

## DELETE
yarn start delete miralos-como-sonrien.testnet influencer.testnet --useLedgerKey
yarn start delete ya.miralos-como-sonrien.testnet influencer.testnet --signWithLedger
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@ledgerhq/hw-transport-node-hid": "^6.28.4",
"ascii-table": "^0.0.9",
"bs58": "^5.0.0",
"chalk": "^4.1.2",
"flagged-respawn": "^2.0.0",
"is-ci": "^3.0.1",
"near-api-js": "^3.0.2",
"near-ledger-js": "^0.2.1",
"near-seed-phrase": "^0.2.0",
"open": "^8.4.2",
"stoppable": "^1.1.0",
Expand Down
9 changes: 9 additions & 0 deletions utils/connect.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
const { connect: nearConnect } = require('near-api-js');
const { getConfig } = require('../config');
const { getPublicKeyForPath, signForPath } = require('./ledger');

module.exports = async function connect({ keyStore, ...options }) {
// If using Ledger, override the signer so that it uses the Ledger device
if (options.signWithLedger) {
options.signer = {
getPublicKey: () => getPublicKeyForPath(options.ledgerPath),
signMessage: (m) => signForPath(m, options.ledgerPath)
};
}

// TODO: Avoid need to wrap in deps
const config = getConfig(options.networkId);
return await nearConnect({ ...options, ...config, deps: { keyStore } });
Expand Down
14 changes: 8 additions & 6 deletions utils/credentials.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
const chalk = require('chalk');

async function assertCredentials(accountId, networkId, keyStore) {
async function assertCredentials({ accountId, networkId, keyStore, signWithLedger }) {
if (signWithLedger) return;

const key = await keyStore.getKey(networkId, accountId);
if(key) return;
if (key) return;

console.error(chalk`You are trying to use the account {bold.white ${accountId}}, but do not have the credentials locally (network: {bold.white ${networkId}})`);
process.exit(1);
}

async function storeCredentials(accountId, networkId, keyStore, keyPair, force) {
const key = await keyStore.getKey(networkId, accountId);
if(key && !force){

if (key && !force) {
console.log(chalk`The account {bold.white ${accountId}} already has local credentials (network: {bold.white ${networkId}})`);
return;
}

console.log(chalk`Storing credentials for account: {bold.white ${accountId}} (network: {bold.white ${networkId}})`);
console.log(`Saving key to '~/.near-credentials/${networkId}/${accountId}.json'`);
await keyStore.setKey(networkId, accountId, keyPair);
await keyStore.setKey(networkId, accountId, keyPair);
}

module.exports = {assertCredentials, storeCredentials};
module.exports = { assertCredentials, storeCredentials };
Loading

0 comments on commit 7ef8c10

Please sign in to comment.