From f744f788a11790f7a15cd0184e76c81c5339a00b Mon Sep 17 00:00:00 2001 From: Sam Jeston Date: Mon, 17 Jun 2019 13:31:19 +1000 Subject: [PATCH] Cardano Primitives (#29) Add Cardano primitives interface to support multiple implementations. The Rust-derived cardano-wallet dependency is the default and has also been updated to recent tx balance behaviour --- README.md | 4 +- src/Cardano/Cardano.ts | 49 ++++ src/Cardano/ChainSettings.ts | 3 + src/Cardano/FeeAlgorithm.ts | 3 + src/Cardano/Transaction.ts | 14 + src/Cardano/TransactionSelection.ts | 6 + src/Cardano/index.ts | 4 + src/KeyManager/InMemoryKey/index.ts | 44 --- src/KeyManager/KeyManager.ts | 6 +- src/KeyManager/index.ts | 3 +- src/Transaction/Transaction.spec.ts | 24 +- src/Transaction/Transaction.ts | 51 +--- src/Transaction/TransactionInput.ts | 3 +- src/Transaction/index.ts | 4 +- src/Utils/address_discovery.spec.ts | 34 ++- src/Utils/address_discovery.ts | 40 ++- src/Utils/coin_to_lovelace.ts | 7 - src/Utils/estimate_fee.ts | 9 - src/Utils/index.ts | 5 +- src/Utils/verify_message.spec.ts | 38 --- src/Utils/verify_message.ts | 8 - src/Wallet/Address.ts | 1 + src/Wallet/Wallet.spec.ts | 80 +++--- src/Wallet/Wallet.ts | 31 ++- src/Wallet/lib/address_derivation.spec.ts | 13 +- src/Wallet/lib/address_derivation.ts | 16 +- src/Wallet/lib/get_next_address.spec.ts | 58 ++-- src/Wallet/lib/get_next_address.ts | 15 +- src/Wallet/lib/index.ts | 1 - src/Wallet/lib/select_inputs.spec.ts | 59 ---- src/Wallet/lib/select_inputs.ts | 59 ---- src/index.ts | 31 ++- .../InMemoryKey/index.spec.ts | 28 +- src/lib/InMemoryKey/index.ts | 34 +++ src/{KeyManager => lib}/Ledger/index.spec.ts | 42 ++- src/{KeyManager => lib}/Ledger/index.ts | 44 ++- src/lib/RustCardanoPrimitives/index.spec.ts | 93 +++++++ src/lib/RustCardanoPrimitives/index.ts | 246 +++++++++++++++++ src/lib/index.ts | 3 + .../DetermineNextAddressForWallet.spec.ts | 31 ++- src/test/InMemoryKeyManager.spec.ts | 16 +- src/test/SelectInputsForTransaction.spec.ts | 31 ++- src/test/SignAndVerify.spec.ts | 13 +- src/test/WalletBalance.spec.ts | 19 +- src/test/utils/seed.ts | 251 +++++++++--------- src/test/utils/test_transaction.ts | 28 +- src/test/utils/utxo.ts | 9 +- 47 files changed, 925 insertions(+), 686 deletions(-) create mode 100644 src/Cardano/Cardano.ts create mode 100644 src/Cardano/ChainSettings.ts create mode 100644 src/Cardano/FeeAlgorithm.ts create mode 100644 src/Cardano/Transaction.ts create mode 100644 src/Cardano/TransactionSelection.ts create mode 100644 src/Cardano/index.ts delete mode 100644 src/KeyManager/InMemoryKey/index.ts delete mode 100644 src/Utils/coin_to_lovelace.ts delete mode 100644 src/Utils/estimate_fee.ts delete mode 100644 src/Utils/verify_message.spec.ts delete mode 100644 src/Utils/verify_message.ts delete mode 100644 src/Wallet/lib/select_inputs.spec.ts delete mode 100644 src/Wallet/lib/select_inputs.ts rename src/{KeyManager => lib}/InMemoryKey/index.spec.ts (64%) create mode 100644 src/lib/InMemoryKey/index.ts rename src/{KeyManager => lib}/Ledger/index.spec.ts (70%) rename src/{KeyManager => lib}/Ledger/index.ts (56%) create mode 100644 src/lib/RustCardanoPrimitives/index.spec.ts create mode 100644 src/lib/RustCardanoPrimitives/index.ts create mode 100644 src/lib/index.ts diff --git a/README.md b/README.md index 7b18d924fdb..d7c718745cc 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ The below examples are implemented as integration tests, they should be very eas - [Generate a keypair in memory from a BIP39 mnemonic](src/test/MemoryKeyManager.spec.ts) - [Message signatures](src/test/SignAndVerify.spec.ts) -- [Get the wallet balance for a BIP44 Public Account](src/test/WalletBalance.spec.ts) -- [Determine the next change and receipt addresses for a BIP44 Public Account](src/test/DetermineNextAddressForWallet.spec.ts) +- [Get the wallet balance for a BIP44 Account](src/test/WalletBalance.spec.ts) +- [Determine the next change and receipt addresses for a BIP44 Account](src/test/DetermineNextAddressForWallet.spec.ts) - [Transaction input selection](src/test/SelectInputsForTransaction.spec.ts) ## Tests diff --git a/src/Cardano/Cardano.ts b/src/Cardano/Cardano.ts new file mode 100644 index 00000000000..c8decfd8868 --- /dev/null +++ b/src/Cardano/Cardano.ts @@ -0,0 +1,49 @@ +import { TransactionInput, TransactionOutput } from '../Transaction' +import { FeeAlgorithm } from './FeeAlgorithm' +import { Transaction } from './Transaction' +import { AddressType, Address, UtxoWithAddressing } from '../Wallet' +import { ChainSettings } from './ChainSettings' +import { TransactionSelection } from './TransactionSelection' + +export interface Cardano { + buildTransaction: ( + inputs: TransactionInput[], + outputs: TransactionOutput[], + feeAlgorithm?: FeeAlgorithm + ) => Transaction + account: ( + mnemonic: string, + passphrase: string, + accountIndex: number + ) => { privateParentKey: string, publicParentKey: string } + address: ( + args: { + publicParentKey: string, + index: number, + type: AddressType + accountIndex: number + }, + chainSettings?: ChainSettings + ) => Address + signMessage: ( + args: { + privateParentKey: string + addressType: AddressType + signingIndex: number + message: string + } + ) => { signature: string, publicKey: string } + verifyMessage: ( + args: { + publicKey: string + message: string + signature: string + } + ) => boolean + inputSelection: ( + outputs: TransactionOutput[], + utxoSet: UtxoWithAddressing[], + changeAddress: string, + feeAlgorithm?: FeeAlgorithm + ) => TransactionSelection +} diff --git a/src/Cardano/ChainSettings.ts b/src/Cardano/ChainSettings.ts new file mode 100644 index 00000000000..4ec1f2b6c11 --- /dev/null +++ b/src/Cardano/ChainSettings.ts @@ -0,0 +1,3 @@ +export enum ChainSettings { + mainnet = 'mainnet', +} diff --git a/src/Cardano/FeeAlgorithm.ts b/src/Cardano/FeeAlgorithm.ts new file mode 100644 index 00000000000..f0162e52516 --- /dev/null +++ b/src/Cardano/FeeAlgorithm.ts @@ -0,0 +1,3 @@ +export enum FeeAlgorithm { + default = 'default' +} diff --git a/src/Cardano/Transaction.ts b/src/Cardano/Transaction.ts new file mode 100644 index 00000000000..0b1797fc1b2 --- /dev/null +++ b/src/Cardano/Transaction.ts @@ -0,0 +1,14 @@ +import { TransactionInput } from '../Transaction' +import { AddressType } from '../Wallet' +import { ChainSettings } from './ChainSettings' + +export interface Transaction { + toHex: () => string + toJson: () => any + id: () => string + addWitness: (args: { privateParentKey: string, addressing: TransactionInput['addressing'], chainSettings?: ChainSettings }) => void + addExternalWitness: (args: { publicParentKey: string, addressType: AddressType, witnessIndex: number, witnessHex: string }) => void + finalize: () => string + fee: () => string + estimateFee?: () => string +} diff --git a/src/Cardano/TransactionSelection.ts b/src/Cardano/TransactionSelection.ts new file mode 100644 index 00000000000..f096d12bb97 --- /dev/null +++ b/src/Cardano/TransactionSelection.ts @@ -0,0 +1,6 @@ +import { TransactionInput, TransactionOutput } from '../Transaction' + +export interface TransactionSelection { + inputs: TransactionInput[] + changeOutput: TransactionOutput +} diff --git a/src/Cardano/index.ts b/src/Cardano/index.ts new file mode 100644 index 00000000000..24e2a32ff28 --- /dev/null +++ b/src/Cardano/index.ts @@ -0,0 +1,4 @@ +export { Cardano } from './Cardano' +export { FeeAlgorithm } from './FeeAlgorithm' +export { ChainSettings } from './ChainSettings' +export { TransactionSelection } from './TransactionSelection' diff --git a/src/KeyManager/InMemoryKey/index.ts b/src/KeyManager/InMemoryKey/index.ts deleted file mode 100644 index 36f5c9bfbf5..00000000000 --- a/src/KeyManager/InMemoryKey/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { validateMnemonic } from 'bip39' -import { InvalidMnemonic } from '../errors' -import { getBindingsForEnvironment } from '../../lib/bindings' -import { AddressType } from '../../Wallet' -import { KeyManager } from '../KeyManager' -const { AccountIndex, AddressKeyIndex, BlockchainSettings, Bip44RootPrivateKey, Entropy, Witness } = getBindingsForEnvironment() - -const HARD_DERIVATION_START = 0x80000000 - -export function InMemoryKeyManager ({ password, accountNumber, mnemonic }: { password: string, accountNumber?: number, mnemonic: string }): KeyManager { - if (!accountNumber) { - accountNumber = 0 - } - - const validMnemonic = validateMnemonic(mnemonic) - if (!validMnemonic) throw new InvalidMnemonic() - - const entropy = Entropy.from_english_mnemonics(mnemonic) - const privateKey = Bip44RootPrivateKey.recover(entropy, password) - const key = privateKey.bip44_account(AccountIndex.new(accountNumber | HARD_DERIVATION_START)) - - return { - signTransaction: (transaction, rawInputs, chainSettings = BlockchainSettings.mainnet()) => { - const transactionId = transaction.id() - const transactionFinalizer = transaction.finalize() - - rawInputs.forEach(({ addressing }) => { - const privateKey = key.bip44_chain(addressing.change === 1).address_key(AddressKeyIndex.new(addressing.index)) - const witness = Witness.new_extended_key(chainSettings, privateKey, transactionId) - transactionFinalizer.add_witness(witness) - }) - - return Promise.resolve(transactionFinalizer.finalize().to_hex()) - }, - signMessage: async (addressType, signingIndex, message) => { - const privateKey = key.bip44_chain(addressType === AddressType.internal).address_key(AddressKeyIndex.new(signingIndex)) - return { - signature: privateKey.sign(Buffer.from(message)).to_hex(), - publicKey: key.public().bip44_chain(addressType === AddressType.internal).address_key(AddressKeyIndex.new(signingIndex)).to_hex() - } - }, - publicAccount: () => Promise.resolve(key.public()) - } -} diff --git a/src/KeyManager/KeyManager.ts b/src/KeyManager/KeyManager.ts index a37540a7cce..3a31dfd28bb 100644 --- a/src/KeyManager/KeyManager.ts +++ b/src/KeyManager/KeyManager.ts @@ -1,9 +1,9 @@ -import { BlockchainSettings as CardanoBlockchainSettings, Bip44AccountPublic } from 'cardano-wallet' import { AddressType } from '../Wallet' import Transaction, { TransactionInput } from '../Transaction' +import { ChainSettings } from '../Cardano' export interface KeyManager { - signTransaction: (transaction: ReturnType, inputs: TransactionInput[], chainSettings?: CardanoBlockchainSettings, transactionsAsProofForSpending?: { [transactionId: string]: string }) => Promise + signTransaction: (transaction: ReturnType, inputs: TransactionInput[], chainSettings?: ChainSettings, transactionsAsProofForSpending?: { [transactionId: string]: string }) => Promise signMessage: (addressType: AddressType, signingIndex: number, message: string) => Promise<{ publicKey: string, signature: string }> - publicAccount: () => Promise + publicParentKey: () => Promise } diff --git a/src/KeyManager/index.ts b/src/KeyManager/index.ts index d709041b945..6eecc070ffd 100644 --- a/src/KeyManager/index.ts +++ b/src/KeyManager/index.ts @@ -1 +1,2 @@ -export { InMemoryKeyManager } from './InMemoryKey' +export { KeyManager } from './KeyManager' +export * from './errors' diff --git a/src/Transaction/Transaction.spec.ts b/src/Transaction/Transaction.spec.ts index 32dac3d4b49..1dbf80f9e88 100644 --- a/src/Transaction/Transaction.spec.ts +++ b/src/Transaction/Transaction.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'chai' import Transaction, { TransactionInput, TransactionOutput } from './' import { InsufficientTransactionInput } from './errors' import { EmptyArray } from '../lib/validator/errors' -import { estimateTransactionFee } from '../Utils/estimate_fee' +import { RustCardano } from '../lib' describe('Transaction', () => { it('throws if inputs are invalid', () => { @@ -13,8 +13,8 @@ describe('Transaction', () => { { address: 'Ae2tdPwUPEZCEhYAUVU7evPfQCJjyuwM6n81x6hSjU9TBMSy2YwZEVydssL', value: '10000' } ] - expect(() => Transaction(emptyInputArray, outputs)).to.throw(EmptyArray) - expect(() => Transaction(invalidInputType, outputs)).to.throw(/Invalid value/) + expect(() => Transaction(RustCardano, emptyInputArray, outputs)).to.throw(EmptyArray) + expect(() => Transaction(RustCardano, invalidInputType, outputs)).to.throw(/Invalid value/) }) it('throws if outputs are invalid', () => { @@ -26,8 +26,8 @@ describe('Transaction', () => { const emptyOutputArray = [] as TransactionOutput[] const invalidOutputType = [{ foo: 'bar' }] as any[] - expect(() => Transaction(inputs, emptyOutputArray)).to.throw(EmptyArray) - expect(() => Transaction(inputs, invalidOutputType)).to.throw(/Invalid value/) + expect(() => Transaction(RustCardano, inputs, emptyOutputArray)).to.throw(EmptyArray) + expect(() => Transaction(RustCardano, inputs, invalidOutputType)).to.throw(/Invalid value/) }) it('throws if a transaction has more combined output value than input value', () => { @@ -40,7 +40,7 @@ describe('Transaction', () => { { address: 'Ae2tdPwUPEZCEhYAUVU7evPfQCJjyuwM6n81x6hSjU9TBMSy2YwZEVydssL', value: '2000000' } ] - expect(() => Transaction(inputs, outputs)).to.throw(InsufficientTransactionInput) + expect(() => Transaction(RustCardano, inputs, outputs)).to.throw(InsufficientTransactionInput) }) it('accepts more combined input value than output, to cover fees', () => { @@ -53,12 +53,12 @@ describe('Transaction', () => { { address: 'Ae2tdPwUPEZCEhYAUVU7evPfQCJjyuwM6n81x6hSjU9TBMSy2YwZEVydssL', value: '10000' } ] - expect(() => Transaction(inputs, outputs)).to.not.throw() + expect(() => Transaction(RustCardano, inputs, outputs)).to.not.throw() }) it('allows access to a transaction as hex', () => { const inputs = [ - { pointer: { id: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', index: 1 }, value: { address: 'addressWithFunds1', value: '1000000' } }, + { pointer: { id: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', index: 1 }, value: { address: 'addressWithFunds1', value: '2000000' } }, { pointer: { id: 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210', index: 0 }, value: { address: 'addressWithFunds2', value: '5000000' } } ] @@ -66,10 +66,10 @@ describe('Transaction', () => { { address: 'Ae2tdPwUPEZCEhYAUVU7evPfQCJjyuwM6n81x6hSjU9TBMSy2YwZEVydssL', value: '6000000' } ] - const fee = estimateTransactionFee(inputs, outputs) + const fee = Transaction(RustCardano, inputs, outputs).estimateFee() outputs[0].value = (6000000 - Number(fee)).toString() - const transaction = Transaction(inputs, outputs) + const transaction = Transaction(RustCardano, inputs, outputs) const expectedHex = '839f8200d81858248258200123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef018200d8185824825820fedcba9876543210fedcba9876543210fedcba9876543210fedcba987654321000ff9f8282d818582183581c9aa3c11f83717c117b5da7f49b9387dc90d1694a75849bd5cbde8e20a0001ae196744f1a0058e69dffa0' expect(transaction.toHex()).to.equal(expectedHex) }) @@ -85,8 +85,8 @@ describe('Transaction', () => { { address: 'Ae2tdPwUPEZCEhYAUVU7evPfQCJjyuwM6n81x6hSjU9TBMSy2YwZEVydssL', value: '5000000' } ] - const estimatedFee = estimateTransactionFee(inputs, outputs) - const realisedFee = Transaction(inputs, outputs).fee() + const estimatedFee = Transaction(RustCardano, inputs, outputs).estimateFee() + const realisedFee = Transaction(RustCardano, inputs, outputs).fee() expect(realisedFee).to.equal('2010000') expect(realisedFee).to.not.eql(estimatedFee) diff --git a/src/Transaction/Transaction.ts b/src/Transaction/Transaction.ts index f55b02e3eef..4a1681efec9 100644 --- a/src/Transaction/Transaction.ts +++ b/src/Transaction/Transaction.ts @@ -1,55 +1,10 @@ -import { getBindingsForEnvironment } from '../lib/bindings' -import { InsufficientTransactionInput } from './errors' import { TransactionInput, TransactionInputCodec } from './TransactionInput' import { TransactionOutput, TransactionOutputCodec } from './TransactionOutput' import { validateCodec } from '../lib/validator' -import { convertCoinToLovelace } from '../Utils' -const { TransactionBuilder, TxoPointer, TxOut, Coin, LinearFeeAlgorithm, TransactionFinalized } = getBindingsForEnvironment() +import { FeeAlgorithm, Cardano } from '../Cardano' -export function Transaction (inputs: TransactionInput[], outputs: TransactionOutput[], feeAlgorithm = LinearFeeAlgorithm.default()) { +export function Transaction (cardano: Cardano, inputs: TransactionInput[], outputs: TransactionOutput[], feeAlgorithm = FeeAlgorithm.default) { validateCodec(TransactionInputCodec, inputs) validateCodec(TransactionOutputCodec, outputs) - - const transactionBuilder = buildTransaction(inputs, outputs) - - const balance = transactionBuilder.get_balance(feeAlgorithm) - if (balance.is_negative()) throw new InsufficientTransactionInput() - - /* - The get_balance_without_fees from the WASM bindings returns: - - Σ(transactionInputValues) - Σ(transactionOutputValues) - - This represents the fee paid on a transaction, as the positive balance - between inputs and the associated outputs is equal to the fee paid - */ - const feeAsCoinType = transactionBuilder.get_balance_without_fees().value() - const fee = convertCoinToLovelace(feeAsCoinType) - - const cardanoTransaction = transactionBuilder.make_transaction() - - return { - toHex: () => cardanoTransaction.to_hex(), - toJson: () => cardanoTransaction.to_json(), - id: () => cardanoTransaction.id(), - finalize: () => new TransactionFinalized(cardanoTransaction), - fee: () => fee - } -} - -export function buildTransaction (inputs: TransactionInput[], outputs: TransactionOutput[]) { - const transactionBuilder = new TransactionBuilder() - - inputs.forEach(input => { - const pointer = TxoPointer.from_json(input.pointer) - const value = Coin.from(0, Number(input.value.value)) - transactionBuilder.add_input(pointer, value) - }) - - outputs.forEach(output => { - const txOut = TxOut.from_json(output) - transactionBuilder.add_output(txOut) - }) - - return transactionBuilder + return cardano.buildTransaction(inputs, outputs, feeAlgorithm) } diff --git a/src/Transaction/TransactionInput.ts b/src/Transaction/TransactionInput.ts index e76344cad22..409ea8560e8 100644 --- a/src/Transaction/TransactionInput.ts +++ b/src/Transaction/TransactionInput.ts @@ -7,7 +7,8 @@ const pointer = t.type({ const addressing = t.type({ change: t.number, - index: t.number + index: t.number, + accountIndex: t.number }) const value = t.type({ diff --git a/src/Transaction/index.ts b/src/Transaction/index.ts index 0e0d961ac6e..7560c0a3f1d 100644 --- a/src/Transaction/index.ts +++ b/src/Transaction/index.ts @@ -1,6 +1,6 @@ -import { Transaction, buildTransaction } from './Transaction' +import { Transaction } from './Transaction' import { TransactionInput } from './TransactionInput' import { TransactionOutput } from './TransactionOutput' export default Transaction -export { TransactionInput, TransactionOutput, buildTransaction } +export { TransactionInput, TransactionOutput } diff --git a/src/Utils/address_discovery.spec.ts b/src/Utils/address_discovery.spec.ts index 22532419cf7..1a014d331c2 100644 --- a/src/Utils/address_discovery.spec.ts +++ b/src/Utils/address_discovery.spec.ts @@ -1,39 +1,45 @@ import { expect } from 'chai' -import { InMemoryKeyManager } from '../KeyManager' import { generateMnemonic } from './mnemonic' import { AddressType } from '../Wallet' import { addressDiscoveryWithinBounds } from './address_discovery' +import { InMemoryKeyManager, RustCardano } from '../lib' +import { ChainSettings } from '../Cardano' -describe('addressDiscovery', async () => { - const mnemonic = generateMnemonic() - const account = await InMemoryKeyManager({ mnemonic, password: 'foobar' }).publicAccount() +describe('addressDiscovery', () => { + let mnemonic: string + let account: string + + beforeEach(async () => { + mnemonic = generateMnemonic() + account = await InMemoryKeyManager(RustCardano, { mnemonic, password: 'foobar' }).publicParentKey() + }) it('correctly returns address indexes and address type', () => { - const internalAddresses = addressDiscoveryWithinBounds({ account, type: AddressType.internal, lowerBound: 0, upperBound: 19 }) + const internalAddresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.internal, lowerBound: 0, upperBound: 19 }, ChainSettings.mainnet) expect(internalAddresses[0].index).to.eql(0) expect(internalAddresses[0].type).to.eql(AddressType.internal) - const externalAddresses = addressDiscoveryWithinBounds({ account, type: AddressType.external, lowerBound: 0, upperBound: 19 }) + const externalAddresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.external, lowerBound: 0, upperBound: 19 }, ChainSettings.mainnet) expect(externalAddresses[0].index).to.eql(0) expect(externalAddresses[0].type).to.eql(AddressType.external) }) describe('internal', () => { it('discovers addresses between bounds', () => { - const internalAddresses = addressDiscoveryWithinBounds({ account, type: AddressType.internal, lowerBound: 0, upperBound: 19 }) + const internalAddresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.internal, lowerBound: 0, upperBound: 19 }, ChainSettings.mainnet) expect(internalAddresses.length).to.eql(20) }) it('does not collide between different bounds', () => { - const first20Addresses = addressDiscoveryWithinBounds({ account, type: AddressType.internal, lowerBound: 0, upperBound: 19 }) - const next20Addresses = addressDiscoveryWithinBounds({ account, type: AddressType.internal, lowerBound: 20, upperBound: 39 }) + const first20Addresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.internal, lowerBound: 0, upperBound: 19 }, ChainSettings.mainnet) + const next20Addresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.internal, lowerBound: 20, upperBound: 39 }, ChainSettings.mainnet) const addressSet = new Set(first20Addresses.concat(next20Addresses)) expect([...addressSet].length).to.eql(40) }) it('does not collide with external addresses', () => { - const internalAddresses = addressDiscoveryWithinBounds({ account, type: AddressType.internal, lowerBound: 0, upperBound: 19 }) - const externalAddresses = addressDiscoveryWithinBounds({ account, type: AddressType.external, lowerBound: 0, upperBound: 19 }) + const internalAddresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.internal, lowerBound: 0, upperBound: 19 }, ChainSettings.mainnet) + const externalAddresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.external, lowerBound: 0, upperBound: 19 }, ChainSettings.mainnet) const addressSet = new Set(internalAddresses.concat(externalAddresses)) expect([...addressSet].length).to.eql(40) }) @@ -41,13 +47,13 @@ describe('addressDiscovery', async () => { describe('external', () => { it('discovers addresses between bounds', () => { - const externalAddresses = addressDiscoveryWithinBounds({ account, type: AddressType.external, lowerBound: 0, upperBound: 19 }) + const externalAddresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.external, lowerBound: 0, upperBound: 19 }, ChainSettings.mainnet) expect(externalAddresses.length).to.eql(20) }) it('does not collide between different bounds', () => { - const first20Addresses = addressDiscoveryWithinBounds({ account, type: AddressType.external, lowerBound: 0, upperBound: 19 }) - const next20Addresses = addressDiscoveryWithinBounds({ account, type: AddressType.external, lowerBound: 20, upperBound: 39 }) + const first20Addresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.external, lowerBound: 0, upperBound: 19 }, ChainSettings.mainnet) + const next20Addresses = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.external, lowerBound: 20, upperBound: 39 }, ChainSettings.mainnet) const addressSet = new Set(first20Addresses.concat(next20Addresses)) expect([...addressSet].length).to.eql(40) }) diff --git a/src/Utils/address_discovery.ts b/src/Utils/address_discovery.ts index be480b9c061..13aea33e8dc 100644 --- a/src/Utils/address_discovery.ts +++ b/src/Utils/address_discovery.ts @@ -1,29 +1,27 @@ -import { Bip44AccountPublic } from 'cardano-wallet' -import { getBindingsForEnvironment } from '../lib/bindings' import { AddressType } from '../Wallet' -const { AddressKeyIndex, BlockchainSettings } = getBindingsForEnvironment() +import { Cardano, ChainSettings } from '../Cardano' -/** BIP44 specifies that discovery should occur for an address type in batches of 20, until no balances exist */ -export function addressDiscoveryWithinBounds ({ type, account, lowerBound, upperBound }: { - type: AddressType, - account: Bip44AccountPublic, - lowerBound: number, +export interface AddressDiscoveryArgs { + type: AddressType + account: string + lowerBound: number upperBound: number -}, chainSettings = BlockchainSettings.mainnet()) { + accountIndex?: number +} + +/** BIP44 specifies that discovery should occur for an address type in batches of 20, until no balances exist */ +export function addressDiscoveryWithinBounds ( + cardano: Cardano, + { type, account, lowerBound, upperBound, accountIndex }: AddressDiscoveryArgs, + chainSettings: ChainSettings +) { + if (!accountIndex) { + accountIndex = 0 + } + const addressIndices = Array(upperBound - lowerBound + 1) .fill(0) .map((_, idx) => lowerBound + idx) - return addressIndices.map(index => { - const pubKey = account - .bip44_chain(type === AddressType.internal) - .address_key(AddressKeyIndex.new(index)) - - const address = pubKey.bootstrap_era_address(chainSettings) - return { - address: address.to_base58(), - index, - type - } - }) + return addressIndices.map(index => cardano.address({ publicParentKey: account, index, type, accountIndex }, chainSettings)) } diff --git a/src/Utils/coin_to_lovelace.ts b/src/Utils/coin_to_lovelace.ts deleted file mode 100644 index 39b65e53a6a..00000000000 --- a/src/Utils/coin_to_lovelace.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Coin } from 'cardano-wallet' - -export function convertCoinToLovelace (coin: Coin): string { - const ada = coin.ada() - const lovelace = coin.lovelace() - return String((ada * 1000000) + lovelace) -} diff --git a/src/Utils/estimate_fee.ts b/src/Utils/estimate_fee.ts deleted file mode 100644 index b58917c63df..00000000000 --- a/src/Utils/estimate_fee.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { getBindingsForEnvironment } from '../lib/bindings' -import { TransactionInput, TransactionOutput, buildTransaction } from '../Transaction' -import { convertCoinToLovelace } from './coin_to_lovelace' -const { LinearFeeAlgorithm } = getBindingsForEnvironment() - -export function estimateTransactionFee (inputs: TransactionInput[], outputs: TransactionOutput[], feeAlgorithm = LinearFeeAlgorithm.default()): string { - const feeEstimate = buildTransaction(inputs, outputs).estimate_fee(feeAlgorithm) - return convertCoinToLovelace(feeEstimate) -} diff --git a/src/Utils/index.ts b/src/Utils/index.ts index bef9a2357d2..b1802ce1d4f 100644 --- a/src/Utils/index.ts +++ b/src/Utils/index.ts @@ -1,5 +1,2 @@ export { generateMnemonic } from './mnemonic' -export { verifyMessage } from './verify_message' -export { convertCoinToLovelace } from './coin_to_lovelace' -export { addressDiscoveryWithinBounds } from './address_discovery' -export { estimateTransactionFee } from './estimate_fee' +export { addressDiscoveryWithinBounds, AddressDiscoveryArgs } from './address_discovery' diff --git a/src/Utils/verify_message.spec.ts b/src/Utils/verify_message.spec.ts deleted file mode 100644 index 9ed0688c3ea..00000000000 --- a/src/Utils/verify_message.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { expect } from 'chai' -import { InMemoryKeyManager } from '../KeyManager' -import { AddressType } from '../Wallet' -import { verifyMessage } from './verify_message' - -describe('utils', () => { - describe('verifyMessage', () => { - it('returns true when verifying a correct signature for a message', async () => { - const message = 'foobar' - const mnemonic = 'height bubble drama century ask online stage camp point loyal hip awesome' - const keyManager = InMemoryKeyManager({ mnemonic, password: 'securepassword' }) - const { signature, publicKey } = await keyManager.signMessage(AddressType.external, 0, message) - - const verification = verifyMessage({ - publicKey, - message, - signature - }) - - expect(verification).to.eql(true) - }) - - it('returns false when verifying an incorrect message for a valid signature', async () => { - const message = 'foobar' - const mnemonic = 'height bubble drama century ask online stage camp point loyal hip awesome' - const keyManager = InMemoryKeyManager({ mnemonic, password: 'securepassword' }) - const { signature, publicKey } = await keyManager.signMessage(AddressType.external, 0, message) - - const verification = verifyMessage({ - publicKey, - message: 'a differnt message', - signature - }) - - expect(verification).to.eql(false) - }) - }) -}) diff --git a/src/Utils/verify_message.ts b/src/Utils/verify_message.ts deleted file mode 100644 index 229e1094260..00000000000 --- a/src/Utils/verify_message.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getBindingsForEnvironment } from '../lib/bindings' -const { Signature, PublicKey } = getBindingsForEnvironment() - -export function verifyMessage ({ publicKey, message, signature: signatureAsHex }: { publicKey: string, message: string, signature: string }) { - const signatureInterface = Signature.from_hex(signatureAsHex) - const publicKeyInterface = PublicKey.from_hex(publicKey) - return publicKeyInterface.verify(Buffer.from(message), signatureInterface) -} diff --git a/src/Wallet/Address.ts b/src/Wallet/Address.ts index 1fbd2aa7f09..f7d0886b4b2 100644 --- a/src/Wallet/Address.ts +++ b/src/Wallet/Address.ts @@ -2,6 +2,7 @@ export interface Address { address: string index: number type: AddressType + accountIndex: number } /** internal = change address & external = receipt address */ diff --git a/src/Wallet/Wallet.spec.ts b/src/Wallet/Wallet.spec.ts index 51901d66711..1f0235aa23b 100644 --- a/src/Wallet/Wallet.spec.ts +++ b/src/Wallet/Wallet.spec.ts @@ -1,32 +1,32 @@ import { expect } from 'chai' -import { Utils, InMemoryKeyManager } from '..' import { AddressType, Utxo } from '.' import { mockProvider, seedTransactionSet, seedUtxoSet, generateTestTransaction, generateTestUtxos } from '../test/utils' import { Wallet } from './Wallet' -import { Bip44AccountPublic } from 'cardano-wallet' -import { addressDiscoveryWithinBounds } from '../Utils' +import { addressDiscoveryWithinBounds, generateMnemonic } from '../Utils' +import { InMemoryKeyManager, RustCardano } from '../lib' +import { ChainSettings } from '../Cardano' describe('Wallet', () => { - let account: Bip44AccountPublic + let account: string let wallet: ReturnType> describe('getNextChangeAddress', () => { beforeEach(async () => { - const mnemonic = Utils.generateMnemonic() - account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() seedTransactionSet([]) - wallet = Wallet(mockProvider)(account) + wallet = Wallet(RustCardano, mockProvider)(account) }) - it('returns the next change address for a new BIP44 public account', async () => { - const firstInternalAddress = addressDiscoveryWithinBounds({ + it('returns the next change address for a new BIP44 account', async () => { + const firstInternalAddress = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.internal, lowerBound: 0, upperBound: 0 - })[0].address + }, ChainSettings.mainnet)[0].address const nextReceivingAddress = await wallet.getNextChangeAddress() expect(nextReceivingAddress.address).to.eql(firstInternalAddress) @@ -37,21 +37,21 @@ describe('Wallet', () => { describe('getNextReceivingAddress', async () => { beforeEach(async () => { - const mnemonic = Utils.generateMnemonic() - account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() seedTransactionSet([]) - wallet = Wallet(mockProvider)(account) + wallet = Wallet(RustCardano, mockProvider)(account) }) - it('returns the next receiving address for a new BIP44 public account', async () => { - const firstExternalAddress = addressDiscoveryWithinBounds({ + it('returns the next receiving address for a new BIP44 account', async () => { + const firstExternalAddress = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.external, lowerBound: 0, upperBound: 0 - })[0].address + }, ChainSettings.mainnet)[0].address const nextReceivingAddress = await wallet.getNextReceivingAddress() expect(nextReceivingAddress.address).to.eql(firstExternalAddress) @@ -62,8 +62,8 @@ describe('Wallet', () => { describe('balance', async () => { beforeEach(async () => { - const mnemonic = Utils.generateMnemonic() - account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() const targetInternalAddressIndex = 5 const targetExternalAddressIndex = 5 @@ -71,16 +71,16 @@ describe('Wallet', () => { const externalOutputs = generateTestUtxos({ lowerBound: 0, upperBound: targetExternalAddressIndex, account, type: AddressType.external, value: '1000000' }) const internalTx = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, - testInputs: [...Array(targetInternalAddressIndex)].map(() => ({ value: '1000000', type: AddressType.internal })), + testInputs: [...Array(targetInternalAddressIndex)].map(() => ({ value: '10000000', type: AddressType.internal })), testOutputs: internalOutputs }) const externalTx = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, - testInputs: [...Array(targetExternalAddressIndex)].map(() => ({ value: '1000000', type: AddressType.external })), + testInputs: [...Array(targetExternalAddressIndex)].map(() => ({ value: '10000000', type: AddressType.external })), testOutputs: externalOutputs }) @@ -94,10 +94,10 @@ describe('Wallet', () => { { address: externalOutputs[0].address, id: internalTx.inputs[0].pointer.id, index: internalTx.inputs[0].pointer.index, value: '2000' } ]) - wallet = Wallet(mockProvider)(account) + wallet = Wallet(RustCardano, mockProvider)(account) }) - it('determines the balance for a BIP44 public account with UTXOs', async () => { + it('determines the balance for a BIP44 account with UTXOs', async () => { const balance = await wallet.balance() expect(balance).to.eql(3000) }) @@ -105,8 +105,8 @@ describe('Wallet', () => { describe('transaction', () => { beforeEach(async () => { - const mnemonic = Utils.generateMnemonic() - account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() const targetInternalAddressIndex = 5 const targetExternalAddressIndex = 5 @@ -114,16 +114,16 @@ describe('Wallet', () => { const externalOutputs = generateTestUtxos({ lowerBound: 0, upperBound: targetExternalAddressIndex, account, type: AddressType.external, value: '1000000' }) const internalTx = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, - testInputs: [...Array(targetInternalAddressIndex)].map(() => ({ value: '1000000', type: AddressType.internal })), + testInputs: [...Array(targetInternalAddressIndex)].map(() => ({ value: '10000000', type: AddressType.internal })), testOutputs: internalOutputs }) const externalTx = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, - testInputs: [...Array(targetExternalAddressIndex)].map(() => ({ value: '1000000', type: AddressType.external })), + testInputs: [...Array(targetExternalAddressIndex)].map(() => ({ value: '10000000', type: AddressType.external })), testOutputs: externalOutputs }) @@ -132,10 +132,10 @@ describe('Wallet', () => { { inputs: externalTx.inputs, outputs: externalOutputs } ]) - wallet = Wallet(mockProvider)(account) + wallet = Wallet(RustCardano, mockProvider)(account) }) - it('returns a list of transactions for a BIP44 public account with associated transactions', async () => { + it('returns a list of transactions for a BIP44 account with associated transactions', async () => { const transactions = await wallet.transactions() expect(transactions.length).to.eql(2) }) @@ -145,8 +145,8 @@ describe('Wallet', () => { let internalOutputs: Utxo[] beforeEach(async () => { - const mnemonic = Utils.generateMnemonic() - account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() const targetInternalAddressIndex = 5 const targetExternalAddressIndex = 5 @@ -154,16 +154,16 @@ describe('Wallet', () => { const externalOutputs = generateTestUtxos({ lowerBound: 0, upperBound: targetExternalAddressIndex, account, type: AddressType.external, value: '1000000' }) const internalTx = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, - testInputs: [...Array(targetInternalAddressIndex)].map(() => ({ value: '1000000', type: AddressType.internal })), + testInputs: [...Array(targetInternalAddressIndex)].map(() => ({ value: '10000000', type: AddressType.internal })), testOutputs: internalOutputs }) const externalTx = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, - testInputs: [...Array(targetExternalAddressIndex)].map(() => ({ value: '1000000', type: AddressType.external })), + testInputs: [...Array(targetExternalAddressIndex)].map(() => ({ value: '10000000', type: AddressType.external })), testOutputs: externalOutputs }) @@ -177,10 +177,10 @@ describe('Wallet', () => { { address: externalOutputs[0].address, id: internalTx.inputs[1].pointer.id, index: internalTx.inputs[0].pointer.index, value: '500000' } ]) - wallet = Wallet(mockProvider)(account) + wallet = Wallet(RustCardano, mockProvider)(account) }) - it('selects inputs from the UTXO set available for the BIP44 public account', async () => { + it('selects inputs from the UTXO set available for the BIP44 account', async () => { const testOutput = [{ address: internalOutputs[4].address, value: '1000' }] const { inputs, changeOutput } = await wallet.selectInputsForTransaction(testOutput) diff --git a/src/Wallet/Wallet.ts b/src/Wallet/Wallet.ts index 4ff789da02d..956ce598701 100644 --- a/src/Wallet/Wallet.ts +++ b/src/Wallet/Wallet.ts @@ -1,41 +1,40 @@ -import { Bip44AccountPublic } from 'cardano-wallet' import { TransactionOutput } from '../Transaction' import { Provider } from '../Provider' import { AddressType, UtxoWithAddressing } from '.' -import { deriveAddressSet, getNextAddressByType, selectInputsAndChangeOutput } from './lib' -import { getBindingsForEnvironment } from '../lib/bindings' -const { LinearFeeAlgorithm } = getBindingsForEnvironment() +import { deriveAddressSet, getNextAddressByType } from './lib' +import { FeeAlgorithm, Cardano } from '../Cardano' -export function Wallet (provider: Provider) { - return (account: Bip44AccountPublic) => { +export function Wallet (cardano: Cardano, provider: Provider) { + return (account: string) => { return { - getNextReceivingAddress: () => getNextAddressByType(provider, account, AddressType.external), - getNextChangeAddress: () => getNextAddressByType(provider, account, AddressType.internal), + getNextReceivingAddress: () => getNextAddressByType(cardano, provider, account, AddressType.external), + getNextChangeAddress: () => getNextAddressByType(cardano, provider, account, AddressType.internal), balance: async () => { - const addresses = await deriveAddressSet(provider, account) + const addresses = await deriveAddressSet(cardano, provider, account) const utxos = await provider.queryUtxosByAddress(addresses.map(({ address }) => address)) return utxos.reduce((accumulatedBalance, utxo) => accumulatedBalance + Number(utxo.value), 0) }, transactions: async () => { - const addresses = await deriveAddressSet(provider, account) + const addresses = await deriveAddressSet(cardano, provider, account) return provider.queryTransactionsByAddress(addresses.map(({ address }) => address)) }, - selectInputsForTransaction: async (paymentOutputs: TransactionOutput[], linearFeeAlgorith = LinearFeeAlgorithm.default()) => { - const addresses = await deriveAddressSet(provider, account) + selectInputsForTransaction: async (paymentOutputs: TransactionOutput[], feeAlgorithm = FeeAlgorithm.default) => { + const addresses = await deriveAddressSet(cardano, provider, account) const utxos = await provider.queryUtxosByAddress(addresses.map(({ address }) => address)) const utxosMappedWithAddresses: UtxoWithAddressing[] = utxos.map(utxo => { - const { index, type } = addresses.find(({ address }) => address === utxo.address) + const { index, type, accountIndex } = addresses.find(({ address }) => address === utxo.address) return { addressing: { index, - change: type === AddressType.internal ? 1 : 0 + change: type === AddressType.internal ? 1 : 0, + accountIndex }, ...utxo } }) - const nextChangeAddress = await getNextAddressByType(provider, account, AddressType.internal) - return selectInputsAndChangeOutput(paymentOutputs, utxosMappedWithAddresses, nextChangeAddress.address, linearFeeAlgorith) + const nextChangeAddress = await getNextAddressByType(cardano, provider, account, AddressType.internal) + return cardano.inputSelection(paymentOutputs, utxosMappedWithAddresses, nextChangeAddress.address, feeAlgorithm) } } } diff --git a/src/Wallet/lib/address_derivation.spec.ts b/src/Wallet/lib/address_derivation.spec.ts index 6daca8d0cac..35d4da8e217 100644 --- a/src/Wallet/lib/address_derivation.spec.ts +++ b/src/Wallet/lib/address_derivation.spec.ts @@ -1,21 +1,22 @@ import { expect } from 'chai' -import { Utils, InMemoryKeyManager } from '../..' +import { generateMnemonic } from '../../Utils' import { SCAN_GAP } from '../config' import { generateTestTransaction, mockProvider, seedTransactionSet, generateTestUtxos } from '../../test/utils' import { deriveAddressSet } from '.' import { AddressType } from '..' +import { RustCardano, InMemoryKeyManager } from '../../lib' describe('deriveAddressSet', () => { it('combines external and internal addresses up to the end of each range', async () => { - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() const targetInternalAddressIndex = SCAN_GAP - 5 const targetExternalAddressIndex = (SCAN_GAP * 2) + 3 const targetTotalAddresses = targetExternalAddressIndex + targetInternalAddressIndex + (SCAN_GAP * 2) const internalOutputs = generateTestUtxos({ lowerBound: 0, upperBound: targetInternalAddressIndex, account, type: AddressType.internal, value: '1000000' }) const internalTx = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ type: AddressType.external, value: '6000000000' }], testOutputs: internalOutputs @@ -23,7 +24,7 @@ describe('deriveAddressSet', () => { const externalOutputs = generateTestUtxos({ lowerBound: 0, upperBound: targetExternalAddressIndex, account, type: AddressType.external, value: '1000000' }) const externalTx = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ type: AddressType.external, value: '6000000000' }], testOutputs: externalOutputs @@ -34,7 +35,7 @@ describe('deriveAddressSet', () => { { inputs: externalTx.inputs, outputs: internalOutputs } ]) - const derivedAddressSet = await deriveAddressSet(mockProvider, account) + const derivedAddressSet = await deriveAddressSet(RustCardano, mockProvider, account) expect(derivedAddressSet.length).to.eql(targetTotalAddresses) }) }) diff --git a/src/Wallet/lib/address_derivation.ts b/src/Wallet/lib/address_derivation.ts index 9e89a8171c9..d3fcb5aa80d 100644 --- a/src/Wallet/lib/address_derivation.ts +++ b/src/Wallet/lib/address_derivation.ts @@ -1,27 +1,27 @@ -import { Bip44AccountPublic } from 'cardano-wallet' import { AddressType } from '..' import { Provider } from '../../Provider' import { getNextAddressByType } from '.' import { SCAN_GAP } from '../config' import { addressDiscoveryWithinBounds } from '../../Utils' +import { Cardano, ChainSettings } from '../../Cardano' -export async function deriveAddressSet (provider: Provider, account: Bip44AccountPublic) { - const nextReceivingAddress = await getNextAddressByType(provider, account, AddressType.external) - const nextChangeAddress = await getNextAddressByType(provider, account, AddressType.internal) +export async function deriveAddressSet (cardano: Cardano, provider: Provider, account: string) { + const nextReceivingAddress = await getNextAddressByType(cardano, provider, account, AddressType.external) + const nextChangeAddress = await getNextAddressByType(cardano, provider, account, AddressType.internal) - const receiptAddresses = addressDiscoveryWithinBounds({ + const receiptAddresses = addressDiscoveryWithinBounds(cardano, { account, lowerBound: 0, upperBound: nextReceivingAddress.index + SCAN_GAP - 1, type: AddressType.external - }) + }, ChainSettings.mainnet) - const changeAddresses = addressDiscoveryWithinBounds({ + const changeAddresses = addressDiscoveryWithinBounds(cardano, { account, lowerBound: 0, upperBound: nextChangeAddress.index + SCAN_GAP - 1, type: AddressType.internal - }) + }, ChainSettings.mainnet) return receiptAddresses.concat(changeAddresses) } diff --git a/src/Wallet/lib/get_next_address.spec.ts b/src/Wallet/lib/get_next_address.spec.ts index 278534820ce..44cca84a6b6 100644 --- a/src/Wallet/lib/get_next_address.spec.ts +++ b/src/Wallet/lib/get_next_address.spec.ts @@ -1,39 +1,39 @@ import { expect } from 'chai' import { getNextAddressByType } from './get_next_address' -import { Utils } from '../..' -import { InMemoryKeyManager } from '../../KeyManager' +import { generateMnemonic, addressDiscoveryWithinBounds } from '../../Utils' import { generateTestTransaction, generateTestUtxos, mockProvider, seedTransactionSet } from '../../test/utils' import { SCAN_GAP } from '../config' import { AddressType } from '..' -import { addressDiscoveryWithinBounds } from '../../Utils' +import { InMemoryKeyManager, RustCardano } from '../../lib' +import { ChainSettings } from '../../Cardano' describe('getNextAddressByType', () => { it('returns the first address index if no transactions exist for internal addresses', async () => { seedTransactionSet([]) - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() - const firstInternalAddress = addressDiscoveryWithinBounds({ + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() + const firstInternalAddress = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.internal, lowerBound: 0, upperBound: 0 - })[0].address + }, ChainSettings.mainnet)[0].address - const nextChangeAddress = await getNextAddressByType(mockProvider, account, AddressType.internal) + const nextChangeAddress = await getNextAddressByType(RustCardano, mockProvider, account, AddressType.internal) expect(nextChangeAddress.address).to.eql(firstInternalAddress) expect(nextChangeAddress.index).to.eql(0) expect(nextChangeAddress.type).to.eql(AddressType.internal) }) it('returns the first address with no transactions for internal addresses, when the address lives within the first SCAN_GAP', async () => { - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() const targetAddressIndex = SCAN_GAP - 5 const outputs = generateTestUtxos({ lowerBound: 0, upperBound: targetAddressIndex, account, type: AddressType.internal, value: '1000000' }) const { inputs } = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ value: '1000000000', type: AddressType.internal }], testOutputs: outputs @@ -41,19 +41,19 @@ describe('getNextAddressByType', () => { seedTransactionSet([{ inputs, outputs }]) - const nextChangeAddress = await getNextAddressByType(mockProvider, account, AddressType.internal) + const nextChangeAddress = await getNextAddressByType(RustCardano, mockProvider, account, AddressType.internal) expect(nextChangeAddress.index).to.eql(targetAddressIndex) expect(nextChangeAddress.type).to.eql(AddressType.internal) }) it('returns the first address with no transactions for internal addresses, when the address lives within beyond the first SCAN_GAP', async () => { - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() const targetAddressIndex = (SCAN_GAP * 3) - 5 const outputs = generateTestUtxos({ lowerBound: 0, upperBound: targetAddressIndex, account, type: AddressType.internal, value: '1000000' }) const { inputs } = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ value: '1000000000', type: AddressType.internal }], testOutputs: outputs @@ -61,7 +61,7 @@ describe('getNextAddressByType', () => { seedTransactionSet([{ inputs, outputs }]) - const nextChangeAddress = await getNextAddressByType(mockProvider, account, AddressType.internal) + const nextChangeAddress = await getNextAddressByType(RustCardano, mockProvider, account, AddressType.internal) expect(nextChangeAddress.index).to.eql(targetAddressIndex) expect(nextChangeAddress.type).to.eql(AddressType.internal) }) @@ -69,29 +69,29 @@ describe('getNextAddressByType', () => { it('returns the first address index if no transactions exist for external addresses', async () => { seedTransactionSet([]) - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() - const firstInternalAddress = addressDiscoveryWithinBounds({ + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() + const firstInternalAddress = addressDiscoveryWithinBounds(RustCardano, { account, type: AddressType.external, lowerBound: 0, upperBound: 0 - })[0].address + }, ChainSettings.mainnet)[0].address - const nextChangeAddress = await getNextAddressByType(mockProvider, account, AddressType.external) + const nextChangeAddress = await getNextAddressByType(RustCardano, mockProvider, account, AddressType.external) expect(nextChangeAddress.address).to.eql(firstInternalAddress) expect(nextChangeAddress.index).to.eql(0) expect(nextChangeAddress.type).to.eql(AddressType.external) }) it('returns the first address with no transactions for external addresses, when the address lives within the first SCAN_GAP', async () => { - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() const targetAddressIndex = SCAN_GAP - 10 const outputs = generateTestUtxos({ lowerBound: 0, upperBound: targetAddressIndex, account, type: AddressType.external, value: '1000000' }) const { inputs } = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ value: '1000000000', type: AddressType.internal }], testOutputs: outputs @@ -99,19 +99,19 @@ describe('getNextAddressByType', () => { seedTransactionSet([{ inputs, outputs }]) - const nextChangeAddress = await getNextAddressByType(mockProvider, account, AddressType.external) + const nextChangeAddress = await getNextAddressByType(RustCardano, mockProvider, account, AddressType.external) expect(nextChangeAddress.index).to.eql(targetAddressIndex) expect(nextChangeAddress.type).to.eql(AddressType.external) }) it('returns the first address with no transactions for external addresses, when the address lives within beyond the first SCAN_GAP', async () => { - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() const targetAddressIndex = (SCAN_GAP * 5) - 5 const outputs = generateTestUtxos({ lowerBound: 0, upperBound: targetAddressIndex, account, type: AddressType.external, value: '1000000' }) const { inputs } = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ value: '1000000000', type: AddressType.internal }], testOutputs: outputs @@ -119,7 +119,7 @@ describe('getNextAddressByType', () => { seedTransactionSet([{ inputs, outputs }]) - const nextChangeAddress = await getNextAddressByType(mockProvider, account, AddressType.external) + const nextChangeAddress = await getNextAddressByType(RustCardano, mockProvider, account, AddressType.external) expect(nextChangeAddress.index).to.eql(targetAddressIndex) expect(nextChangeAddress.type).to.eql(AddressType.external) }) diff --git a/src/Wallet/lib/get_next_address.ts b/src/Wallet/lib/get_next_address.ts index 51230026f07..340eba31740 100644 --- a/src/Wallet/lib/get_next_address.ts +++ b/src/Wallet/lib/get_next_address.ts @@ -1,11 +1,12 @@ import { Provider } from '../../Provider' -import { Bip44AccountPublic } from 'cardano-wallet' import { AddressType, Address } from '..' import { SCAN_GAP } from '../config' import { addressDiscoveryWithinBounds } from '../../Utils' +import { Cardano, ChainSettings } from '../../Cardano' -export function getNextAddressByType (provider: Provider, account: Bip44AccountPublic, type: AddressType) { +export function getNextAddressByType (cardano: Cardano, provider: Provider, account: string, type: AddressType) { return scanBip44AccountForAddressWithoutTransactions({ + cardano, account, provider, lowerBound: 0, @@ -15,20 +16,21 @@ export function getNextAddressByType (provider: Provider, account: Bip44AccountP } interface ScanRangeParameters { + cardano: Cardano provider: Provider - account: Bip44AccountPublic + account: string lowerBound: number upperBound: number type: AddressType } -async function scanBip44AccountForAddressWithoutTransactions ({ provider, account, lowerBound, upperBound, type }: ScanRangeParameters): Promise
{ - const addresses = addressDiscoveryWithinBounds({ +async function scanBip44AccountForAddressWithoutTransactions ({ cardano, provider, account, lowerBound, upperBound, type }: ScanRangeParameters): Promise
{ + const addresses = addressDiscoveryWithinBounds(cardano, { account, lowerBound, upperBound, type - }) + }, ChainSettings.mainnet) const transactions = await provider.queryTransactionsByAddress(addresses.map(a => a.address)) @@ -59,6 +61,7 @@ async function scanBip44AccountForAddressWithoutTransactions ({ provider, accoun // has transactions associated with it, then this range has been consumed if (lastAddressWithTransactionsIndex === 0) { return scanBip44AccountForAddressWithoutTransactions({ + cardano, provider, account, lowerBound: lowerBound + SCAN_GAP, diff --git a/src/Wallet/lib/index.ts b/src/Wallet/lib/index.ts index 494c6908590..ba6a89ff0f6 100644 --- a/src/Wallet/lib/index.ts +++ b/src/Wallet/lib/index.ts @@ -1,3 +1,2 @@ -export * from './select_inputs' export * from './address_derivation' export * from './get_next_address' diff --git a/src/Wallet/lib/select_inputs.spec.ts b/src/Wallet/lib/select_inputs.spec.ts deleted file mode 100644 index ce6e19ac453..00000000000 --- a/src/Wallet/lib/select_inputs.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { expect } from 'chai' -import { selectInputsAndChangeOutput } from '.' -import { AddressType } from '..' -import { Utils, InMemoryKeyManager } from '../..' -import { hexGenerator } from '../../test/utils' -import { addressDiscoveryWithinBounds } from '../../Utils' - -describe('selectInputsAndChangeOutput', () => { - it('throws if there is insufficient inputs to cover the payment cost', async () => { - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() - const [address1, address2, address3, changeAddress] = addressDiscoveryWithinBounds({ - account, - type: AddressType.internal, - lowerBound: 0, - upperBound: 5 - }) - - const utxosWithAddressing = [ - { address: address1.address, value: '1000', id: hexGenerator(64), index: 0, addressing: { index: 0, change: 0 } }, - { address: address2.address, value: '1000', id: hexGenerator(64), index: 1, addressing: { index: 0, change: 0 } } - ] - - const outputs = [ - { address: address3.address, value: '1000000' } - ] - - expect(() => selectInputsAndChangeOutput(outputs, utxosWithAddressing, changeAddress.address)).to.throw('NotEnoughInput') - }) - - describe('FirstMatchFirst', () => { - it('selects valid UTXOs and produces change', async () => { - const mnemonic = Utils.generateMnemonic() - const account = await InMemoryKeyManager({ password: '', mnemonic }).publicAccount() - const [address1, address2, address3, address4, address5, change] = addressDiscoveryWithinBounds({ - account, - type: AddressType.internal, - lowerBound: 0, - upperBound: 5 - }) - - // Any combination of these inputs will always produce change - const utxosWithAddressing = [ - { address: address1.address, value: '600000', id: hexGenerator(64), index: 0, addressing: { index: 0, change: 0 } }, - { address: address2.address, value: '500000', id: hexGenerator(64), index: 1, addressing: { index: 0, change: 0 } }, - { address: address3.address, value: '330000', id: hexGenerator(64), index: 2, addressing: { index: 0, change: 0 } }, - { address: address4.address, value: '410000', id: hexGenerator(64), index: 3, addressing: { index: 0, change: 0 } } - ] - - const outputs = [ - { address: address5.address, value: '10000' } - ] - - const { inputs, changeOutput } = selectInputsAndChangeOutput(outputs, utxosWithAddressing, change.address) - expect(inputs.length > 0).to.eql(true) - expect(changeOutput.address).to.eql(change.address) - }) - }) -}) diff --git a/src/Wallet/lib/select_inputs.ts b/src/Wallet/lib/select_inputs.ts deleted file mode 100644 index b0e9bd62698..00000000000 --- a/src/Wallet/lib/select_inputs.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { TransactionInput, TransactionOutput } from '../../Transaction' -import { getBindingsForEnvironment } from '../../lib/bindings' -import { UtxoWithAddressing } from '..' -import { TxInput as CardanoTxInput } from 'cardano-wallet' -import { convertCoinToLovelace } from '../../Utils' -const { Coin, TransactionId, TxoPointer, TxInput, TxOut, OutputPolicy, Address, InputSelectionBuilder, LinearFeeAlgorithm } = getBindingsForEnvironment() - -export interface TransactionSelection { - inputs: TransactionInput[] - changeOutput: TransactionOutput -} - -export function selectInputsAndChangeOutput (outputs: TransactionOutput[], utxoSet: UtxoWithAddressing[], changeAddress: string, linearFeeAlgorithm = LinearFeeAlgorithm.default()): TransactionSelection { - const potentialInputs: CardanoTxInput[] = utxoSet.map(utxo => { - return TxInput.new( - TxoPointer.new(TransactionId.from_hex(utxo.id), utxo.index), - TxOut.new(Address.from_base58(utxo.address), Coin.from_str(utxo.value)) - ) - }) - - const txOuts = outputs.map((out) => TxOut.new(Address.from_base58(out.address), Coin.from_str(out.value))) - const changeOutputPolicy = OutputPolicy.change_to_one_address(Address.from_base58(changeAddress)) - - let selectionBuilder = InputSelectionBuilder.first_match_first() - potentialInputs.forEach(input => selectionBuilder.add_input(input)) - txOuts.forEach(output => selectionBuilder.add_output(output)) - - const selectionResult = selectionBuilder.select_inputs(linearFeeAlgorithm, changeOutputPolicy) - - const estimatedChange = convertCoinToLovelace(selectionResult.estimated_change()) - const pointers = potentialInputs.map(i => { - return TxoPointer.from_json(i.to_json().ptr) - }) - - let changeOutput - if (estimatedChange !== '0') { - changeOutput = { - value: estimatedChange, - address: changeAddress - } - } - - const selectedPointers = pointers.filter((pointer) => selectionResult.is_input(pointer)) - - const inputs: TransactionInput[] = selectedPointers.map(ptr => { - const pointer = ptr.to_json() - const relevantUtxo = utxoSet.find(u => u.id === pointer.id) - - const value = { - address: relevantUtxo.address, - value: relevantUtxo.value - } - - const addressing = relevantUtxo.addressing - return { pointer, value, addressing } - }) - - return { inputs, changeOutput } -} diff --git a/src/index.ts b/src/index.ts index f025d29e6e5..fadcbc18b76 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,35 @@ -import Transaction from './Transaction' +import Transaction, { TransactionInput, TransactionOutput } from './Transaction' import Wallet from './Wallet' import { Provider, HttpProvider } from './Provider' import * as Utils from './Utils' -import { InMemoryKeyManager } from './KeyManager' - -export { Transaction, InMemoryKeyManager, Utils } +import { InMemoryKeyManager, LedgerKeyManager, RustCardano } from './lib' +import { FeeAlgorithm, ChainSettings } from './Cardano' export const providers: { [provider: string]: (connection: string) => Provider } = { http: HttpProvider } -export function connect (provider: Provider) { +export default function CardanoSDK (cardano = RustCardano) { return { - wallet: Wallet(provider), - ...provider + Transaction (inputs: TransactionInput[], outputs: TransactionOutput[], feeAlgorithm = FeeAlgorithm.default) { + return Transaction(cardano, inputs, outputs, feeAlgorithm) + }, + InMemoryKeyManager (keyArgs: { password: string, accountIndex?: number, mnemonic: string }) { + return InMemoryKeyManager(cardano, keyArgs) + }, + LedgerKeyManager, + Utils: { + generateMnemonic: Utils.generateMnemonic, + addressDiscoveryWithinBounds: (addressDiscoveryArgs: Utils.AddressDiscoveryArgs, chainSettings = ChainSettings.mainnet) => { + return Utils.addressDiscoveryWithinBounds(cardano, addressDiscoveryArgs, chainSettings) + }, + verifyMessage: cardano.verifyMessage + }, + connect (provider: Provider) { + return { + wallet: Wallet(cardano, provider), + ...provider + } + } } } diff --git a/src/KeyManager/InMemoryKey/index.spec.ts b/src/lib/InMemoryKey/index.spec.ts similarity index 64% rename from src/KeyManager/InMemoryKey/index.spec.ts rename to src/lib/InMemoryKey/index.spec.ts index 10f16971f9c..e2b5b71d22a 100644 --- a/src/KeyManager/InMemoryKey/index.spec.ts +++ b/src/lib/InMemoryKey/index.spec.ts @@ -1,40 +1,40 @@ import { expect } from 'chai' +import { ChainSettings } from '../../Cardano' import { InMemoryKeyManager } from '.' -import { InvalidMnemonic } from '../errors' +import { InvalidMnemonic } from '../../KeyManager' import { AddressType } from '../../Wallet' -import { getBindingsForEnvironment } from '../../lib/bindings' import { generateTestTransaction } from '../../test/utils/test_transaction' import { generateMnemonic } from '../../Utils' -const { BlockchainSettings } = getBindingsForEnvironment() +import { RustCardano } from '../../lib' describe('MemoryKeyManager', () => { it('throws if the mnemonic passed is invalid', () => { const invalidMnemonic = 'xxxx' - expect(() => InMemoryKeyManager({ mnemonic: invalidMnemonic, password: 'xx' })).to.throw(InvalidMnemonic) + expect(() => InMemoryKeyManager(RustCardano, { mnemonic: invalidMnemonic, password: 'xx' })).to.throw(InvalidMnemonic) }) - describe('publicAccount', () => { - it('exposes a Bip44 public account', () => { + describe('publicParentKey', () => { + it('exposes the Bip44 public parent key', () => { const mnemonic = generateMnemonic() - const keyManager = InMemoryKeyManager({ mnemonic, password: 'securepassword' }) - expect(keyManager.publicAccount).to.be.an.instanceOf(Function) + const keyManager = InMemoryKeyManager(RustCardano, { mnemonic, password: 'securepassword' }) + expect(keyManager.publicParentKey).to.be.an.instanceOf(Function) }) }) describe('signTransaction', () => { it('adds witnesses to a transaction and returns the hex of the signed transaction', async () => { const mnemonic = 'height bubble drama century ask online stage camp point loyal hip awesome' - const keyManager = InMemoryKeyManager({ mnemonic, password: 'securepassword' }) - const publicAccount = await keyManager.publicAccount() + const keyManager = InMemoryKeyManager(RustCardano, { mnemonic, password: 'securepassword' }) + const account = await keyManager.publicParentKey() const { transaction, inputs } = generateTestTransaction({ - publicAccount, + account, lowerBoundOfAddresses: 0, - testInputs: [{ type: AddressType.external, value: '1000000' }, { type: AddressType.external, value: '5000000' }], + testInputs: [{ type: AddressType.external, value: '2000000' }, { type: AddressType.external, value: '5000000' }], testOutputs: [{ address: 'Ae2tdPwUPEZEjJcLmvgKnuwUnfKSVuGCzRW1PqsLcWqmoGJUocBGbvWjjTx', value: '6000000' }] }) - const signedTransaction = await keyManager.signTransaction(transaction, inputs, BlockchainSettings.mainnet()) + const signedTransaction = await keyManager.signTransaction(transaction, inputs, ChainSettings.mainnet) expect(signedTransaction.length).to.eql(838) }) }) @@ -43,7 +43,7 @@ describe('MemoryKeyManager', () => { it('returns a signed message for a private key index', async () => { const message = 'foobar' const mnemonic = 'height bubble drama century ask online stage camp point loyal hip awesome' - const keyManager = InMemoryKeyManager({ mnemonic, password: 'securepassword' }) + const keyManager = InMemoryKeyManager(RustCardano, { mnemonic, password: 'securepassword' }) const { signature } = await keyManager.signMessage(AddressType.external, 0, message) const expectedSignature = '175121c7d4c18007e0f693181584c74a3b0d667cfbe2b81f5e2afba74dc1070b818f500d26a74e9b23a9a8b0246356a156b33bb17de979f3b429c4b1cfff2303' expect(signature).to.eql(expectedSignature) diff --git a/src/lib/InMemoryKey/index.ts b/src/lib/InMemoryKey/index.ts new file mode 100644 index 00000000000..1325249c10e --- /dev/null +++ b/src/lib/InMemoryKey/index.ts @@ -0,0 +1,34 @@ +import { validateMnemonic } from 'bip39' +import { KeyManager, InvalidMnemonic } from '../../KeyManager' +import { ChainSettings, Cardano } from '../../Cardano' + +export function InMemoryKeyManager ( + cardano: Cardano, + { password, accountIndex, mnemonic }: { + password: string + accountIndex?: number + mnemonic: string + }): KeyManager { + if (!accountIndex) { + accountIndex = 0 + } + + const validMnemonic = validateMnemonic(mnemonic) + if (!validMnemonic) throw new InvalidMnemonic() + + const { privateParentKey, publicParentKey } = cardano.account(mnemonic, password, accountIndex) + + return { + signTransaction: async (transaction, rawInputs, chainSettings = ChainSettings.mainnet) => { + rawInputs.forEach(({ addressing }) => { + transaction.addWitness({ privateParentKey: privateParentKey, addressing, chainSettings }) + }) + + return transaction.finalize() + }, + signMessage: async (addressType, signingIndex, message) => { + return cardano.signMessage({ privateParentKey: privateParentKey, addressType, signingIndex, message }) + }, + publicParentKey: async () => publicParentKey + } +} diff --git a/src/KeyManager/Ledger/index.spec.ts b/src/lib/Ledger/index.spec.ts similarity index 70% rename from src/KeyManager/Ledger/index.spec.ts rename to src/lib/Ledger/index.spec.ts index 84458dfd730..5876391ffcf 100644 --- a/src/KeyManager/Ledger/index.spec.ts +++ b/src/lib/Ledger/index.spec.ts @@ -1,12 +1,12 @@ import { LedgerKeyManager } from '.' -import { KeyManager } from '../KeyManager' +import { KeyManager } from '../../KeyManager' import { generateTestTransaction, generateTestUtxos } from '../../test/utils' import { AddressType } from '../../Wallet' -import { getBindingsForEnvironment } from '../../lib/bindings' -import { UnsupportedOperation, InsufficientData } from '../errors' +import { UnsupportedOperation, InsufficientData } from '../../KeyManager/errors' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' -const { AddressKeyIndex, BlockchainSettings } = getBindingsForEnvironment() +import { ChainSettings } from '../../Cardano' +import { RustCardano } from '../RustCardanoPrimitives' use(chaiAsPromised) const runLedgerSpecs = process.env.LEDGER_SPECS!! ? describe.only : describe.skip @@ -19,14 +19,10 @@ runLedgerSpecs('LedgerKeyManager', async function () { manager = await LedgerKeyManager(0) }) - describe('publicAccount', () => { - it('exposes a Bip44 public account', async () => { - const pk = await manager.publicAccount() - const address = pk - .bip44_chain(false) - .address_key(AddressKeyIndex.new(0)) - .bootstrap_era_address(BlockchainSettings.mainnet()) - .to_base58() + describe('publicParentKey', () => { + it('exposes the Bip44 public parent key', async () => { + const pk = await manager.publicParentKey() + const address = RustCardano.address({ publicParentKey: pk, index: 0, type: AddressType.external, accountIndex: 0 }) expect(typeof address).to.be.eql('string') }) @@ -41,52 +37,52 @@ runLedgerSpecs('LedgerKeyManager', async function () { describe('signTransaction', () => { it('throws if preceding transactions for inputs being spent are not provided', async () => { - const account = await manager.publicAccount() + const account = await manager.publicParentKey() const outputs = generateTestUtxos({ lowerBound: 0, upperBound: 5, account, type: AddressType.internal, value: '1000000' }) const { transaction } = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ value: '1000000000', type: AddressType.internal }], testOutputs: outputs }) const spendingTransaction = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ value: '1000000', type: AddressType.external }], testOutputs: [{ value: '900000', address: 'Ae2tdPwUPEZEjJcLmvgKnuwUnfKSVuGCzRW1PqsLcWqmoGJUocBGbvWjjTx' }], - inputId: transaction.id().to_hex() + inputId: transaction.id() }) - const insufficientDataForSigning = manager.signTransaction(spendingTransaction.transaction, spendingTransaction.inputs, BlockchainSettings.mainnet(), {}) + const insufficientDataForSigning = manager.signTransaction(spendingTransaction.transaction, spendingTransaction.inputs, ChainSettings.mainnet, {}) return expect(insufficientDataForSigning).to.eventually.be.rejectedWith(InsufficientData) }) it('signs a transaction with a ledger device', async () => { - const account = await manager.publicAccount() + const account = await manager.publicParentKey() const outputs = generateTestUtxos({ lowerBound: 0, upperBound: 5, account, type: AddressType.internal, value: '1000000' }) const { transaction } = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ value: '1000000000', type: AddressType.internal }], testOutputs: outputs }) const transactionsAsProofForSpending = { - [transaction.id().to_hex()]: transaction.toHex() + [transaction.id()]: transaction.toHex() } const spendingTransaction = generateTestTransaction({ - publicAccount: account, + account, lowerBoundOfAddresses: 0, testInputs: [{ value: '1000000', type: AddressType.external }], testOutputs: [{ value: '900000', address: 'Ae2tdPwUPEZEjJcLmvgKnuwUnfKSVuGCzRW1PqsLcWqmoGJUocBGbvWjjTx' }], - inputId: transaction.id().to_hex() + inputId: transaction.id() }) - await manager.signTransaction(spendingTransaction.transaction, spendingTransaction.inputs, BlockchainSettings.mainnet(), transactionsAsProofForSpending) + await manager.signTransaction(spendingTransaction.transaction, spendingTransaction.inputs, ChainSettings.mainnet, transactionsAsProofForSpending) }) }) }) diff --git a/src/KeyManager/Ledger/index.ts b/src/lib/Ledger/index.ts similarity index 56% rename from src/KeyManager/Ledger/index.ts rename to src/lib/Ledger/index.ts index 5158047f1d5..486391f508b 100644 --- a/src/KeyManager/Ledger/index.ts +++ b/src/lib/Ledger/index.ts @@ -1,36 +1,35 @@ -import { getLedgerTransportForEnvironment, getBindingsForEnvironment } from '../../lib/bindings' -import { KeyManager } from '../KeyManager' +import { getLedgerTransportForEnvironment } from '../bindings' +import { KeyManager, UnsupportedOperation, InsufficientData } from '../../KeyManager' import { TransactionOutput } from '../../Transaction' -import { UnsupportedOperation, InsufficientData } from '../errors' +import { ChainSettings } from '../../Cardano' +import { AddressType } from '../../Wallet' const { default: Ledger, utils } = require('@cardano-foundation/ledgerjs-hw-app-cardano') -const { AddressKeyIndex, DerivationScheme, Bip44AccountPublic, PublicKey, BlockchainSettings, TransactionFinalized, TransactionSignature, Witness, Transaction } = getBindingsForEnvironment() async function connectToLedger () { const transport = await getLedgerTransportForEnvironment().create() return new Ledger(transport) } -export async function LedgerKeyManager (accountIndex = 0, publicKey?: string): Promise { +export async function LedgerKeyManager (accountIndex = 0, publicParentKey?: string): Promise { const ledger = await connectToLedger() async function deriveBip44Account () { - if (!publicKey) { + if (!publicParentKey) { const { publicKeyHex, chainCodeHex } = await ledger.getExtendedPublicKey([ utils.HARDENED + 44, utils.HARDENED + 1815, utils.HARDENED + accountIndex ]) - publicKey = `${publicKeyHex}${chainCodeHex}` + publicParentKey = `${publicKeyHex}${chainCodeHex}` } - const wasmPublicKey = PublicKey.from_hex(publicKey) - return Bip44AccountPublic.new(wasmPublicKey, DerivationScheme.v2()) + return publicParentKey } return { - signTransaction: async (transaction, rawInputs, _chainSettings = BlockchainSettings.mainnet(), transactionsAsProofForSpending) => { + signTransaction: async (transaction, rawInputs, _chainSettings = ChainSettings.mainnet, transactionsAsProofForSpending) => { const transactionJson = transaction.toJson() for (const txInput of rawInputs) { @@ -53,29 +52,22 @@ export async function LedgerKeyManager (accountIndex = 0, publicKey?: string): P }) const ledgerSignedTransaction = await ledger.signTransaction(inputs, outputs) - const bip44AccountPublic = await deriveBip44Account() - - const tx = Transaction.from_json(transactionJson) - const transactionFinalizer = new TransactionFinalized(tx) + const publicParentKey = await deriveBip44Account() ledgerSignedTransaction.witnesses.forEach((ledgerWitness: any) => { - const pubKey = bip44AccountPublic - .bip44_chain(ledgerWitness.path[3] === 1) - .address_key(AddressKeyIndex.new(ledgerWitness.path[4])) - - const txSignature = TransactionSignature.from_hex( - ledgerWitness.witnessSignatureHex - ) - - const witness = Witness.from_external(pubKey, txSignature) - transactionFinalizer.add_witness(witness) + transaction.addExternalWitness({ + addressType: ledgerWitness.path[3] === 1 ? AddressType.internal : AddressType.external, + witnessIndex: ledgerWitness.path[4], + publicParentKey, + witnessHex: ledgerWitness.witnessSignatureHex + }) }) - return transactionFinalizer.finalize().to_hex() + return transaction.finalize() }, signMessage: async () => { throw new UnsupportedOperation('Ledger signMessage') }, - publicAccount: () => deriveBip44Account() + publicParentKey: () => deriveBip44Account() } } diff --git a/src/lib/RustCardanoPrimitives/index.spec.ts b/src/lib/RustCardanoPrimitives/index.spec.ts new file mode 100644 index 00000000000..cffbbe75367 --- /dev/null +++ b/src/lib/RustCardanoPrimitives/index.spec.ts @@ -0,0 +1,93 @@ +import { expect } from 'chai' +import { InMemoryKeyManager } from '../../lib' +import { AddressType } from '../../Wallet' +import { RustCardano } from '.' +import { hexGenerator } from '../../test/utils' +import { generateMnemonic, addressDiscoveryWithinBounds } from '../../Utils' +import { ChainSettings } from '../../Cardano' + +describe('RustCardano', () => { + describe('verifyMessage', () => { + it('returns true when verifying a correct signature for a message', async () => { + const message = 'foobar' + const mnemonic = 'height bubble drama century ask online stage camp point loyal hip awesome' + const keyManager = InMemoryKeyManager(RustCardano, { mnemonic, password: 'securepassword' }) + const { signature, publicKey } = await keyManager.signMessage(AddressType.external, 0, message) + + const verification = RustCardano.verifyMessage({ + publicKey, + message, + signature + }) + + expect(verification).to.eql(true) + }) + + it('returns false when verifying an incorrect message for a valid signature', async () => { + const message = 'foobar' + const mnemonic = 'height bubble drama century ask online stage camp point loyal hip awesome' + const keyManager = InMemoryKeyManager(RustCardano, { mnemonic, password: 'securepassword' }) + const { signature, publicKey } = await keyManager.signMessage(AddressType.external, 0, message) + + const verification = RustCardano.verifyMessage({ + publicKey, + message: 'a differnt message', + signature + }) + + expect(verification).to.eql(false) + }) + }) + describe('inputSelection', () => { + it('throws if there is insufficient inputs to cover the payment cost', async () => { + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() + const [address1, address2, address3, changeAddress] = addressDiscoveryWithinBounds(RustCardano, { + account, + type: AddressType.internal, + lowerBound: 0, + upperBound: 5 + }, ChainSettings.mainnet) + + const utxosWithAddressing = [ + { address: address1.address, value: '1000', id: hexGenerator(64), index: 0, addressing: { index: 0, change: 0, accountIndex: 0 } }, + { address: address2.address, value: '1000', id: hexGenerator(64), index: 1, addressing: { index: 0, change: 0, accountIndex: 0 } } + ] + + const outputs = [ + { address: address3.address, value: '1000000' } + ] + + expect(() => RustCardano.inputSelection(outputs, utxosWithAddressing, changeAddress.address)).to.throw('NotEnoughInput') + }) + + describe('FirstMatchFirst', () => { + it('selects valid UTXOs and produces change', async () => { + const mnemonic = generateMnemonic() + const account = await InMemoryKeyManager(RustCardano, { password: '', mnemonic }).publicParentKey() + const [address1, address2, address3, address4, address5, change] = addressDiscoveryWithinBounds(RustCardano, { + account, + type: AddressType.internal, + lowerBound: 0, + upperBound: 5 + }, ChainSettings.mainnet) + + // Any combination of these inputs will always produce change + const utxosWithAddressing = [ + { address: address1.address, value: '600000', id: hexGenerator(64), index: 0, addressing: { index: 0, change: 0, accountIndex: 0 } }, + { address: address2.address, value: '500000', id: hexGenerator(64), index: 1, addressing: { index: 0, change: 0, accountIndex: 0 } }, + { address: address3.address, value: '330000', id: hexGenerator(64), index: 2, addressing: { index: 0, change: 0, accountIndex: 0 } }, + { address: address4.address, value: '410000', id: hexGenerator(64), index: 3, addressing: { index: 0, change: 0, accountIndex: 0 } } + ] + + const outputs = [ + { address: address5.address, value: '10000' } + ] + + const { inputs, changeOutput } = RustCardano.inputSelection(outputs, utxosWithAddressing, change.address) + expect(inputs.length > 0).to.eql(true) + expect(changeOutput.address).to.eql(change.address) + }) + }) + }) +}) diff --git a/src/lib/RustCardanoPrimitives/index.ts b/src/lib/RustCardanoPrimitives/index.ts new file mode 100644 index 00000000000..130447936e3 --- /dev/null +++ b/src/lib/RustCardanoPrimitives/index.ts @@ -0,0 +1,246 @@ +import { Cardano, FeeAlgorithm, ChainSettings, TransactionSelection } from '../../Cardano' +import { getBindingsForEnvironment } from '../bindings' +import { InsufficientTransactionInput } from '../../Transaction/errors' +import { TxInput as CardanoTxInput, Coin as CoinT, Bip44AccountPrivate } from 'cardano-wallet' + +import { AddressType, UtxoWithAddressing } from '../../Wallet' +import { TransactionOutput, TransactionInput } from '../../Transaction' +const { + Transaction, + OutputPolicy, + InputSelectionBuilder, + TxInput, + Address, + Signature, + TransactionSignature, + Entropy, + Bip44RootPrivateKey, + PrivateKey, + AccountIndex, + AddressKeyIndex, + BlockchainSettings, + Bip44AccountPublic, + PublicKey, + TransactionBuilder, + TxoPointer, + Coin, + TxOut, + LinearFeeAlgorithm, + TransactionFinalized, + DerivationScheme, + Witness, + TransactionId +} = getBindingsForEnvironment() + +const HARD_DERIVATION_START = 0x80000000 + +function getRustFeeAlgorithm (algo: FeeAlgorithm) { + const feeAlgoMapping = { + [FeeAlgorithm.default]: LinearFeeAlgorithm.default() + } + + const targetAlgo = feeAlgoMapping[algo] + + if (!targetAlgo) { + throw new Error('Fee algorithm unsupported') + } + + return targetAlgo +} + +function getRustChainSettings (chainSettings: ChainSettings) { + const chainSettingsMapping = { + [ChainSettings.mainnet]: BlockchainSettings.mainnet() + } + + const targetSetting = chainSettingsMapping[chainSettings] + + if (!targetSetting) { + throw new Error('Chain settings unsupported') + } + + return targetSetting +} + +export function convertCoinToLovelace (coin: CoinT): string { + const ada = coin.ada() + const lovelace = coin.lovelace() + return String((ada * 1000000) + lovelace) +} + +export const RustCardano: Cardano = { + buildTransaction: (inputs, outputs, feeAlgorithm = FeeAlgorithm.default) => { + const transactionBuilder = new TransactionBuilder() + + inputs.forEach(input => { + const pointer = TxoPointer.from_json(input.pointer) + const value = Coin.from(0, Number(input.value.value)) + transactionBuilder.add_input(pointer, value) + }) + + outputs.forEach(output => { + const txOut = TxOut.from_json(output) + transactionBuilder.add_output(txOut) + }) + + const rustFeeAlgo = getRustFeeAlgorithm(feeAlgorithm) + const balance = transactionBuilder.get_balance(rustFeeAlgo) + if (balance.is_negative()) throw new InsufficientTransactionInput() + + /* + The get_balance_without_fees from the WASM bindings returns: + + Σ(transactionInputValues) - Σ(transactionOutputValues) + + This represents the fee paid on a transaction, as the positive balance + between inputs and the associated outputs is equal to the fee paid + */ + const feeAsCoinType = transactionBuilder.get_balance_without_fees().value() + const fee = convertCoinToLovelace(feeAsCoinType) + + /* + It can be useful to use the Transaction builder to estimate the + fee of a transaction before exact inputs are allocate for desired + outputs. The WASM bindings have the ability to do this + */ + const feeEstimate = transactionBuilder.estimate_fee(rustFeeAlgo) + const feeEstimateAsLovelace = convertCoinToLovelace(feeEstimate) + + const cardanoTransaction = transactionBuilder.make_transaction() + + // This gets around WASM incorrectly freeing the tx allocation when + // we want reference to a finalizer + const txClone = Transaction.from_json(cardanoTransaction.to_json()) + let finalizer = new TransactionFinalized(cardanoTransaction) + + return { + toHex: () => txClone.to_hex(), + toJson: () => txClone.to_json(), + id: () => txClone.id().to_hex(), + addWitness: ({ privateParentKey, addressing, chainSettings }) => { + if (!chainSettings) { + chainSettings = ChainSettings.mainnet + } + + const rustChainSettings = getRustChainSettings(chainSettings) + + const privateKey = PrivateKey.from_hex(privateParentKey) + const privateKeyBip44 = Bip44RootPrivateKey + .new(privateKey, DerivationScheme.v2()) + .bip44_account(AccountIndex.new(addressing.accountIndex | HARD_DERIVATION_START)) + .bip44_chain(addressing.change === 1) + .address_key(AddressKeyIndex.new(addressing.index)) + + const witness = Witness.new_extended_key(rustChainSettings, privateKeyBip44, txClone.id()) + finalizer.add_witness(witness) + }, + addExternalWitness: ({ publicParentKey, witnessIndex, witnessHex, addressType }) => { + const publicKey = PublicKey.from_hex(publicParentKey) + const publicKeyBip44 = Bip44AccountPublic + .new(publicKey, DerivationScheme.v2()) + .bip44_chain(addressType === AddressType.internal) + .address_key(AddressKeyIndex.new(witnessIndex)) + + const txSignature = TransactionSignature.from_hex(witnessHex) + + const witness = Witness.from_external(publicKeyBip44, txSignature) + finalizer.add_witness(witness) + }, + finalize: () => finalizer.finalize().to_hex(), + fee: () => fee, + estimateFee: () => feeEstimateAsLovelace + } + }, + account: (mnemonic, passphrase = '', accountIndex = 0) => { + const entropy = Entropy.from_english_mnemonics(mnemonic) + const privateKey = Bip44RootPrivateKey.recover(entropy, passphrase) + const bip44Account = privateKey.bip44_account(AccountIndex.new(accountIndex | HARD_DERIVATION_START)) + return { + privateParentKey: bip44Account.key().to_hex(), + publicParentKey: bip44Account.public().key().to_hex() + } + }, + address: ( + { publicParentKey, index, type, accountIndex }, + chainSettings = ChainSettings.mainnet + ) => { + const pk = PublicKey.from_hex(publicParentKey) + const bip44Account = Bip44AccountPublic.new(pk, DerivationScheme.v2()) + const rustChainSettings = getRustChainSettings(chainSettings) + const pubKey = bip44Account + .bip44_chain(type === AddressType.internal) + .address_key(AddressKeyIndex.new(index)) + + const address = pubKey.bootstrap_era_address(rustChainSettings) + return { + address: address.to_base58(), + index, + type, + accountIndex + } + }, + signMessage: ({ privateParentKey, addressType, signingIndex, message }) => { + const pk = PrivateKey.from_hex(privateParentKey) + const bip44PrivateKey = Bip44AccountPrivate.new(pk, DerivationScheme.v2()) + const privateKey = bip44PrivateKey + .bip44_chain(addressType === AddressType.internal) + .address_key(AddressKeyIndex.new(signingIndex)) + + return { + signature: privateKey.sign(Buffer.from(message)).to_hex(), + publicKey: bip44PrivateKey.public().bip44_chain(addressType === AddressType.internal).address_key(AddressKeyIndex.new(signingIndex)).to_hex() + } + }, + verifyMessage: ({ message, publicKey, signature }) => { + const signatureInterface = Signature.from_hex(signature) + const publicKeyInterface = PublicKey.from_hex(publicKey) + return publicKeyInterface.verify(Buffer.from(message), signatureInterface) + }, + inputSelection (outputs: TransactionOutput[], utxoSet: UtxoWithAddressing[], changeAddress: string, feeAlgorithm = FeeAlgorithm.default): TransactionSelection { + const potentialInputs: CardanoTxInput[] = utxoSet.map(utxo => { + return TxInput.new( + TxoPointer.new(TransactionId.from_hex(utxo.id), utxo.index), + TxOut.new(Address.from_base58(utxo.address), Coin.from_str(utxo.value)) + ) + }) + + const txOuts = outputs.map((out) => TxOut.new(Address.from_base58(out.address), Coin.from_str(out.value))) + const changeOutputPolicy = OutputPolicy.change_to_one_address(Address.from_base58(changeAddress)) + + let selectionBuilder = InputSelectionBuilder.first_match_first() + potentialInputs.forEach(input => selectionBuilder.add_input(input)) + txOuts.forEach(output => selectionBuilder.add_output(output)) + + const selectionResult = selectionBuilder.select_inputs(getRustFeeAlgorithm(feeAlgorithm), changeOutputPolicy) + + const estimatedChange = convertCoinToLovelace(selectionResult.estimated_change()) + const pointers = potentialInputs.map(i => { + return TxoPointer.from_json(i.to_json().ptr) + }) + + let changeOutput + if (estimatedChange !== '0') { + changeOutput = { + value: estimatedChange, + address: changeAddress + } + } + + const selectedPointers = pointers.filter((pointer) => selectionResult.is_input(pointer)) + + const inputs: TransactionInput[] = selectedPointers.map(ptr => { + const pointer = ptr.to_json() + const relevantUtxo = utxoSet.find(u => u.id === pointer.id) + + const value = { + address: relevantUtxo.address, + value: relevantUtxo.value + } + + const addressing = relevantUtxo.addressing + return { pointer, value, addressing } + }) + + return { inputs, changeOutput } + } +} diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 00000000000..93d116ca1c5 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1,3 @@ +export { InMemoryKeyManager } from './InMemoryKey' +export { LedgerKeyManager } from './Ledger' +export { RustCardano } from './RustCardanoPrimitives' diff --git a/src/test/DetermineNextAddressForWallet.spec.ts b/src/test/DetermineNextAddressForWallet.spec.ts index d45926fa75b..f84a4e06c66 100644 --- a/src/test/DetermineNextAddressForWallet.spec.ts +++ b/src/test/DetermineNextAddressForWallet.spec.ts @@ -1,25 +1,32 @@ import { seed } from './utils/seed' import { expect } from 'chai' -import { InMemoryKeyManager, connect } from '..' +import CardanoSDK from '..' import { mockProvider, seedMockProvider } from './utils/mock_provider' import { AddressType } from '../Wallet' import { addressDiscoveryWithinBounds } from '../Utils' +import { RustCardano } from '../lib' +import { ChainSettings } from '../Cardano' describe('Example: Key Derivation', () => { + let cardano: ReturnType + beforeEach(() => { + cardano = CardanoSDK() + }) + it('allows a user to determine their next receipt address', async () => { seedMockProvider(seed.utxos, seed.transactions) const mnemonic = seed.accountMnemonics.account1 - const keyManager = InMemoryKeyManager({ mnemonic, password: '' }) - const publicAccount = await keyManager.publicAccount() + const keyManager = cardano.InMemoryKeyManager({ mnemonic, password: '' }) + const publicAccount = await keyManager.publicParentKey() - const { address } = await connect(mockProvider).wallet(publicAccount).getNextReceivingAddress() - const nextAddressBasedOnSeedContext = addressDiscoveryWithinBounds({ - account: (await keyManager.publicAccount()), + const { address } = await cardano.connect(mockProvider).wallet(publicAccount).getNextReceivingAddress() + const nextAddressBasedOnSeedContext = addressDiscoveryWithinBounds(RustCardano, { + account: (await keyManager.publicParentKey()), lowerBound: 16, upperBound: 16, type: AddressType.external - })[0].address + }, ChainSettings.mainnet)[0].address expect(nextAddressBasedOnSeedContext).to.eql(address) }) @@ -28,16 +35,16 @@ describe('Example: Key Derivation', () => { seedMockProvider(seed.utxos, seed.transactions) const mnemonic = seed.accountMnemonics.account1 - const keyManager = InMemoryKeyManager({ mnemonic, password: '' }) - const publicAccount = await keyManager.publicAccount() + const keyManager = cardano.InMemoryKeyManager({ mnemonic, password: '' }) + const publicAccount = await keyManager.publicParentKey() - const { address } = await connect(mockProvider).wallet(publicAccount).getNextChangeAddress() - const nextAddressBasedOnSeedContext = addressDiscoveryWithinBounds({ + const { address } = await cardano.connect(mockProvider).wallet(publicAccount).getNextChangeAddress() + const nextAddressBasedOnSeedContext = addressDiscoveryWithinBounds(RustCardano, { account: publicAccount, lowerBound: 0, upperBound: 0, type: AddressType.internal - })[0].address + }, ChainSettings.mainnet)[0].address expect(nextAddressBasedOnSeedContext).to.eql(address) }) diff --git a/src/test/InMemoryKeyManager.spec.ts b/src/test/InMemoryKeyManager.spec.ts index 25c5c1368d1..8d92926b7a1 100644 --- a/src/test/InMemoryKeyManager.spec.ts +++ b/src/test/InMemoryKeyManager.spec.ts @@ -1,26 +1,30 @@ import { expect } from 'chai' -import { Utils, InMemoryKeyManager, connect } from '..' +import CardanoSDK from '..' import { generateTestTransaction } from './utils' import { mockProvider } from './utils/mock_provider' import { AddressType } from '../Wallet' describe('Example: In Memory Key Manager', () => { const password = 'secure' + let cardano: ReturnType + beforeEach(() => { + cardano = CardanoSDK() + }) it('allows a user to create a key manager in memory from a valid mnemonic, sign a transaction and submit it to the network', async () => { - const mnemonic = Utils.generateMnemonic() - const keyManager = InMemoryKeyManager({ mnemonic, password }) + const mnemonic = cardano.Utils.generateMnemonic() + const keyManager = cardano.InMemoryKeyManager({ mnemonic, password }) const { transaction, inputs } = generateTestTransaction({ - publicAccount: (await keyManager.publicAccount()), + account: (await keyManager.publicParentKey()), lowerBoundOfAddresses: 0, - testInputs: [{ type: AddressType.external, value: '1000000' }, { type: AddressType.external, value: '5000000' }], + testInputs: [{ type: AddressType.external, value: '2000000' }, { type: AddressType.external, value: '5000000' }], testOutputs: [{ address: 'Ae2tdPwUPEZEjJcLmvgKnuwUnfKSVuGCzRW1PqsLcWqmoGJUocBGbvWjjTx', value: '6000000' }] }) const signedTransaction = await keyManager.signTransaction(transaction, inputs) - const transactionSubmission = await connect(mockProvider).submitTransaction(signedTransaction) + const transactionSubmission = await cardano.connect(mockProvider).submitTransaction(signedTransaction) expect(transactionSubmission).to.eql(true) }) }) diff --git a/src/test/SelectInputsForTransaction.spec.ts b/src/test/SelectInputsForTransaction.spec.ts index 7be52c289e0..fa5631afb26 100644 --- a/src/test/SelectInputsForTransaction.spec.ts +++ b/src/test/SelectInputsForTransaction.spec.ts @@ -1,23 +1,28 @@ import { seed } from './utils/seed' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' -import { InMemoryKeyManager, connect } from '..' +import CardanoSDK from '..' import { mockProvider, seedMockProvider } from './utils/mock_provider' use(chaiAsPromised) describe('Example: Select inputs for transaction', () => { + let cardano: ReturnType + beforeEach(() => { + cardano = CardanoSDK() + }) + it('returns transaction inputs and a change output', async () => { seedMockProvider(seed.utxos, seed.transactions) const mnemonic = seed.accountMnemonics.account1 - const keyManager = InMemoryKeyManager({ mnemonic, password: '' }) - const publicAccount1 = await keyManager.publicAccount() - const wallet = connect(mockProvider).wallet(publicAccount1) + const keyManager = cardano.InMemoryKeyManager({ mnemonic, password: '' }) + const publicAccount1 = await keyManager.publicParentKey() + const wallet = cardano.connect(mockProvider).wallet(publicAccount1) const mnemonic2 = seed.accountMnemonics.account2 - const keyManager2 = InMemoryKeyManager({ mnemonic: mnemonic2, password: '' }) - const publicAccount2 = await keyManager2.publicAccount() - const targetOutputAddress = await connect(mockProvider).wallet(publicAccount2).getNextReceivingAddress() + const keyManager2 = cardano.InMemoryKeyManager({ mnemonic: mnemonic2, password: '' }) + const publicAccount2 = await keyManager2.publicParentKey() + const targetOutputAddress = await cardano.connect(mockProvider).wallet(publicAccount2).getNextReceivingAddress() const { inputs, changeOutput } = await wallet.selectInputsForTransaction([ { value: '100', address: targetOutputAddress.address } @@ -34,14 +39,14 @@ describe('Example: Select inputs for transaction', () => { seedMockProvider(seed.utxos, seed.transactions) const mnemonic = seed.accountMnemonics.account2 - const keyManager = InMemoryKeyManager({ mnemonic, password: '' }) - const publicAccount1 = await keyManager.publicAccount() - const wallet = connect(mockProvider).wallet(publicAccount1) + const keyManager = cardano.InMemoryKeyManager({ mnemonic, password: '' }) + const publicAccount1 = await keyManager.publicParentKey() + const wallet = cardano.connect(mockProvider).wallet(publicAccount1) const mnemonic2 = seed.accountMnemonics.account1 - const keyManager2 = InMemoryKeyManager({ mnemonic: mnemonic2, password: '' }) - const publicAccount2 = await keyManager2.publicAccount() - const targetOutputAddress = await connect(mockProvider).wallet(publicAccount2).getNextReceivingAddress() + const keyManager2 = cardano.InMemoryKeyManager({ mnemonic: mnemonic2, password: '' }) + const publicAccount2 = await keyManager2.publicParentKey() + const targetOutputAddress = await cardano.connect(mockProvider).wallet(publicAccount2).getNextReceivingAddress() const call = wallet.selectInputsForTransaction([ { value: '100', address: targetOutputAddress.address } diff --git a/src/test/SignAndVerify.spec.ts b/src/test/SignAndVerify.spec.ts index 95247c78781..d6feab18ddc 100644 --- a/src/test/SignAndVerify.spec.ts +++ b/src/test/SignAndVerify.spec.ts @@ -1,15 +1,20 @@ import { expect } from 'chai' -import { Utils, InMemoryKeyManager } from '..' +import CardanoSDK from '..' import { AddressType } from '../Wallet' describe('Example: Sign And Verify', () => { + let cardano: ReturnType + beforeEach(() => { + cardano = CardanoSDK() + }) + it('allows a user to sign a message that can be verified by others who have a reference to the public key', async () => { - const mnemonic = Utils.generateMnemonic() - const keyManager = InMemoryKeyManager({ mnemonic, password: '' }) + const mnemonic = cardano.Utils.generateMnemonic() + const keyManager = cardano.InMemoryKeyManager({ mnemonic, password: '' }) const message = 'hello world' const { signature, publicKey } = await keyManager.signMessage(AddressType.external, 0, message) - expect(Utils.verifyMessage({ + expect(cardano.Utils.verifyMessage({ publicKey, message, signature: signature diff --git a/src/test/WalletBalance.spec.ts b/src/test/WalletBalance.spec.ts index e7fc2fffbc9..fa44482678c 100644 --- a/src/test/WalletBalance.spec.ts +++ b/src/test/WalletBalance.spec.ts @@ -1,17 +1,22 @@ import { seed } from './utils/seed' import { expect } from 'chai' -import { InMemoryKeyManager, connect } from '..' +import CardanoSDK from '..' import { mockProvider, seedMockProvider } from './utils/mock_provider' describe('Example: Determine the balance for a PublicAccount, in Lovelace', () => { + let cardano: ReturnType + beforeEach(() => { + cardano = CardanoSDK() + }) + it('returns a positive number for an account with UTXOs', async () => { seedMockProvider(seed.utxos, seed.transactions) const mnemonic = seed.accountMnemonics.account1 - const keyManager = InMemoryKeyManager({ mnemonic, password: '' }) - const publicAccount = await keyManager.publicAccount() + const keyManager = cardano.InMemoryKeyManager({ mnemonic, password: '' }) + const publicAccount = await keyManager.publicParentKey() - const balance = await connect(mockProvider).wallet(publicAccount).balance() + const balance = await cardano.connect(mockProvider).wallet(publicAccount).balance() expect(balance).to.eql(200000 * 6) }) @@ -19,10 +24,10 @@ describe('Example: Determine the balance for a PublicAccount, in Lovelace', () = seedMockProvider(seed.utxos, seed.transactions) const mnemonic = seed.accountMnemonics.account2 - const keyManager = InMemoryKeyManager({ mnemonic, password: '' }) - const publicAccount = await keyManager.publicAccount() + const keyManager = cardano.InMemoryKeyManager({ mnemonic, password: '' }) + const publicAccount = await keyManager.publicParentKey() - const balance = await connect(mockProvider).wallet(publicAccount).balance() + const balance = await cardano.connect(mockProvider).wallet(publicAccount).balance() expect(balance).to.eql(0) }) }) diff --git a/src/test/utils/seed.ts b/src/test/utils/seed.ts index bb167b151d7..6fb24e6e94f 100644 --- a/src/test/utils/seed.ts +++ b/src/test/utils/seed.ts @@ -1,7 +1,8 @@ -import { Utils, InMemoryKeyManager } from '../..' import { AddressType, Utxo } from '../../Wallet' import { generateTestTransaction } from './test_transaction' -import { addressDiscoveryWithinBounds } from '../../Utils' +import { addressDiscoveryWithinBounds, generateMnemonic } from '../../Utils' +import { InMemoryKeyManager, RustCardano } from '../../lib' +import { ChainSettings } from '../../Cardano' /* This seed generates the following "chain state" @@ -18,28 +19,28 @@ import { addressDiscoveryWithinBounds } from '../../Utils' - No UTXOs */ export async function generateSeed () { - const mnemonic1 = Utils.generateMnemonic() - const mnemonic2 = Utils.generateMnemonic() - const mnemonic3 = Utils.generateMnemonic() + const mnemonic1 = generateMnemonic() + const mnemonic2 = generateMnemonic() + const mnemonic3 = generateMnemonic() - const account1 = await InMemoryKeyManager({ password: '', mnemonic: mnemonic1 }).publicAccount() - const account2 = await InMemoryKeyManager({ password: '', mnemonic: mnemonic2 }).publicAccount() + const account1 = await InMemoryKeyManager(RustCardano, { password: '', mnemonic: mnemonic1 }).publicParentKey() + const account2 = await InMemoryKeyManager(RustCardano, { password: '', mnemonic: mnemonic2 }).publicParentKey() - const account2Addresses = addressDiscoveryWithinBounds({ + const account2Addresses = addressDiscoveryWithinBounds(RustCardano, { account: account2, lowerBound: 0, upperBound: 39, type: AddressType.external - }) + }, ChainSettings.mainnet) const tx1 = generateTestTransaction({ - publicAccount: account1, + account: account1, testInputs: [ - { type: AddressType.external, value: '100000' }, - { type: AddressType.external, value: '200000' }, - { type: AddressType.external, value: '300000' }, - { type: AddressType.external, value: '200000' }, - { type: AddressType.external, value: '100000' } + { type: AddressType.external, value: '1000000' }, + { type: AddressType.external, value: '2000000' }, + { type: AddressType.external, value: '3000000' }, + { type: AddressType.external, value: '2000000' }, + { type: AddressType.external, value: '1000000' } ], lowerBoundOfAddresses: 0, testOutputs: [ @@ -50,13 +51,13 @@ export async function generateSeed () { }) const tx2 = generateTestTransaction({ - publicAccount: account1, + account: account1, testInputs: [ - { type: AddressType.external, value: '100000' }, - { type: AddressType.external, value: '200000' }, - { type: AddressType.external, value: '300000' }, - { type: AddressType.external, value: '200000' }, - { type: AddressType.external, value: '100000' } + { type: AddressType.external, value: '1000000' }, + { type: AddressType.external, value: '2000000' }, + { type: AddressType.external, value: '3000000' }, + { type: AddressType.external, value: '2000000' }, + { type: AddressType.external, value: '1000000' } ], lowerBoundOfAddresses: 4, testOutputs: [ @@ -67,16 +68,16 @@ export async function generateSeed () { }) const account1Utxos: Utxo[] = tx1.inputs.concat(tx2.inputs).map(input => { - return { address: input.value.address, id: tx1.transaction.id().to_hex(), index: 0, value: '1000000' } + return { address: input.value.address, id: tx1.transaction.id(), index: 0, value: '10000000' } }) const account2Utxos: Utxo[] = [ - { id: tx1.transaction.id().to_hex(), index: 0, address: account2Addresses[0].address, value: '200000' }, - { id: tx1.transaction.id().to_hex(), index: 1, address: account2Addresses[5].address, value: '200000' }, - { id: tx1.transaction.id().to_hex(), index: 2, address: account2Addresses[10].address, value: '200000' }, - { id: tx2.transaction.id().to_hex(), index: 3, address: account2Addresses[15].address, value: '200000' }, - { id: tx2.transaction.id().to_hex(), index: 4, address: account2Addresses[20].address, value: '200000' }, - { id: tx2.transaction.id().to_hex(), index: 5, address: account2Addresses[25].address, value: '200000' } + { id: tx1.transaction.id(), index: 0, address: account2Addresses[0].address, value: '200000' }, + { id: tx1.transaction.id(), index: 1, address: account2Addresses[5].address, value: '200000' }, + { id: tx1.transaction.id(), index: 2, address: account2Addresses[10].address, value: '200000' }, + { id: tx2.transaction.id(), index: 3, address: account2Addresses[15].address, value: '200000' }, + { id: tx2.transaction.id(), index: 4, address: account2Addresses[20].address, value: '200000' }, + { id: tx2.transaction.id(), index: 5, address: account2Addresses[25].address, value: '200000' } ] return { @@ -93,98 +94,105 @@ export async function generateSeed () { } // Uncomment and run this file with `npx ts-node src/test/utils/mock_provider.ts` to regenerate the seed -// console.log(JSON.stringify(generateSeed(), null, 4)) +// generateSeed().then(r => { +// console.log(JSON.stringify(r, null, 4)) +// }) export const seed = { 'accountMnemonics': { - 'account1': 'dune bike sunny phrase service clip slice taste game limit define symbol', - 'account2': 'brave path obscure silk drum mosquito all coffee next summer nothing winner' + 'account1': 'access sausage absorb leopard brother wave victory travel confirm draw glimpse animal', + 'account2': 'cycle burst badge budget fabric utility napkin salmon rubber knee hunt reform' }, 'transactions': [ { 'inputs': [ { 'pointer': { - 'id': 'dedd121350a4337e65b7b6188aada7f2c06add547a3fc722a9910d97998c7759', + 'id': '50c691765a7b18d1681e3ca3160307e8ecb298c09c935ead26e21e51e36329f1', 'index': 0 }, 'value': { - 'address': 'Ae2tdPwUPEZGiQQTVEcNicxT9omVNNTVwkgM3A4atejUQaimxa9Y5FPgaKY', - 'value': '100000' + 'address': 'Ae2tdPwUPEZ6oDR4P2CcWCTzxLiGgQRTezrjFBSPitw4nCDgHbV6iPxQTyk', + 'value': '1000000' }, 'addressing': { 'change': 0, - 'index': 0 + 'index': 0, + 'accountIndex': 0 } }, { 'pointer': { - 'id': '60ef31b7f3033a981182d4c6f08384c74d94e5669360cea680459c27c308088f', + 'id': '4fa89fff297cbc144729982ab76512c441a6bbbd3d83bb2d29a4395b7bc9dc44', 'index': 1 }, 'value': { - 'address': 'Ae2tdPwUPEZGfJ2hQvKtJewggrung58P4GWh1K8KgKYgSJ2CB9fHmyArCJX', - 'value': '200000' + 'address': 'Ae2tdPwUPEYzrf1toTgJbkEXpvQZ3CCRasPUotJGDp4HjEeK9U4rNiyJ4PL', + 'value': '2000000' }, 'addressing': { 'change': 0, - 'index': 1 + 'index': 1, + 'accountIndex': 0 } }, { 'pointer': { - 'id': '91698b97779e76876ea05655ce7f70b75a2bb8f67b75a218a772eadac6bbcb2e', + 'id': 'b69742db27f442fe93ca7aef917a45b8ea4bef62b08693ae75d5877bc151ed09', 'index': 2 }, 'value': { - 'address': 'Ae2tdPwUPEYzLpMAhWQJ6KQacUWh1D53bCnvJPfXVuab2UMJBPhkAKPCJAb', - 'value': '300000' + 'address': 'Ae2tdPwUPEZ5qQN7ZMrbf52GzsCXSPzfwxFKexJFyGBbKZXb9hQPxh5h89S', + 'value': '3000000' }, 'addressing': { 'change': 0, - 'index': 2 + 'index': 2, + 'accountIndex': 0 } }, { 'pointer': { - 'id': '88543abc5b47664c734d4b0870bd48bdd880d590d39a6792e8fa0298f4311c50', + 'id': '8cd009c79fd855b8517584692f78618173e4cb06150b4e3a180252ab6f089324', 'index': 3 }, 'value': { - 'address': 'Ae2tdPwUPEZKzpvrs2oZSA3ftFKFLGu3eccKZ5iEpMSwzXzfjpMknKDAFZw', - 'value': '200000' + 'address': 'Ae2tdPwUPEZCB5tJAHkszVk3hWDZEPuQGEYqs5T1ieXGeMMCmvqKzk6X1oJ', + 'value': '2000000' }, 'addressing': { 'change': 0, - 'index': 3 + 'index': 3, + 'accountIndex': 0 } }, { 'pointer': { - 'id': '2de5075d9dccb85fc181709679ed4b52539ac5b67b75c15397a1af80dde0d69a', + 'id': '8882a06acc04f1c2c3e9c284a29fa0b4b551cbeeb69e665b9762e2ef6846bfaa', 'index': 4 }, 'value': { - 'address': 'Ae2tdPwUPEZ4Rc8yxB726KP2W15Zvn5FRZR5CZbVoK6yYMYHLnTCt6AeqNQ', - 'value': '100000' + 'address': 'Ae2tdPwUPEZ7UVopLf3cA62JjQhAoQJSHLUbPcBhTLrwbZrW6o6yRu7ha5s', + 'value': '1000000' }, 'addressing': { 'change': 0, - 'index': 4 + 'index': 4, + 'accountIndex': 0 } } ], 'outputs': [ { - 'address': 'Ae2tdPwUPEYxJD58QiQesER14LvUtZKC3CQmnkvgiADAKqpTXPaRMbj4xo2', + 'address': 'Ae2tdPwUPEZLbgtNSaBHc3LEUCaF1GSE1ZgHDApYfgHMo37DGTUZ3Yz47ir', 'value': '198036' }, { - 'address': 'Ae2tdPwUPEZ5mjoaDxQeZhqDsD82gdTT8AGmPxFQ6bN7ALXnJXNYoLxYTty', + 'address': 'Ae2tdPwUPEZLanS6tvrZGLqy5dQLtTtB3xTCLtQy226V7YAk3SDgwt9V9AZ', 'value': '200000' }, { - 'address': 'Ae2tdPwUPEZLLSW7Y33qFVhkm2DhUzKamXuijrmQqBx3nstFBzNFaeJpEFh', + 'address': 'Ae2tdPwUPEZA3mqVuZvzzk28D4uwLkkbj8LpMHVQ9BeGoW8Smi6mzpnKu5n', 'value': '200000' } ] @@ -193,86 +201,91 @@ export const seed = { 'inputs': [ { 'pointer': { - 'id': '273afe80b119ab518b01e5bbe936425d4cd84e1fb9056d5eef79d60371213787', + 'id': '645d49b533c54df44ab1380042a35306131ffde58474664ac5681f4e25be5952', 'index': 0 }, 'value': { - 'address': 'Ae2tdPwUPEZ4Rc8yxB726KP2W15Zvn5FRZR5CZbVoK6yYMYHLnTCt6AeqNQ', - 'value': '100000' + 'address': 'Ae2tdPwUPEZ7UVopLf3cA62JjQhAoQJSHLUbPcBhTLrwbZrW6o6yRu7ha5s', + 'value': '1000000' }, 'addressing': { 'change': 0, - 'index': 4 + 'index': 4, + 'accountIndex': 0 } }, { 'pointer': { - 'id': 'f360479df2b54997795cf28be471bf5a16518e895ec637877aa5ded27cb229b3', + 'id': '5c49483b9016b197da7b8537e9d41a561a381c5f68d9385ea74d17a0a81f54e4', 'index': 1 }, 'value': { - 'address': 'Ae2tdPwUPEZ83naRuph3AxaoPAyGbnLduu7FvgDhd2XjpbpcSSYeTgvuSxW', - 'value': '200000' + 'address': 'Ae2tdPwUPEZN9QPGcGdkrwsS8apBPosQyRBLQMrZ2JW67wY1ndQvbe6Sjn6', + 'value': '2000000' }, 'addressing': { 'change': 0, - 'index': 5 + 'index': 5, + 'accountIndex': 0 } }, { 'pointer': { - 'id': 'f4ef145e8104828dae1aa6631faefdc08dd962dd21176e9b62a7b9b36e09ab08', + 'id': '27adf8b485a20619690dd32f56fbabcead058c8ec4c8a7cc2108b54fe4425127', 'index': 2 }, 'value': { - 'address': 'Ae2tdPwUPEYxM2oCK42hHw4gTo8K5p7BthQQxecEJDxRWxTQQMvxvyUCbbV', - 'value': '300000' + 'address': 'Ae2tdPwUPEZCe9QR4svAfHN7Rrr9NUX5V5pDY97oe4PSFbPfbgCEzvMa55R', + 'value': '3000000' }, 'addressing': { 'change': 0, - 'index': 6 + 'index': 6, + 'accountIndex': 0 } }, { 'pointer': { - 'id': '143508ba21cebacc8c99122676de600450a30e8d51c4cd1ab090975092e47672', + 'id': '633ea0126b27f4d2e8d97bb79a845e1c89248bdafdeccdacaf65d248e7ad38e9', 'index': 3 }, 'value': { - 'address': 'Ae2tdPwUPEZEAJKhiMpNFURjybgAboYbaZRFLXtbGZBg7JaY13mLXWBnd6C', - 'value': '200000' + 'address': 'Ae2tdPwUPEZ4H5sZ5AJRfrUpXGiCbpGzHGopkcGV9Tdq6iJKYsn2TYmRDra', + 'value': '2000000' }, 'addressing': { 'change': 0, - 'index': 7 + 'index': 7, + 'accountIndex': 0 } }, { 'pointer': { - 'id': '76cb4ca05a35b37cc84265d52649baf3d6c4910580ece140919babe9426e2a42', + 'id': '3597d347557f4c6cf37c3879f93dc46a92b3d17139ef3e1e7e3330d78452dfbc', 'index': 4 }, 'value': { - 'address': 'Ae2tdPwUPEZEbC3pCF3NwnZfhKo1i5PTgwcsZsNor4po6qbfQbrB12FK9q2', - 'value': '100000' + 'address': 'Ae2tdPwUPEZLVRMEve2GjWbxGfFm9ws577j68cttrL9UfYLhUv4zrf53Mrh', + 'value': '1000000' }, 'addressing': { 'change': 0, - 'index': 8 + 'index': 8, + 'accountIndex': 0 } } ], 'outputs': [ { - 'address': 'Ae2tdPwUPEZC68988DdY4VKUXGHuUadkZiQEc14FJhEDuZoaXzu8gHAiXoL', + 'address': 'Ae2tdPwUPEYyhqEXhcYHWZsSwFkeJdiYkNPf2xEyAprzQ9wfruoA3avMVGY', 'value': '198036' }, { - 'address': 'Ae2tdPwUPEZ3mXn6kBpoGfBp6ZjWbeHir6LJQtQ95mJ6DGYt4wGwSWpLyCB', + 'address': 'Ae2tdPwUPEZLtX243BCbEWi4nwdAvcv274GQxmxJpB1FR2UBiy3HHmopD1R', 'value': '200000' }, { - 'address': 'Ae2tdPwUPEZKZcGaQderAkYknURwJ34dzxWq3WDPS1y6K2PXjcmuy94zeo7', + 'address': 'Ae2tdPwUPEZ3QJWHcWh9gAaacB6rkdHLkxEvUEwMJ6gYRfTshnN7xazAb2P', 'value': '200000' } ] @@ -280,99 +293,99 @@ export const seed = { ], 'utxos': [ { - 'address': 'Ae2tdPwUPEZGiQQTVEcNicxT9omVNNTVwkgM3A4atejUQaimxa9Y5FPgaKY', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZ6oDR4P2CcWCTzxLiGgQRTezrjFBSPitw4nCDgHbV6iPxQTyk', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEZGfJ2hQvKtJewggrung58P4GWh1K8KgKYgSJ2CB9fHmyArCJX', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEYzrf1toTgJbkEXpvQZ3CCRasPUotJGDp4HjEeK9U4rNiyJ4PL', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEYzLpMAhWQJ6KQacUWh1D53bCnvJPfXVuab2UMJBPhkAKPCJAb', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZ5qQN7ZMrbf52GzsCXSPzfwxFKexJFyGBbKZXb9hQPxh5h89S', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEZKzpvrs2oZSA3ftFKFLGu3eccKZ5iEpMSwzXzfjpMknKDAFZw', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZCB5tJAHkszVk3hWDZEPuQGEYqs5T1ieXGeMMCmvqKzk6X1oJ', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEZ4Rc8yxB726KP2W15Zvn5FRZR5CZbVoK6yYMYHLnTCt6AeqNQ', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZ7UVopLf3cA62JjQhAoQJSHLUbPcBhTLrwbZrW6o6yRu7ha5s', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEZ4Rc8yxB726KP2W15Zvn5FRZR5CZbVoK6yYMYHLnTCt6AeqNQ', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZ7UVopLf3cA62JjQhAoQJSHLUbPcBhTLrwbZrW6o6yRu7ha5s', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEZ83naRuph3AxaoPAyGbnLduu7FvgDhd2XjpbpcSSYeTgvuSxW', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZN9QPGcGdkrwsS8apBPosQyRBLQMrZ2JW67wY1ndQvbe6Sjn6', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEYxM2oCK42hHw4gTo8K5p7BthQQxecEJDxRWxTQQMvxvyUCbbV', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZCe9QR4svAfHN7Rrr9NUX5V5pDY97oe4PSFbPfbgCEzvMa55R', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEZEAJKhiMpNFURjybgAboYbaZRFLXtbGZBg7JaY13mLXWBnd6C', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZ4H5sZ5AJRfrUpXGiCbpGzHGopkcGV9Tdq6iJKYsn2TYmRDra', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'address': 'Ae2tdPwUPEZEbC3pCF3NwnZfhKo1i5PTgwcsZsNor4po6qbfQbrB12FK9q2', - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'address': 'Ae2tdPwUPEZLVRMEve2GjWbxGfFm9ws577j68cttrL9UfYLhUv4zrf53Mrh', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'value': '1000000' + 'value': '10000000' }, { - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 0, - 'address': 'Ae2tdPwUPEYxJD58QiQesER14LvUtZKC3CQmnkvgiADAKqpTXPaRMbj4xo2', + 'address': 'Ae2tdPwUPEZLbgtNSaBHc3LEUCaF1GSE1ZgHDApYfgHMo37DGTUZ3Yz47ir', 'value': '200000' }, { - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 1, - 'address': 'Ae2tdPwUPEZ5mjoaDxQeZhqDsD82gdTT8AGmPxFQ6bN7ALXnJXNYoLxYTty', + 'address': 'Ae2tdPwUPEZLanS6tvrZGLqy5dQLtTtB3xTCLtQy226V7YAk3SDgwt9V9AZ', 'value': '200000' }, { - 'id': 'c4b435294aed00ed3e5cb3208714b76a79096bf037f8a22cb4bafe48795c20a3', + 'id': 'edad308e5d8e0047ef501aef84267646367cc524cdc333dbbb348739594c8de9', 'index': 2, - 'address': 'Ae2tdPwUPEZLLSW7Y33qFVhkm2DhUzKamXuijrmQqBx3nstFBzNFaeJpEFh', + 'address': 'Ae2tdPwUPEZA3mqVuZvzzk28D4uwLkkbj8LpMHVQ9BeGoW8Smi6mzpnKu5n', 'value': '200000' }, { - 'id': 'b8396414af5ce98eb230e5d1abdd0a44b4215c38219134d97866f1d6787bc8de', + 'id': '9224942d944020ab1abdd6c19046201a316d1846e2269e082b3bde537d1a8a20', 'index': 3, - 'address': 'Ae2tdPwUPEZC68988DdY4VKUXGHuUadkZiQEc14FJhEDuZoaXzu8gHAiXoL', + 'address': 'Ae2tdPwUPEYyhqEXhcYHWZsSwFkeJdiYkNPf2xEyAprzQ9wfruoA3avMVGY', 'value': '200000' }, { - 'id': 'b8396414af5ce98eb230e5d1abdd0a44b4215c38219134d97866f1d6787bc8de', + 'id': '9224942d944020ab1abdd6c19046201a316d1846e2269e082b3bde537d1a8a20', 'index': 4, - 'address': 'Ae2tdPwUPEZ3mXn6kBpoGfBp6ZjWbeHir6LJQtQ95mJ6DGYt4wGwSWpLyCB', + 'address': 'Ae2tdPwUPEZLtX243BCbEWi4nwdAvcv274GQxmxJpB1FR2UBiy3HHmopD1R', 'value': '200000' }, { - 'id': 'b8396414af5ce98eb230e5d1abdd0a44b4215c38219134d97866f1d6787bc8de', + 'id': '9224942d944020ab1abdd6c19046201a316d1846e2269e082b3bde537d1a8a20', 'index': 5, - 'address': 'Ae2tdPwUPEZKZcGaQderAkYknURwJ34dzxWq3WDPS1y6K2PXjcmuy94zeo7', + 'address': 'Ae2tdPwUPEZ3QJWHcWh9gAaacB6rkdHLkxEvUEwMJ6gYRfTshnN7xazAb2P', 'value': '200000' } ] diff --git a/src/test/utils/test_transaction.ts b/src/test/utils/test_transaction.ts index 54d76c9e16b..58e540c40d4 100644 --- a/src/test/utils/test_transaction.ts +++ b/src/test/utils/test_transaction.ts @@ -1,38 +1,39 @@ -import { Bip44AccountPublic } from 'cardano-wallet' import { AddressType } from '../../Wallet' import Transaction, { TransactionInput } from '../../Transaction' -import { addressDiscoveryWithinBounds, estimateTransactionFee } from '../../Utils' +import { addressDiscoveryWithinBounds } from '../../Utils' +import { RustCardano } from '../../lib' +import { ChainSettings } from '../../Cardano' /** * generateTestTransaction is a test helper. * It can be used to make relatively realistic transactions that can be used for transaction testing or seeding a mock provider. */ export function generateTestTransaction ({ - publicAccount, + account, testInputs, lowerBoundOfAddresses, testOutputs, inputId }: { - publicAccount: Bip44AccountPublic, + account: string, testInputs: { value: string, type: AddressType }[], lowerBoundOfAddresses: number, testOutputs: { address: string, value: string }[], inputId?: string }) { - const receiptAddresses = addressDiscoveryWithinBounds({ - account: publicAccount, + const receiptAddresses = addressDiscoveryWithinBounds(RustCardano, { + account, type: AddressType.external, lowerBound: lowerBoundOfAddresses, upperBound: lowerBoundOfAddresses + testInputs.length - }) + }, ChainSettings.mainnet) - const changeAddresses = addressDiscoveryWithinBounds({ - account: publicAccount, + const changeAddresses = addressDiscoveryWithinBounds(RustCardano, { + account, type: AddressType.internal, lowerBound: lowerBoundOfAddresses, upperBound: lowerBoundOfAddresses + testInputs.length - }) + }, ChainSettings.mainnet) const inputs: TransactionInput[] = testInputs.map(({ value }, index) => { const { address, index: addressIndex } = testInputs[index].type === AddressType.external @@ -43,14 +44,15 @@ export function generateTestTransaction ({ // Mock a 64 byte transaction id pointer: { id: inputId || hexGenerator(64), index }, value: { address, value }, - addressing: { change: testInputs[index].type === AddressType.internal ? 1 : 0, index: addressIndex } + addressing: { change: testInputs[index].type === AddressType.internal ? 1 : 0, index: addressIndex, accountIndex: 0 } } }) - const fee = estimateTransactionFee(inputs, testOutputs) + const fee = Transaction(RustCardano, inputs, testOutputs).estimateFee() testOutputs[0].value = (Number(testOutputs[0].value) - Number(fee)).toString() - return { transaction: Transaction(inputs, testOutputs), inputs } + + return { transaction: Transaction(RustCardano, inputs, testOutputs), inputs } } /** Test helper only */ diff --git a/src/test/utils/utxo.ts b/src/test/utils/utxo.ts index c51dd161964..27cf584eaf7 100644 --- a/src/test/utils/utxo.ts +++ b/src/test/utils/utxo.ts @@ -1,17 +1,18 @@ import { AddressType } from '../../Wallet' -import { Bip44AccountPublic } from 'cardano-wallet' import { hexGenerator } from '.' import { addressDiscoveryWithinBounds } from '../../Utils' +import { RustCardano } from '../../lib' +import { ChainSettings } from '../../Cardano' -export function generateTestUtxos ({ account, lowerBound, upperBound, type, value }: { account: Bip44AccountPublic, lowerBound: number, upperBound: number, type: AddressType, value: string }) { +export function generateTestUtxos ({ account, lowerBound, upperBound, type, value }: { account: string, lowerBound: number, upperBound: number, type: AddressType, value: string }) { const numberOfUtxos = upperBound - lowerBound return [...Array(numberOfUtxos)].map((_, index) => { - const address = addressDiscoveryWithinBounds({ + const address = addressDiscoveryWithinBounds(RustCardano, { account, type, lowerBound: index + lowerBound, upperBound: index + lowerBound - })[0].address + }, ChainSettings.mainnet)[0].address return { value, address, id: hexGenerator(64), index } })