Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ for arg in "$@"; do
fi
done

DOCKER_UID=$(id -u)
DOCKER_GID=$(id -g)
export DOCKER_UID
export DOCKER_GID

if [ "$1" == "build" ]; then
docker compose -f docker-compose.test.base.yml -f docker-compose.test.local.yml build
elif [ "$1" = "run" ]; then
Expand Down
1 change: 1 addition & 0 deletions docker-compose.test.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ services:
build:
context: .
dockerfile: Dockerfile
user: "${DOCKER_UID}:${DOCKER_GID}"
volumes:
- .:/bitcore
ports:
Expand Down
10 changes: 5 additions & 5 deletions packages/bitcore-cli/src/commands/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export async function createTransaction(
return; // valid value, optional
}
const val = parseInt(value);
if (isNaN(val) || val < 0) {
if (isNaN(val) || val < 0 || !(/^\d+$/.test(value))) {
return 'Please enter a valid destination tag';
}
return; // valid value
Expand Down Expand Up @@ -256,7 +256,7 @@ export async function createTransaction(
throw new UserCancelled();
}
if (BWCUtils.isUtxoChain(chain)) {
customFeeRate = (Number(customFeeRate) * 1000).toString(); // convert to sats/KB
customFeeRate = Math.round(Number(customFeeRate) * 1000).toString(); // convert to sats/KB
}
}

