Refactor generateFaucetWallet to return a Wallet (#1564)

* add Wallet.generate() and 

* return Wallet in generateFaucetWallet

* refactor Wallet tests

* rename wallet-generation.ts to generateFaucetWallet.ts

* rename and move Wallet.ts to src/wallet/index.ts and update webpack config
This commit is contained in:
Omar Khan
2021-09-01 17:41:43 -04:00
committed by Mayukha Vadari
parent 62538a75b1
commit e0f4d99d86
8 changed files with 116 additions and 48 deletions

View File

@@ -115,7 +115,7 @@ import prepareTrustline from "../transaction/trustline";
import { TransactionJSON, Instructions, Prepare } from "../transaction/types";
import * as transactionUtils from "../transaction/utils";
import { deriveAddress, deriveXAddress } from "../utils/derive";
import generateFaucetWallet from "../wallet/wallet-generation";
import generateFaucetWallet from "../wallet/generateFaucetWallet";
import { Connection, ConnectionUserOptions } from "./connection";

View File

@@ -11,4 +11,4 @@ export * from "./utils";
// Broadcast client is experimental
export { BroadcastClient } from "./client/broadcastClient";
export * from "./Wallet";
export { default as Wallet } from "./wallet";

View File

@@ -3,11 +3,10 @@ import _ from "lodash";
import binaryCodec from "ripple-binary-codec";
import keypairs from "ripple-keypairs";
import { Client } from "..";
import { Client, Wallet } from "..";
import { SignedTransaction } from "../common/types/objects";
import { xrpToDrops } from "../utils";
import { computeBinaryTransactionHash } from "../utils/hashes";
import Wallet from "../Wallet";
import { SignOptions, KeyPair, TransactionJSON } from "./types";
import * as utils from "./utils";

View File

@@ -1,6 +1,6 @@
import https = require("https");
import { Client } from "..";
import { Client, Wallet } from "..";
import { errors } from "../common";
import { RippledError } from "../common/errors";
import { isValidAddress } from "../common/schema-validator";
@@ -23,41 +23,44 @@ const MAX_ATTEMPTS = 20; // Maximum attempts to retrieve a balance
/**
* Generates a random wallet with some amount of XRP (usually 1000 XRP).
*
* @param this
* @param address - An existing XRPL address to fund, if undefined, a new wallet will be created.
* @param client - Client.
* @param wallet - An existing XRPL Wallet to fund, if undefined, a new Wallet will be created.
* @returns A Wallet on the Testnet or Devnet that contains some amount of XRP.
* @throws When either Client isn't connected or unable to fund wallet address.
*/
async function generateFaucetWallet(
this: Client,
address?: string
): Promise<FaucetWallet | void> {
if (!this.isConnected()) {
client: Client,
wallet?: Wallet
): Promise<Wallet | void> {
if (!client.isConnected()) {
throw new RippledError("Client not connected, cannot call faucet");
}
// Initialize some variables
let body: Uint8Array | undefined;
let startingBalance = 0;
const faucetUrl = getFaucetUrl(this);
// Generate a new Wallet if no existing Wallet is provided or its address is invalid to fund
const fundWallet =
wallet && isValidAddress(wallet.classicAddress)
? wallet
: Wallet.generate();
// If the user provides an existing wallet to fund
if (address && isValidAddress(address)) {
// Create the POST request body
body = new TextEncoder().encode(
JSON.stringify({
destination: address,
})
);
// Retrieve the existing account balance
const addressToFundBalance = await getAddressXrpBalance(this, address);
// Create the POST request body
const body: Uint8Array | undefined = new TextEncoder().encode(
JSON.stringify({
destination: fundWallet.classicAddress,
})
);
// Retrieve the existing account balance
const addressToFundBalance = await getAddressXrpBalance(
client,
fundWallet.classicAddress
);
// Check the address balance is not undefined and is a number
if (addressToFundBalance && !isNaN(Number(addressToFundBalance))) {
startingBalance = Number(addressToFundBalance);
} else {
startingBalance = 0;
}
}
// Check the address balance is not undefined and is a number
const startingBalance =
addressToFundBalance && !isNaN(Number(addressToFundBalance))
? Number(addressToFundBalance)
: 0;
const faucetUrl = getFaucetUrl(client);
// Options to pass to https.request
const options = {
@@ -82,20 +85,20 @@ async function generateFaucetWallet(
// "application/json; charset=utf-8"
if (response.headers["content-type"]?.startsWith("application/json")) {
const wallet: FaucetWallet = JSON.parse(body);
const classicAddress = wallet.account.classicAddress;
const faucetWallet: FaucetWallet = JSON.parse(body);
const classicAddress = faucetWallet.account.classicAddress;
if (classicAddress) {
try {
// Check at regular interval if the address is enabled on the XRPL and funded
const isFunded = await hasAddressBalanceIncreased(
this,
client,
classicAddress,
startingBalance
);
if (isFunded) {
resolve(wallet);
resolve(fundWallet);
} else {
reject(
new errors.XRPLFaucetError(

View File

@@ -9,11 +9,11 @@ import {
verify,
} from "ripple-keypairs";
import ECDSA from "./common/ecdsa";
import { ValidationError } from "./common/errors";
import { SignedTransaction } from "./common/types/objects";
import { signOffline } from "./transaction/sign";
import { SignOptions } from "./transaction/types";
import ECDSA from "../common/ecdsa";
import { ValidationError } from "../common/errors";
import { SignedTransaction } from "../common/types/objects";
import { signOffline } from "../transaction/sign";
import { SignOptions } from "../transaction/types";
/**
* A utility for deriving a wallet composed of a keypair (publicKey/privateKey).
@@ -23,12 +23,27 @@ import { SignOptions } from "./transaction/types";
class Wallet {
readonly publicKey: string;
readonly privateKey: string;
readonly classicAddress: string;
readonly seed?: string;
private static readonly defaultAlgorithm: ECDSA = ECDSA.ed25519;
private static readonly defaultDerivationPath: string = "m/44'/144'/0'/0/0";
constructor(publicKey: string, privateKey: string) {
constructor(publicKey: string, privateKey: string, seed?: string) {
this.publicKey = publicKey;
this.privateKey = privateKey;
this.classicAddress = deriveAddress(publicKey);
this.seed = seed;
}
/**
* Generates a new Wallet using a generated seed.
*
* @param algorithm - The digital signature algorithm to generate an address for.
* @returns A new Wallet derived from a generated seed.
*/
static generate(algorithm: ECDSA = Wallet.defaultAlgorithm): Wallet {
const seed = generateSeed({ algorithm });
return Wallet.fromSeed(seed);
}
/**
@@ -98,7 +113,7 @@ class Wallet {
algorithm: ECDSA = Wallet.defaultAlgorithm
): Wallet {
const { publicKey, privateKey } = deriveKeypair(seed, { algorithm });
return new Wallet(publicKey, privateKey);
return new Wallet(publicKey, privateKey, seed);
}
/**
@@ -136,7 +151,7 @@ class Wallet {
* @returns An X-address.
*/
getXAddress(tag: number, test = false): string {
return classicAddressToXAddress(deriveAddress(this.publicKey), tag, test);
return classicAddressToXAddress(this.classicAddress, tag, test);
}
}

View File

@@ -1,7 +1,7 @@
import { assert } from "chai";
import ECDSA from "../../src/common/ecdsa";
import Wallet from "../../src/Wallet";
import Wallet from "../../src/wallet";
/**
* Wallet testing.
@@ -9,6 +9,49 @@ import Wallet from "../../src/Wallet";
* Provides tests for Wallet class.
*/
describe("Wallet", function () {
describe("generate", function () {
const classicAddressPrefix = "r";
const ed25519KeyPrefix = "ED";
const secp256k1PrivateKeyPrefix = "00";
it("generates a new wallet using default algorithm", function () {
const wallet = Wallet.generate();
assert.isString(wallet.publicKey);
assert.isString(wallet.privateKey);
assert.isString(wallet.classicAddress);
assert.isString(wallet.seed);
assert.isTrue(wallet.publicKey.startsWith(ed25519KeyPrefix));
assert.isTrue(wallet.privateKey.startsWith(ed25519KeyPrefix));
assert.isTrue(wallet.classicAddress.startsWith(classicAddressPrefix));
});
it("generates a new wallet using algorithm ecdsa-secp256k1", function () {
const algorithm = ECDSA.secp256k1;
const wallet = Wallet.generate(algorithm);
assert.isString(wallet.publicKey);
assert.isString(wallet.privateKey);
assert.isString(wallet.classicAddress);
assert.isString(wallet.seed);
assert.isTrue(wallet.privateKey.startsWith(secp256k1PrivateKeyPrefix));
assert.isTrue(wallet.classicAddress.startsWith(classicAddressPrefix));
});
it("generates a new wallet using algorithm ed25519", function () {
const algorithm = ECDSA.ed25519;
const wallet = Wallet.generate(algorithm);
assert.isString(wallet.publicKey);
assert.isString(wallet.privateKey);
assert.isString(wallet.classicAddress);
assert.isString(wallet.seed);
assert.isTrue(wallet.publicKey.startsWith(ed25519KeyPrefix));
assert.isTrue(wallet.privateKey.startsWith(ed25519KeyPrefix));
assert.isTrue(wallet.classicAddress.startsWith(classicAddressPrefix));
});
});
describe("fromSeed", function () {
const seed = "ssL9dv2W5RK8L3tuzQxYY6EaZhSxW";
const publicKey =
@@ -65,7 +108,7 @@ describe("Wallet", function () {
});
describe("fromEntropy", function () {
const entropy: number[] = new Array(16).fill(0);
let entropy;
const publicKey =
"0390A196799EE412284A5D80BF78C3E84CBB80E1437A0AECD9ADF94D7FEAAFA284";
const privateKey =
@@ -75,6 +118,11 @@ describe("Wallet", function () {
const privateKeyED25519 =
"ED0B6CBAC838DFE7F47EA1BD0DF00EC282FDF45510C92161072CCFB84035390C4D";
beforeEach(function () {
const entropySize = 16;
entropy = new Array(entropySize).fill(0);
});
it("derives a wallet using entropy", function () {
const wallet = Wallet.fromEntropy(entropy);

View File

@@ -1,6 +1,9 @@
import { assert } from "chai";
import { getFaucetUrl, FaucetNetwork } from "../src/wallet/wallet-generation";
import {
getFaucetUrl,
FaucetNetwork,
} from "../src/wallet/generateFaucetWallet";
import setupClient from "./setupClient";

View File

@@ -17,7 +17,7 @@ function getDefaultConfiguration() {
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/^ws$/, './wsWrapper'),
new webpack.NormalModuleReplacementPlugin(/^\.\/wallet$/, './wallet-web'),
new webpack.NormalModuleReplacementPlugin(/^\.\/wallet\/index$/, './wallet-web'),
new webpack.NormalModuleReplacementPlugin(
/^.*setup-api$/,
'./setup-api-web'