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
41 changes: 8 additions & 33 deletions packages/bitcore-wallet-client/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ for (const network in NetworkChar) { // invert NetworkChar
NetworkChar[NetworkChar[network]] = network;
}

const defaultNumberFormat = 'number'; // 'number' | 'string' | 'hex'

const BASE_URL = 'http://localhost:3232/bws/api';

export class API extends EventEmitter {
Expand Down Expand Up @@ -1407,7 +1405,6 @@ export class API extends EventEmitter {
qs.push(`includeExtendedInfo=${opts.includeExtendedInfo ? '1' : '0'}`);
qs.push(`twoStep=${opts.twoStep ? '1' : '0'}`);
qs.push('serverMessageArray=1');
qs.push('numberFormat=' + defaultNumberFormat); // Only applies to `pendingTxps` in response. TODO apply this to balances as well.

if (opts.tokenAddress) {
qs.push('tokenAddress=' + opts.tokenAddress);
Expand Down Expand Up @@ -1718,8 +1715,7 @@ export class API extends EventEmitter {
/** The transaction proposal object returned by the API#createTxProposal method */
txp: Txp;
/**
* 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.
* Number format for the tx-building numbers (e.g. amounts, nonce, etc.). Omitted by default, which returns the stored values unconverted.
*/
numberFormat?: 'hex' | 'number' | 'string';
},
Expand All @@ -1740,9 +1736,9 @@ export class API extends EventEmitter {
const args = {
proposalSignature: Utils.signMessage(hash, this.credentials.requestPrivKey)
};
const qs = `numberFormat=${opts.numberFormat || defaultNumberFormat}`;
const qs = opts.numberFormat ? `?numberFormat=${opts.numberFormat}` : '';

const url = `/v2/txproposals/${opts.txp.id}/publish?${qs}`;
const url = `/v2/txproposals/${opts.txp.id}/publish${qs}`;
const { body: txp } = await this.request.post<object, PublishedTxp>(url, args);
this._processTxps(txp);
if (cb) { cb(null, txp); }
Expand Down Expand Up @@ -1930,7 +1926,7 @@ export class API extends EventEmitter {
forAirGapped?: boolean;
/** Do not encrypt the public key ring */
doNotEncryptPkr?: boolean;
/** Number format for the tx-building numbers (e.g. amounts, fee, nonce, etc.). Default: 'hex' */
/** Number format for the tx-building numbers (e.g. amounts, fee, nonce, etc.). Omitted by default, which returns the stored values unconverted. */
numberFormat?: 'hex' | 'number' | 'string';
},
/** @deprecated */
Expand All @@ -1945,9 +1941,9 @@ export class API extends EventEmitter {

opts = opts || {};
const { doNotVerify, forAirGapped, doNotEncryptPkr } = opts;
const qs = `numberFormat=${opts.numberFormat || defaultNumberFormat}`;
const qs = opts.numberFormat ? `?numberFormat=${opts.numberFormat}` : '';

const { body: txps } = await this.request.get(`/v2/txproposals?${qs}`);
const { body: txps } = await this.request.get(`/v2/txproposals${qs}`);
this._processTxps(txps);

if (!doNotVerify) {
Expand Down Expand Up @@ -2076,18 +2072,8 @@ export class API extends EventEmitter {
const isLegit = Verifier.checkTxProposal(this.credentials, txp, { paypro });
if (!isLegit) throw new Errors.SERVER_COMPROMISED();

// Determine number format for the API request based on the given txp's values.
// This ensures the server maintains number precision when verifying signatures.
const amt = txp.amount || txp.outputs?.[0]?.amount;
const numberFormat = typeof amt === 'number'
? 'number'
: amt.startsWith('0x')
? 'hex'
: 'string';

const qs = `numberFormat=${numberFormat}`;
baseUrl = baseUrl || '/v2/txproposals/';
const url = `${baseUrl}${txp.id}/signatures?${qs}`;
const url = `${baseUrl}${txp.id}/signatures`;
const args: any = { signatures, nonce: txp.nonce };
const { body: signedTxp } = await this.request.post<object, Txp>(url, args);
this._processTxps(signedTxp);
Expand All @@ -2109,18 +2095,7 @@ export class API extends EventEmitter {
}): Promise<Txp> {
$.checkState(this.credentials?.isComplete(), 'Failed state: this.credentials at <prepareTx()>');

// Determine number format for the API request based on the type of txp.amount.
// This ensures the server maintains number precision when verifying signatures.
const amt = opts.txp.amount || opts.txp.outputs?.[0]?.amount;
const numberFormat = typeof amt === 'number'
? 'number'
: amt.startsWith('0x')
? 'hex'
: 'string';

const qs = `numberFormat=${numberFormat}`;

const url = `/v1/txproposals/${opts.txp.id}/prepare?${qs}`;
const url = `/v1/txproposals/${opts.txp.id}/prepare`;
const { body: txp } = await this.request.post<object, Txp>(url, {});
this._processTxps(txp);
return txp;
Expand Down
1 change: 0 additions & 1 deletion packages/bitcore-wallet-client/src/lib/bulkclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ export class BulkClient extends Request<Array<Credentials>> {
qs.push('twoStep=' + (opts.twoStep ? '1' : '0'));
qs.push('serverMessageArray=1');
qs.push('silentFailure=' + (opts.silentFailure ? '1' : '0'));
qs.push('numberFormat=hex'); // Only applies to `pendingTxps` in response. TODO apply this to balances as well.

const wallets = opts.wallets;
if (wallets) {
Expand Down
2 changes: 1 addition & 1 deletion packages/bitcore-wallet-client/src/lib/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ export class Utils {
if (txp.instantAcceptanceEscrow && txp.escrowAddress) {
t.escrow(
txp.escrowAddress.address,
txp.instantAcceptanceEscrow + txp.fee
Number(txp.instantAcceptanceEscrow) + Number(txp.fee)
);
}

Expand Down
28 changes: 28 additions & 0 deletions packages/bitcore-wallet-client/test/amount-type-divergence.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as chai from 'chai';
import { Transactions, ethers } from '@bitpay-labs/crypto-wallet-core';

chai.should();

describe('Amount type divergence', function() {
const exact = '70000000000000008';
const numeric = Number(exact);
const evm = {
network: 'livenet',
nonce: 5,
gasPrice: 25000000000,
gasLimit: 21000,
recipients: [{ address: '0x1111111111111111111111111111111111111111', amount: exact }]
};

it('should read a number as its printed form for native EVM but as its exact value for ERC20', function() {
numeric.toLocaleString('fullwide', { useGrouping: false }).should.equal('70000000000000010');
BigInt(numeric).toString().should.equal(exact);

const native = Transactions.create({ ...evm, chain: 'ETH', recipients: [{ ...evm.recipients[0], amount: numeric }] });
ethers.Transaction.from(native).value.should.equal(70000000000000010n);

const token = { ...evm, chain: 'ETHERC20', tokenAddress: '0x2222222222222222222222222222222222222222' };
Transactions.create({ ...token, recipients: [{ ...evm.recipients[0], amount: numeric }] })
.should.equal(Transactions.create(token));
});
});
48 changes: 48 additions & 0 deletions packages/bitcore-wallet-client/test/escrow-amount-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as chai from 'chai';
import { BitcoreLibCash } from '@bitpay-labs/crypto-wallet-core';
import { Utils } from '../src/lib/common';
import { Key } from '../src/lib/key';

chai.should();

describe('BCH escrow amount types', function() {
it('should build the same escrow tx from string and number amounts', function() {
const key = new Key({
seedType: 'mnemonic',
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
});
const credentials = key.createCredentials(null, { coin: 'bch', chain: 'bch', network: 'livenet', account: 0, n: 1 });
credentials.addWalletInfo('escrow-wallet', 'escrow wallet', 1, 1, 'copayer');
const address = Utils.deriveAddress('P2PKH', credentials.publicKeyRing, 'm/0/0', 1, 'livenet', 'bch');
const txp: any = {
version: 3,
coin: 'bch',
chain: 'bch',
network: 'livenet',
addressType: 'P2PKH',
requiredSignatures: 1,
inputs: [{
txid: 'ab'.repeat(32),
vout: 0,
satoshis: 3000000,
scriptPubKey: BitcoreLibCash.Script.buildPublicKeyHashOut(address.address).toHex(),
path: address.path
}],
outputs: [{ toAddress: address.address, amount: 1000000 }],
changeAddress: Utils.deriveAddress('P2PKH', credentials.publicKeyRing, 'm/1/0', 1, 'livenet', 'bch'),
outputOrder: [2, 0, 1],
fee: 200,
instantAcceptanceEscrow: 1000
};
txp.escrowAddress = {
...Utils.deriveAddress('P2SH', credentials.publicKeyRing, 'm/1/1', 1, 'livenet', 'bch', txp.inputs),
type: 'P2SH'
};

const fromNumbers = Utils.buildTx(txp).uncheckedSerialize();
txp.instantAcceptanceEscrow = '1000';
txp.fee = '200';

Utils.buildTx(txp).uncheckedSerialize().should.equal(fromNumbers);
});
});
Loading