Expand All @@ -267,8 +267,8 @@ export async function createTransaction(
}],
message: note,
feeLevel: feeLevel === 'custom' ? undefined : feeLevel,
feePerKb: feeLevel === 'custom' ? parseFloat(customFeeRate) : undefined,
fee: opts.fee ? parseFloat(opts.fee) : undefined,
feePerKb: feeLevel === 'custom' ? BigInt(Math.ceil(Number(customFeeRate))) : undefined,
fee: opts.fee ? BigInt(Math.ceil(parseFloat(opts.fee))) : undefined,
sendMax,
tokenAddress: tokenObj?.contractAddress,
flags: opts.flags,
Expand All @@ -294,7 +294,7 @@ export async function createTransaction(
: Utils.renderAmount(currency, BigInt(txp.amount) + BigInt(txp.fee))
}`);
if (txp.nonce != null) {
lines.push(`Nonce: ${txp.nonce}`);
lines.push(`Nonce: ${BigInt(txp.nonce)}`);
}
if (note) {
lines.push(`Note: ${txp.message}`);
Expand Down
2 changes: 1 addition & 1 deletion packages/bitcore-client/src/storage/level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class Level {
const walletExists =
fs.existsSync(this.path) && fs.existsSync(this.path + '/LOCK') && fs.existsSync(this.path + '/LOG');
if (!walletExists) {
throw new Error('Not a valid wallet path');
throw new Error('Not a valid wallet path: ' + this.path);
}
}
if (StorageCache[this.path]) {
Expand Down
2 changes: 1 addition & 1 deletion packages/bitcore-client/src/storage/textFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class TextFile {
if (!createIfMissing) {
const walletPath = fs.existsSync(this.db);
if (!walletPath) {
throw new Error('Not a valid wallet path');
throw new Error('Not a valid wallet path: ' + this.db);
}
}
console.log('using wallets at', this.db);
Expand Down
11 changes: 9 additions & 2 deletions packages/bitcore-client/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,15 @@ export class Wallet {
static async deleteWallet(params: { name: string; path?: string; storage?: Storage; storageType?: StorageType }) {
const { name, path, storageType } = params;
let { storage } = params;
storage = storage || new Storage({ errorIfExists: false, createIfMissing: false, path, storageType });
await storage.deleteWallet({ name });
try {
storage = storage || new Storage({ errorIfExists: false, createIfMissing: false, path, storageType });
await storage.deleteWallet({ name });
} catch (e: any) {
// ignore error if default wallet path does not exist
if (!path && !e.message.includes('Not a valid wallet path')) {
throw e;
}
}
}

static async create(params: Partial<IWalletExt>) {
Expand Down
31 changes: 16 additions & 15 deletions packages/bitcore-node/test/integration/models/wallet.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Wallet, IWalletExt } from '@bitpay-labs/bitcore-client';
import { Wallet, type IWalletExt } from '@bitpay-labs/bitcore-client';
import { expect } from 'chai';
import config from '../../../src/config';
import { WalletStorage } from '../../../src/models/wallet';
import { WalletAddressStorage } from '../../../src/models/walletAddress';
import { AsyncRPC } from '../../../src/rpc';
import { Api } from '../../../src/services/api';
import { Event } from '../../../src/services/event';
import { IUtxoNetworkConfig } from '../../../src/types/Config';
import { intAfterHelper, intBeforeHelper } from '../../helpers/integration';
import type { IUtxoNetworkConfig } from '../../../src/types/Config';

describe('Wallet Model', function() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
Expand All @@ -24,23 +24,24 @@ describe('Wallet Model', function() {
let rpc: AsyncRPC;

before(async function() {
chainConfig = config.chains[chain][network] as IUtxoNetworkConfig;
creds = chainConfig.rpc;
rpc = new AsyncRPC(creds.username, creds.password, creds.host, creds.port);
await Wallet.deleteWallet({ name: walletName });
await intBeforeHelper();
try {
chainConfig = config.chains[chain][network] as IUtxoNetworkConfig;
creds = chainConfig.rpc;
rpc = new AsyncRPC(creds.username, creds.password, creds.host, creds.port);
await Wallet.deleteWallet({ name: walletName });
await intBeforeHelper();
await Event.start();
await Api.start();
} catch (e: any) {
console.error(e.stack ? 'ERROR STACK: ' + e.stack : e);
throw e;
}
});

after(async () => intAfterHelper(suite));

before(async () => {
await Event.start();
await Api.start();
});


after(async () => {
await Event.stop();
await Api.stop();
intAfterHelper(suite);
});

describe('Wallet Create', () => {
Expand Down
103 changes: 68 additions & 35 deletions packages/bitcore-wallet-client/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { Address } from '../types/address';
import type { ServerAssistedImportEvents } from '../types/serverAssistedImportEvents';

const $ = singleton();
const BigIntTry = CWC.Utils.BI.BigIntTry;

const Bitcore = CWC.BitcoreLib;
const Bitcore_ = {
Expand Down Expand Up @@ -1613,15 +1614,15 @@ export class API extends EventEmitter {
*/
async createTxProposal(
/** Txp object */
opts: {
txOpts: {
/** If provided it will be used as this TX proposal ID. Should be unique in the scope of the wallet. */
txProposalId?: string;
/** Transaction outputs. */
outputs: Array<{
/** Destination address. */
toAddress: string;
/** Amount to transfer in satoshis. */
amount: number | bigint;
amount: bigint;
/** A message to attach to this output. */
message?: string;
}>;
Expand All @@ -1630,7 +1631,7 @@ export class API extends EventEmitter {
/** Specify the fee level for this TX. Default: normal */
feeLevel?: 'priority' | 'normal' | 'economy' | 'superEconomy';
/** Specify the fee per kilobyte for this tx (in satoshis). */
feePerKb?: number | bigint;
feePerKb?: bigint;
/** Use this address as the change address for the tx. The address should belong to the wallet. In the case of singleAddress wallets, the first main address will be used. */
changeAddress?: string;
/** Send maximum amount of funds that make sense under the specified fee/feePerKb conditions. */
Expand All @@ -1644,7 +1645,7 @@ export class API extends EventEmitter {
/** Inputs for this TX */
inputs?: Array<any>; // TODO
/** Use a fixed fee for this TX (only when opts.inputs is specified). */
fee?: number | bigint;
fee?: bigint;
/** If set, TX outputs won't be shuffled. */
noShuffleOutputs?: boolean;
/** Specify signing method (ecdsa or schnorr) otherwise use default for chain. Only applies to BCH */
Expand All @@ -1663,32 +1664,46 @@ export class API extends EventEmitter {
flags?: string;
/** (XRP only) Destination tag for the transaction */
destinationTag?: number | string;
/** (EVM only) Nonce for the transaction */
nonce?: string | bigint;
},
opts?: {
/** ONLY FOR TESTING */
baseUrl?: string;
/**
* Number format for the tx-building numbers (e.g. amounts, nonce, etc.). Default: 'hex'
* Note: The given `txp` will be converted server-side and returned in the specified format.
*/
numberFormat?: 'hex' | 'number' | 'string';
},
/** @deprecated */
cb?: (err?: Error, txp?: any) => void,
/** ONLY FOR TESTING */
baseUrl?: string
cb?: (err?: Error, txp?: any) => void
) {
opts = opts || {};
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (typeof cb === 'function') {
log.warn('DEPRECATED: createTxProposal will remove callback support in the future.');
} else if (typeof cb === 'string') {
baseUrl = cb;
}

try {
$.checkState(this.credentials && this.credentials.isComplete(), 'Failed state: this.credentials at <createTxProposal()>');
$.checkState(this.credentials.sharedEncryptingKey);
$.checkArgument(opts);
$.checkArgument(txOpts);

// BCH schnorr deployment
if (!opts.signingMethod && this.credentials.coin == 'bch') {
opts.signingMethod = 'schnorr';
if (!txOpts.signingMethod && this.credentials.coin == 'bch') {
txOpts.signingMethod = 'schnorr';
}

const args = this._getCreateTxProposalArgs(opts);
baseUrl = baseUrl || '/v3/txproposals/';
const args = this._getCreateTxProposalArgs(txOpts);
const baseUrl = (typeof process !== 'undefined' && process.argv.some(arg => arg.includes('.test.js')) && opts.baseUrl) || '/v3/txproposals';
// baseUrl = baseUrl || '/v4/txproposals/'; // DISABLED 2020-04-07
const qs = `?numberFormat=${opts.numberFormat || 'hex'}`;

const { body: txp } = await this.request.post<any, Txp>(baseUrl, args);
const { body: txp } = await this.request.post<any, Txp>(baseUrl + qs, args);
this._processTxps(txp);
if (!Verifier.checkProposalCreation(args, txp, this.credentials.sharedEncryptingKey)) {
throw new Errors.SERVER_COMPROMISED();
Expand Down Expand Up @@ -2696,7 +2711,7 @@ export class API extends EventEmitter {
try {
$.checkState(this.credentials && this.credentials.isComplete(), 'Failed state: this.credentials at <getTx()>');

const { body: txp } = await this.request.get<Txp>(`/v1/txproposals/${txProposalId}`);
const { body: txp } = await this.request.get<Txp>(`/v1/txproposals/${txProposalId}?numberFormat=hex`);
this._processTxps(txp);
if (cb) { cb(null, txp); }
return txp;
Expand Down Expand Up @@ -3043,33 +3058,40 @@ export class API extends EventEmitter {
/** Specify the fee level. Default: normal */
feeLevel?: 'priority' | 'normal' | 'economy' | 'superEconomy';
/** Specify the fee per KB (in satoshi) */
feePerKb?: number;
feePerKb?: bigint;
/** Indicates it if should use (or not) the unconfirmed utxos */
excludeUnconfirmedUtxos?: boolean;
/** Return the inputs used to build the tx */
returnInputs?: boolean;
},
/** @deprecated */
cb?: (err?: Error, result?: any) => void
) {
cb?: (err?: Error, result?: SendMaxInfo<bigint>) => void
): Promise<SendMaxInfo<bigint>> {
if (cb) {
log.warn('DEPRECATED: getSendMaxInfo will remove callback support in the future.');
}
try {
opts = opts || {};

const args = [];
args.push('numberFormat=hex');
if (opts.feeLevel) args.push('feeLevel=' + opts.feeLevel);
if (opts.feePerKb != null) args.push('feePerKb=' + opts.feePerKb);
if (opts.excludeUnconfirmedUtxos) args.push('excludeUnconfirmedUtxos=1');
if (opts.returnInputs) args.push('returnInputs=1');

let qs = '';
if (args.length > 0) qs = '?' + args.join('&');

const { body: result } = await this.request.get('/v1/sendmaxinfo/' + qs);
if (cb) { cb(null, result); }
return result;
const { body: result } = await this.request.get<SendMaxInfo<string>>(`/v1/sendmaxinfo?${args.join('&')}`);
const resultWithBigInt: SendMaxInfo<bigint> = {
...result,
amount: BigIntTry(result.amount),
amountBelowFee: BigIntTry(result.amountBelowFee),
fee: BigIntTry(result.fee),
feePerKb: BigIntTry(result.feePerKb),
size: BigIntTry(result.size),
amountAboveMaxSize: BigIntTry(result.amountAboveMaxSize)
};
if (cb) { cb(null, resultWithBigInt); }
return resultWithBigInt;
} catch (err) {
if (cb) cb(err);
else throw err;
Expand Down Expand Up @@ -4235,7 +4257,7 @@ export interface Txp {
comment?: string;
}>; // TODO
addressType: string;
amount: number | string;
amount: string;
chain: string;
coin: string;
changeAddress?: {
Expand All @@ -4257,9 +4279,9 @@ export interface Txp {
creatorId: string;
creatorName?: string; // might be an encrypted object
excludeUnconfirmedUtxos: boolean;
fee: number | string;
fee: string;
feeLevel: string;
feePerKb: number | string;
feePerKb: string;
from?: string;
hasUnconfirmedInputs?: boolean;
id: string;
Expand All @@ -4281,12 +4303,12 @@ export interface Txp {
message?: string; // might be an encrypted object
encryptedMessage?: string; // is set equal to `message` before decryption in processTxps()
network: string;
nonce?: number | string;
nonce?: string;
deferNonce?: boolean;
note?: Note;
outputOrder: Array<number>;
outputs?: Array<{
amount: number | string;
amount: string;
toAddress: string;
message?: string; // might be an encrypted object
encryptedMessage?: string; // is set equal to `message` before decryption in processTxps()
Expand Down Expand Up @@ -4314,20 +4336,31 @@ export interface PublishedTxp extends Txp {
data?: string; // ?
destinationTag?: string; // XRP
enableRBF?: boolean; // Replace-By-Fee
gasLimit?: number;
gasPrice?: number;
gasLimit?: string;
gasPrice?: string;
instantAcceptanceEscrow?: boolean; // BCH
invoiceID?: string;
maxGasFee?: number;
maxGasFee?: string;
multiSendContractAddress?: string;
multisigContractAddress?: string;
multiTx?: boolean; //
nonceAddress?: string; // SOL
priorityFee?: number;
priorityGasFee?: number;
priorityFee?: string;
priorityGasFee?: string;
proposalSignature: string;
space?: any; // ?
tokenAddress?: string;
txType?: number; // or string?
};

export interface SendMaxInfo<NumberType = bigint | string> {
amount: NumberType;
amountBelowFee: NumberType;
fee: NumberType;
feePerKb: NumberType;
utxosBelowFee: number;
inputs?: Array<any>;
size?: NumberType;
utxosAboveMaxSize?: number;
amountAboveMaxSize?: NumberType;
}
Loading