mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-03 05:55:03 +01:00
chore(suite): autofix newlines
This commit is contained in:
committed by
Peter Sanderson
parent
6d23f67570
commit
c82455e746
@@ -273,10 +273,10 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'prefer-numeric-literals': 'error',
|
||||
"padding-line-between-statements": [
|
||||
"error",
|
||||
{ blankLine: "always", prev: "*", next: "return" },
|
||||
]
|
||||
'padding-line-between-statements': [
|
||||
'error',
|
||||
{ blankLine: 'always', prev: '*', next: 'return' },
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
||||
@@ -95,6 +95,7 @@ export class Analytics<T extends AnalyticsEvent> {
|
||||
console.error(
|
||||
`Unable to report ${data.type}. Analytics is not initialized! Missing: ${listOfMissingFields}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ export const filterTokenTransfers = (
|
||||
const all: (string | null)[] = addresses.map(a => {
|
||||
if (typeof a === 'string') return a;
|
||||
if (typeof a === 'object' && typeof a.address === 'string') return a.address;
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -58,6 +59,7 @@ export const filterTokenTransfers = (
|
||||
(transfer.to && all.indexOf(transfer.to) >= 0)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.map(transfer => {
|
||||
@@ -288,6 +290,7 @@ export const transformTokenInfo = (
|
||||
if (!tokens || !Array.isArray(tokens)) return undefined;
|
||||
const info = tokens.reduce((arr, token) => {
|
||||
if (token.type === 'XPUBAddress') return arr;
|
||||
|
||||
return arr.concat([
|
||||
{
|
||||
...token,
|
||||
@@ -295,6 +298,7 @@ export const transformTokenInfo = (
|
||||
},
|
||||
]);
|
||||
}, [] as TokenInfo[]);
|
||||
|
||||
return info.length > 0 ? info : undefined;
|
||||
};
|
||||
|
||||
@@ -304,6 +308,7 @@ export const transformAddresses = (
|
||||
if (!tokens || !Array.isArray(tokens)) return undefined;
|
||||
const addresses = tokens.reduce((arr, t) => {
|
||||
if (t.type !== 'XPUBAddress') return arr;
|
||||
|
||||
return arr.concat([
|
||||
{
|
||||
address: t.name,
|
||||
|
||||
@@ -37,6 +37,7 @@ export const transformUtxos = (utxos: BlockfrostUtxos[]): Utxo[] => {
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -64,6 +65,7 @@ const getSubtype = (tx: Pick<BlockfrostTransaction, 'txData'>) => {
|
||||
// transaction could both register staking address and delegate stake at the same time. In that case we treat it as "stake registration"
|
||||
return 'stake_registration';
|
||||
}
|
||||
|
||||
return 'stake_deregistration';
|
||||
}
|
||||
|
||||
@@ -90,6 +92,7 @@ export const transformTokenInfo = (
|
||||
if (!tokens || !Array.isArray(tokens)) return undefined;
|
||||
const info = tokens.map(token => {
|
||||
const { assetName } = parseAsset(token.unit);
|
||||
|
||||
return {
|
||||
type: 'BLOCKFROST',
|
||||
name: token.fingerprint!, // this is safe as fingerprint is defined for all tokens except lovelace and lovelace is never included in account.tokens
|
||||
|
||||
@@ -95,6 +95,7 @@ type SplTokenAccount = { account: AccountInfo<SplTokenAccountData>; pubkey: Publ
|
||||
|
||||
const isSplTokenAccount = (tokenAccount: ApiTokenAccount): tokenAccount is SplTokenAccount => {
|
||||
const { parsed } = tokenAccount.account.data;
|
||||
|
||||
return (
|
||||
tokenAccount.account.data.program === 'spl-token' &&
|
||||
'info' in parsed &&
|
||||
@@ -117,6 +118,7 @@ export const transformTokenInfo = (
|
||||
A.filter(isSplTokenAccount),
|
||||
A.map(tokenAccount => {
|
||||
const { info } = tokenAccount.account.data.parsed;
|
||||
|
||||
return {
|
||||
type: 'SPL', // Designation for Solana tokens
|
||||
contract: info.mint,
|
||||
@@ -260,6 +262,7 @@ export const getTargets = (
|
||||
if (txType === 'unknown') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// count in only positive effects, for `sent` tx they gonna be represented as negative, for `recv` as positive
|
||||
return effect.amount.isGreaterThan(0);
|
||||
})
|
||||
@@ -271,6 +274,7 @@ export const getTargets = (
|
||||
amount: effect.amount.abs().toString(),
|
||||
isAccountTarget: effect.address === accountAddress && txType !== 'sent',
|
||||
};
|
||||
|
||||
return target;
|
||||
});
|
||||
|
||||
@@ -383,6 +387,7 @@ export const getDetails = (
|
||||
if (txType === 'self') {
|
||||
vout.push(getVin({ address: accountAddress }, vout.length));
|
||||
}
|
||||
|
||||
return {
|
||||
size: transaction.meta?.computeUnitsConsumed || 0,
|
||||
totalInput: senders
|
||||
@@ -406,6 +411,7 @@ export const getAmount = (
|
||||
if (txType === 'self') {
|
||||
return accountEffect.amount?.abs().toString();
|
||||
}
|
||||
|
||||
return accountEffect.amount.toString();
|
||||
};
|
||||
|
||||
@@ -485,6 +491,7 @@ export const getTokens = (
|
||||
if (isAccountDestination) {
|
||||
return 'recv';
|
||||
}
|
||||
|
||||
return 'sent';
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export const filterTargets = (addresses: Addresses, targets: VinVout[]): VinVout
|
||||
.map(a => {
|
||||
if (typeof a === 'string') return a;
|
||||
if (typeof a === 'object' && typeof a.address === 'string') return a.address;
|
||||
|
||||
return undefined;
|
||||
})
|
||||
.filter(isNotUndefined);
|
||||
@@ -65,5 +66,6 @@ export const sortTxsFromLatest = (transactions: Transaction[]) => {
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
|
||||
return txs;
|
||||
};
|
||||
|
||||
@@ -91,6 +91,7 @@ class BlockchainLink extends TypedEmitter<Events> {
|
||||
this.worker.onmessage = this.onMessage.bind(this);
|
||||
this.worker.onerror = this.onError.bind(this);
|
||||
}
|
||||
|
||||
return this.worker;
|
||||
}
|
||||
|
||||
@@ -99,6 +100,7 @@ class BlockchainLink extends TypedEmitter<Events> {
|
||||
const worker = await this.getWorker();
|
||||
const { promiseId, promise } = this.deferred.create();
|
||||
worker.postMessage({ id: promiseId, ...message });
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -271,6 +273,7 @@ class BlockchainLink extends TypedEmitter<Events> {
|
||||
// eslint-disable-next-line require-await
|
||||
async disconnect(): Promise<boolean> {
|
||||
if (!this.worker) return true;
|
||||
|
||||
return this.sendMessage({
|
||||
type: MESSAGES.DISCONNECT,
|
||||
});
|
||||
@@ -283,6 +286,7 @@ class BlockchainLink extends TypedEmitter<Events> {
|
||||
|
||||
if (data.id === -1) {
|
||||
this.onEvent(data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -349,8 +349,10 @@ const init = (instances: any[]) => {
|
||||
const b = i.blockchain;
|
||||
if (i.selected) {
|
||||
fillValues(i.data);
|
||||
|
||||
return `<option value="${b.name}" selected>${b.name}</option>`;
|
||||
}
|
||||
|
||||
return `<option value="${b.name}">${b.name}</option>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
@@ -8,6 +8,7 @@ export const onClear = () => {
|
||||
|
||||
export const getInputValue = (id: string): string => {
|
||||
const input = document.getElementById(id) as HTMLInputElement;
|
||||
|
||||
return input.value;
|
||||
};
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ export abstract class BaseWebsocket<T extends EventMap> extends TypedEmitter<T &
|
||||
this.options.onSending?.(message);
|
||||
|
||||
ws.send(JSON.stringify(req));
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -142,6 +143,7 @@ export abstract class BaseWebsocket<T extends EventMap> extends TypedEmitter<T &
|
||||
// remove previous subscriptions
|
||||
this.subscriptions.splice(index, 1);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ export abstract class BaseWorker<API> {
|
||||
this.debug('Connected');
|
||||
this.api = api;
|
||||
this.connectPromise = undefined;
|
||||
|
||||
return api;
|
||||
});
|
||||
}
|
||||
@@ -120,6 +121,7 @@ export abstract class BaseWorker<API> {
|
||||
|
||||
return this.tryConnect(url).catch(error => {
|
||||
this.debug('Connection failed', error);
|
||||
|
||||
return this.connectRecursive(rest);
|
||||
});
|
||||
}
|
||||
@@ -142,21 +144,25 @@ export abstract class BaseWorker<API> {
|
||||
this.proxyAgent = data.settings.proxy
|
||||
? SocksProxyAgent(data.settings.proxy)
|
||||
: undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
if (data.type === MESSAGES.CONNECT) {
|
||||
await this.connect();
|
||||
this.post({ id, type: RESPONSES.CONNECT, payload: true });
|
||||
|
||||
return true;
|
||||
}
|
||||
if (data.type === MESSAGES.DISCONNECT) {
|
||||
this.disconnect();
|
||||
this.post({ id, type: RESPONSES.DISCONNECTED, payload: true });
|
||||
|
||||
return true;
|
||||
}
|
||||
if (data.type === MESSAGES.TERMINATE) {
|
||||
this.disconnect();
|
||||
this.cleanup();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type Request<T> = T & Context;
|
||||
const getInfo = async (request: Request<MessageTypes.GetInfo>) => {
|
||||
const api = await request.connect();
|
||||
const info = await api.getServerInfo();
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_INFO,
|
||||
payload: {
|
||||
@@ -30,6 +31,7 @@ const getInfo = async (request: Request<MessageTypes.GetInfo>) => {
|
||||
const getBlockHash = async (request: Request<MessageTypes.GetBlockHash>) => {
|
||||
const api = await request.connect();
|
||||
const info = await api.getBlockHash(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_BLOCK_HASH,
|
||||
payload: info.hash,
|
||||
@@ -39,6 +41,7 @@ const getBlockHash = async (request: Request<MessageTypes.GetBlockHash>) => {
|
||||
const getBlock = async (request: Request<MessageTypes.GetBlock>) => {
|
||||
const api = await request.connect();
|
||||
const info = await api.getBlock(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_BLOCK,
|
||||
payload: info,
|
||||
@@ -49,6 +52,7 @@ const getAccountInfo = async (request: Request<MessageTypes.GetAccountInfo>) =>
|
||||
const { payload } = request;
|
||||
const api = await request.connect();
|
||||
const info = await api.getAccountInfo(payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_ACCOUNT_INFO,
|
||||
payload: utils.transformAccountInfo(info),
|
||||
@@ -59,6 +63,7 @@ const getAccountUtxo = async (request: Request<MessageTypes.GetAccountUtxo>) =>
|
||||
const { payload } = request;
|
||||
const api = await request.connect();
|
||||
const utxos = await api.getAccountUtxo(payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_ACCOUNT_UTXO,
|
||||
payload: utils.transformAccountUtxo(utxos),
|
||||
@@ -71,6 +76,7 @@ const getAccountBalanceHistory = async (
|
||||
const { payload } = request;
|
||||
const api = await request.connect();
|
||||
const history = await api.getAccountBalanceHistory(payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_ACCOUNT_BALANCE_HISTORY,
|
||||
payload: history,
|
||||
@@ -81,6 +87,7 @@ const getCurrentFiatRates = async (request: Request<MessageTypes.GetCurrentFiatR
|
||||
const { payload } = request;
|
||||
const api = await request.connect();
|
||||
const fiatRates = await api.getCurrentFiatRates(payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_CURRENT_FIAT_RATES,
|
||||
payload: fiatRates,
|
||||
@@ -93,6 +100,7 @@ const getFiatRatesForTimestamps = async (
|
||||
const { payload } = request;
|
||||
const api = await request.connect();
|
||||
const { tickers } = await api.getFiatRatesForTimestamps(payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_FIAT_RATES_FOR_TIMESTAMPS,
|
||||
payload: { tickers },
|
||||
@@ -103,6 +111,7 @@ const getFiatRatesTickersList = async (request: Request<MessageTypes.GetFiatRate
|
||||
const { payload } = request;
|
||||
const api = await request.connect();
|
||||
const tickers = await api.getFiatRatesTickersList(payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_FIAT_RATES_TICKERS_LIST,
|
||||
payload: {
|
||||
@@ -116,6 +125,7 @@ const getTransaction = async (request: Request<MessageTypes.GetTransaction>) =>
|
||||
const api = await request.connect();
|
||||
const rawtx = await api.getTransaction(request.payload);
|
||||
const tx = utils.transformTransaction(rawtx);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_TRANSACTION,
|
||||
payload: tx,
|
||||
@@ -126,6 +136,7 @@ const getTransactionHex = async (request: Request<MessageTypes.GetTransactionHex
|
||||
const api = await request.connect();
|
||||
const { hex } = await api.getTransaction(request.payload);
|
||||
if (!hex) throw new CustomError(`Missing hex of ${request.payload}`);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_TRANSACTION_HEX,
|
||||
payload: hex,
|
||||
@@ -135,6 +146,7 @@ const getTransactionHex = async (request: Request<MessageTypes.GetTransactionHex
|
||||
const pushTransaction = async (request: Request<MessageTypes.PushTransaction>) => {
|
||||
const api = await request.connect();
|
||||
const resp = await api.pushTransaction(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.PUSH_TRANSACTION,
|
||||
payload: resp.result,
|
||||
@@ -144,6 +156,7 @@ const pushTransaction = async (request: Request<MessageTypes.PushTransaction>) =
|
||||
const estimateFee = async (request: Request<MessageTypes.EstimateFee>) => {
|
||||
const api = await request.connect();
|
||||
const resp = await api.estimateFee(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.ESTIMATE_FEE,
|
||||
payload: resp,
|
||||
@@ -217,6 +230,7 @@ const subscribeAccounts = async (ctx: Context, accounts: SubscriptionAccountInfo
|
||||
api.on('notification', ev => onTransaction(ctx, ev));
|
||||
state.addSubscription('notification');
|
||||
}
|
||||
|
||||
return api.subscribeAddresses(state.getAddresses());
|
||||
};
|
||||
|
||||
@@ -229,6 +243,7 @@ const subscribeAddresses = async (ctx: Context, addresses: string[]) => {
|
||||
api.on('notification', ev => onTransaction(ctx, ev));
|
||||
state.addSubscription('notification');
|
||||
}
|
||||
|
||||
return api.subscribeAddresses(state.getAddresses());
|
||||
};
|
||||
|
||||
@@ -237,6 +252,7 @@ const subscribeBlock = async (ctx: Context) => {
|
||||
const api = await ctx.connect();
|
||||
ctx.state.addSubscription('block');
|
||||
api.on('block', ev => onNewBlock(ctx, ev));
|
||||
|
||||
return api.subscribeBlock();
|
||||
};
|
||||
|
||||
@@ -246,6 +262,7 @@ const subscribeFiatRates = async (ctx: Context, currency?: string) => {
|
||||
ctx.state.addSubscription('fiatRates');
|
||||
api.on('fiatRates', ev => onNewFiatRates(ctx, ev));
|
||||
}
|
||||
|
||||
return api.subscribeFiatRates(currency);
|
||||
};
|
||||
|
||||
@@ -255,6 +272,7 @@ const subscribeMempool = async (ctx: Context) => {
|
||||
ctx.state.addSubscription('mempool');
|
||||
api.on('mempool', ev => onMempoolTx(ctx, ev));
|
||||
}
|
||||
|
||||
return api.subscribeMempool();
|
||||
};
|
||||
|
||||
@@ -295,8 +313,10 @@ const unsubscribeAccounts = async (
|
||||
// remove listeners
|
||||
api.removeAllListeners('notification');
|
||||
state.removeSubscription('notification');
|
||||
|
||||
return api.unsubscribeAddresses();
|
||||
}
|
||||
|
||||
// subscribe remained addresses
|
||||
return api.subscribeAddresses(subscribed);
|
||||
};
|
||||
@@ -313,8 +333,10 @@ const unsubscribeAddresses = async ({ state, connect }: Context, addresses?: str
|
||||
// remove listeners
|
||||
api.removeAllListeners('notification');
|
||||
state.removeSubscription('notification');
|
||||
|
||||
return api.unsubscribeAddresses();
|
||||
}
|
||||
|
||||
// subscribe remained addresses
|
||||
return api.subscribeAddresses(subscribed);
|
||||
};
|
||||
@@ -324,6 +346,7 @@ const unsubscribeBlock = async ({ state, connect }: Context) => {
|
||||
const api = await connect();
|
||||
api.removeAllListeners('block');
|
||||
state.removeSubscription('block');
|
||||
|
||||
return api.unsubscribeBlock();
|
||||
};
|
||||
|
||||
@@ -332,6 +355,7 @@ const unsubscribeFiatRates = async ({ state, connect }: Context) => {
|
||||
const api = await connect();
|
||||
api.removeAllListeners('fiatRates');
|
||||
state.removeSubscription('fiatRates');
|
||||
|
||||
return api.unsubscribeFiatRates();
|
||||
};
|
||||
|
||||
@@ -340,6 +364,7 @@ const unsubscribeMempool = async ({ state, connect }: Context) => {
|
||||
const api = await connect();
|
||||
api.removeAllListeners('mempool');
|
||||
state.removeSubscription('mempool');
|
||||
|
||||
return api.unsubscribeMempool();
|
||||
};
|
||||
|
||||
|
||||
@@ -144,44 +144,52 @@ export class BlockbookAPI extends BaseWebsocket<BlockbookEvents> {
|
||||
subscribeAddresses(addresses: string[]) {
|
||||
this.removeSubscription('notification');
|
||||
this.addSubscription('notification', result => this.emit('notification', result));
|
||||
|
||||
return this.send('subscribeAddresses', { addresses });
|
||||
}
|
||||
|
||||
unsubscribeAddresses() {
|
||||
const index = this.removeSubscription('notification');
|
||||
|
||||
return index >= 0 ? this.send('unsubscribeAddresses') : { subscribed: false };
|
||||
}
|
||||
|
||||
subscribeBlock() {
|
||||
this.removeSubscription('block');
|
||||
this.addSubscription('block', result => this.emit('block', result));
|
||||
|
||||
return this.send('subscribeNewBlock');
|
||||
}
|
||||
|
||||
unsubscribeBlock() {
|
||||
const index = this.removeSubscription('block');
|
||||
|
||||
return index >= 0 ? this.send('unsubscribeNewBlock') : { subscribed: false };
|
||||
}
|
||||
|
||||
subscribeFiatRates(currency?: string) {
|
||||
this.removeSubscription('fiatRates');
|
||||
this.addSubscription('fiatRates', result => this.emit('fiatRates', result));
|
||||
|
||||
return this.send('subscribeFiatRates', { currency });
|
||||
}
|
||||
|
||||
unsubscribeFiatRates() {
|
||||
const index = this.removeSubscription('fiatRates');
|
||||
|
||||
return index >= 0 ? this.send('unsubscribeFiatRates') : { subscribed: false };
|
||||
}
|
||||
|
||||
subscribeMempool() {
|
||||
this.removeSubscription('mempool');
|
||||
this.addSubscription('mempool', result => this.emit('mempool', result));
|
||||
|
||||
return this.send('subscribeNewTransaction');
|
||||
}
|
||||
|
||||
unsubscribeMempool() {
|
||||
const index = this.removeSubscription('mempool');
|
||||
|
||||
return index >= 0 ? this.send('unsubscribeNewTransaction') : { subscribed: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ type Request<T> = T & Context;
|
||||
const getInfo = async (request: Request<MessageTypes.GetInfo>) => {
|
||||
const api = await request.connect();
|
||||
const info = await api.getServerInfo();
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_INFO,
|
||||
payload: {
|
||||
@@ -33,6 +34,7 @@ const getInfo = async (request: Request<MessageTypes.GetInfo>) => {
|
||||
const getBlockHash = async (request: Request<MessageTypes.GetBlockHash>) => {
|
||||
const api = await request.connect();
|
||||
const blockMessage = await api.getBlockHash(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_BLOCK_HASH,
|
||||
payload: blockMessage.hash,
|
||||
@@ -44,6 +46,7 @@ const getAccountBalanceHistory = async (
|
||||
) => {
|
||||
const socket = await request.connect();
|
||||
const history = await socket.getAccountBalanceHistory(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_ACCOUNT_BALANCE_HISTORY,
|
||||
payload: history,
|
||||
@@ -54,6 +57,7 @@ const getTransaction = async (request: Request<MessageTypes.GetTransaction>) =>
|
||||
const api = await request.connect();
|
||||
const txData = await api.getTransaction(request.payload);
|
||||
const tx = transformTransaction({ txData });
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_TRANSACTION,
|
||||
payload: tx,
|
||||
@@ -76,6 +80,7 @@ const estimateFee = async (request: Request<MessageTypes.EstimateFee>) => {
|
||||
const pushTransaction = async (request: Request<MessageTypes.PushTransaction>) => {
|
||||
const api = await request.connect();
|
||||
const payload = await api.pushTransaction(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.PUSH_TRANSACTION,
|
||||
payload,
|
||||
@@ -85,6 +90,7 @@ const pushTransaction = async (request: Request<MessageTypes.PushTransaction>) =
|
||||
const getAccountInfo = async (request: Request<MessageTypes.GetAccountInfo>) => {
|
||||
const api = await request.connect();
|
||||
const info = await api.getAccountInfo(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_ACCOUNT_INFO,
|
||||
payload: transformAccountInfo(info),
|
||||
@@ -94,6 +100,7 @@ const getAccountInfo = async (request: Request<MessageTypes.GetAccountInfo>) =>
|
||||
const getAccountUtxo = async (request: Request<MessageTypes.GetAccountUtxo>) => {
|
||||
const api = await request.connect();
|
||||
const utxos = await api.getAccountUtxo(request.payload);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_ACCOUNT_UTXO,
|
||||
payload: transformUtxos(utxos),
|
||||
@@ -138,6 +145,7 @@ const subscribeBlock = async (ctx: Context) => {
|
||||
const api = await ctx.connect();
|
||||
ctx.state.addSubscription('block');
|
||||
api.on('block', ev => onNewBlock(ctx, ev));
|
||||
|
||||
return api.subscribeBlock();
|
||||
};
|
||||
|
||||
@@ -149,6 +157,7 @@ const subscribeAccounts = async (ctx: Context, accounts: SubscriptionAccountInfo
|
||||
api.on('notification', ev => onTransaction(ctx, ev));
|
||||
state.addSubscription('notification');
|
||||
}
|
||||
|
||||
return api.subscribeAddresses(state.getAddresses());
|
||||
};
|
||||
|
||||
@@ -160,6 +169,7 @@ const subscribeAddresses = async (ctx: Context, addresses: string[]) => {
|
||||
api.on('notification', ev => onTransaction(ctx, ev));
|
||||
state.addSubscription('notification');
|
||||
}
|
||||
|
||||
return api.subscribeAddresses(state.getAddresses());
|
||||
};
|
||||
|
||||
@@ -188,6 +198,7 @@ const unsubscribeBlock = async ({ state, connect }: Context) => {
|
||||
const api = await connect();
|
||||
api.removeAllListeners('block');
|
||||
state.removeSubscription('block');
|
||||
|
||||
return api.unsubscribeBlock();
|
||||
};
|
||||
|
||||
@@ -204,8 +215,10 @@ const unsubscribeAccounts = async (
|
||||
// remove listeners
|
||||
api.removeAllListeners('notification');
|
||||
state.removeSubscription('notification');
|
||||
|
||||
return api.unsubscribeAddresses();
|
||||
}
|
||||
|
||||
// subscribe remained addresses
|
||||
return api.subscribeAddresses(subscribed);
|
||||
};
|
||||
@@ -222,8 +235,10 @@ const unsubscribeAddresses = async ({ state, connect }: Context, addresses?: str
|
||||
// remove listeners
|
||||
socket.removeAllListeners('notification');
|
||||
state.removeSubscription('notification');
|
||||
|
||||
return socket.unsubscribeAddresses();
|
||||
}
|
||||
|
||||
// subscribe remained addresses
|
||||
return socket.subscribeAddresses(subscribed);
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ interface BlockfrostEvents {
|
||||
export class BlockfrostAPI extends BaseWebsocket<BlockfrostEvents> {
|
||||
protected createWebsocket() {
|
||||
const { url } = this.options;
|
||||
|
||||
// options are not used in web builds (see ./src/utils/ws)
|
||||
return new WebSocket(url, {
|
||||
agent: this.options.agent,
|
||||
@@ -68,22 +69,26 @@ export class BlockfrostAPI extends BaseWebsocket<BlockfrostEvents> {
|
||||
subscribeBlock() {
|
||||
this.removeSubscription('block');
|
||||
this.addSubscription('block', result => this.emit('block', result));
|
||||
|
||||
return this.send('SUBSCRIBE_BLOCK');
|
||||
}
|
||||
|
||||
subscribeAddresses(addresses: string[]) {
|
||||
this.removeSubscription('notification');
|
||||
this.addSubscription('notification', result => this.emit('notification', result));
|
||||
|
||||
return this.send('SUBSCRIBE_ADDRESS', { addresses });
|
||||
}
|
||||
|
||||
unsubscribeBlock() {
|
||||
const index = this.removeSubscription('block');
|
||||
|
||||
return index >= 0 ? this.send('UNSUBSCRIBE_BLOCK') : { subscribed: false };
|
||||
}
|
||||
|
||||
unsubscribeAddresses() {
|
||||
const index = this.removeSubscription('notification');
|
||||
|
||||
return index >= 0 ? this.send('UNSUBSCRIBE_ADDRESS') : { subscribed: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export class BatchingJsonRpcClient extends JsonRpcClient {
|
||||
protected send(message: string) {
|
||||
if (this.batchingDisabled) {
|
||||
super.send(message);
|
||||
|
||||
return;
|
||||
}
|
||||
const { queue } = this;
|
||||
|
||||
@@ -31,11 +31,13 @@ export class CachingElectrumClient extends ElectrumClient {
|
||||
const [cachedStatus, cachedResponse] = cached;
|
||||
if (cachedStatus === status) {
|
||||
this.cached++;
|
||||
|
||||
return cachedResponse;
|
||||
}
|
||||
}
|
||||
const response = await super.request(method, ...params);
|
||||
this.cache[descriptor] = [status, response];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -48,6 +50,7 @@ export class CachingElectrumClient extends ElectrumClient {
|
||||
// Subscribe to the new scripthash and store the status
|
||||
const newStatus = await super.request('blockchain.scripthash.subscribe', scripthash);
|
||||
this.statuses[scripthash] = newStatus;
|
||||
|
||||
return newStatus;
|
||||
}
|
||||
|
||||
@@ -59,25 +62,30 @@ export class CachingElectrumClient extends ElectrumClient {
|
||||
case 'blockchain.scripthash.listunspent': {
|
||||
const [scripthash] = params;
|
||||
const status = await this.trySubscribe(scripthash);
|
||||
|
||||
return this.cacheRequest(status, method, params);
|
||||
}
|
||||
case 'blockchain.transaction.get': {
|
||||
const curBlock = this.lastBlock?.hex;
|
||||
if (curBlock === undefined) break;
|
||||
|
||||
return this.cacheRequest(curBlock, method, params);
|
||||
}
|
||||
case 'blockchain.scripthash.subscribe': {
|
||||
const [scripthash] = params;
|
||||
|
||||
return this.trySubscribe(scripthash);
|
||||
}
|
||||
case 'blockchain.scripthash.unsubscribe': {
|
||||
const [scripthash] = params;
|
||||
delete this.statuses[scripthash];
|
||||
|
||||
return super.request(method, ...params);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return super.request(method, ...params);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ export class ElectrumClient extends BatchingJsonRpcClient implements ElectrumAPI
|
||||
|
||||
request(method: string, ...params: any[]) {
|
||||
this.timeLastCall = new Date().getTime();
|
||||
|
||||
return super.request(method, ...params);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ const onRequest = async <T extends Message>(
|
||||
// @ts-expect-error
|
||||
|
||||
const { method, params } = request.payload;
|
||||
|
||||
return client
|
||||
.request(method, ...params)
|
||||
.then((res: any) => ({ type: method, payload: res }));
|
||||
|
||||
@@ -29,6 +29,7 @@ export const blockListener = (worker: BaseWorker<ElectrumAPI>) => {
|
||||
state.addSubscription('block');
|
||||
api().on('blockchain.headers.subscribe', onBlock);
|
||||
}
|
||||
|
||||
return { subscribed: true };
|
||||
};
|
||||
|
||||
@@ -37,6 +38,7 @@ export const blockListener = (worker: BaseWorker<ElectrumAPI>) => {
|
||||
api().off('blockchain.headers.subscribe', onBlock);
|
||||
state.removeSubscription('block');
|
||||
}
|
||||
|
||||
return { subscribed: false };
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ const mostRecent = (previous: HistoryTx | undefined, current: HistoryTx) => {
|
||||
if (current.height === -1) return current;
|
||||
if (previous.height === 0) return previous;
|
||||
if (current.height === 0) return current;
|
||||
|
||||
return previous.height >= current.height ? previous : current;
|
||||
};
|
||||
|
||||
@@ -72,6 +73,7 @@ export const txListener = (worker: BaseWorker<ElectrumAPI>) => {
|
||||
api().request('blockchain.scripthash.subscribe', scripthash),
|
||||
),
|
||||
);
|
||||
|
||||
return { subscribed: true };
|
||||
};
|
||||
|
||||
@@ -93,6 +95,7 @@ export const txListener = (worker: BaseWorker<ElectrumAPI>) => {
|
||||
api().request('blockchain.scripthash.unsubscribe', scripthash),
|
||||
),
|
||||
);
|
||||
|
||||
return { subscribed: false };
|
||||
};
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ const aggregateTransactions = (txs: (Transaction & { blockTime: number })[], gro
|
||||
});
|
||||
i = j;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ const getBalances =
|
||||
confirmed: 0,
|
||||
unconfirmed: 0,
|
||||
};
|
||||
|
||||
return {
|
||||
address,
|
||||
path,
|
||||
|
||||
@@ -35,6 +35,7 @@ const getAccountUtxo: Api<Req, Res> = async (client, descriptor) => {
|
||||
|
||||
if (parsed.valid) {
|
||||
const utxos = await client.request('blockchain.scripthash.listunspent', parsed.scripthash);
|
||||
|
||||
return utxos.map(transformUtxo(height));
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ const getAccountUtxo: Api<Req, Res> = async (client, descriptor) => {
|
||||
.then(utxos => utxos.map(transformUtxo(height, { address, path }))),
|
||||
),
|
||||
).then(res => res.flat());
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { GetBlockHash as Res } from '@trezor/blockchain-link-types/lib/resp
|
||||
|
||||
const getBlockHash: Api<Req, Res> = async (client, payload) => {
|
||||
const blockheader = await client.request('blockchain.block.header', payload);
|
||||
|
||||
return blockheaderToBlockhash(blockheader);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const getInfo: Api<Req, Res> = client => {
|
||||
block: { hex, height },
|
||||
version: [_name, version],
|
||||
} = client.getInfo() || throwError('Client not initialized');
|
||||
|
||||
return Promise.resolve({
|
||||
url,
|
||||
version,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { GetTransaction as Res } from '@trezor/blockchain-link-types/lib/re
|
||||
|
||||
const getTransaction: Api<Req, Res> = async (client, payload) => {
|
||||
const [tx] = await getTransactions(client, [{ tx_hash: payload, height: -1 }]);
|
||||
|
||||
return transformTransaction(tx);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { PushTransaction as Res } from '@trezor/blockchain-link-types/lib/r
|
||||
|
||||
const pushTransaction: Api<Req, Res> = async (client, payload) => {
|
||||
const res = await client.request('blockchain.transaction.broadcast', payload);
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export class TcpSocket extends SocketBase {
|
||||
const socket = new TCPSocket();
|
||||
this.configureSocket(socket);
|
||||
this.bindSocket(socket, listener);
|
||||
|
||||
return new Promise<TCPSocket>((resolve, reject) => {
|
||||
const errorHandler = (err: Error) => reject(err);
|
||||
socket.on('error', errorHandler);
|
||||
|
||||
@@ -7,6 +7,7 @@ export class TlsSocket extends SocketBase {
|
||||
const socket = new TLSSocket(null as any /* TODO omg why? */);
|
||||
this.configureSocket(socket);
|
||||
this.bindSocket(socket, listener);
|
||||
|
||||
return new Promise<TLSSocket>((resolve, reject) => {
|
||||
const errorHandler = (err: Error) => reject(err);
|
||||
socket.on('error', errorHandler);
|
||||
|
||||
@@ -25,6 +25,7 @@ export class TorSocket extends SocketBase {
|
||||
listener.onConnect();
|
||||
this.configureSocket(socket);
|
||||
this.bindSocket(socket, listener);
|
||||
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ export const createAddressManager = (getNetwork: () => Network | undefined) => {
|
||||
([_acc, { change, unused, used }]) =>
|
||||
!!change.concat(used, unused).find(ad => ad.address === address),
|
||||
) || [];
|
||||
|
||||
return {
|
||||
descriptor: account || address,
|
||||
addresses,
|
||||
|
||||
@@ -14,6 +14,7 @@ export const discoverAddress =
|
||||
async ({ address, path }: { address: string; path: string }): Promise<AddressHistory> => {
|
||||
const scripthash = addressToScripthash(address, client.getInfo()?.network);
|
||||
const history = await client.request('blockchain.scripthash.get_history', scripthash);
|
||||
|
||||
return {
|
||||
address,
|
||||
scripthash,
|
||||
|
||||
@@ -12,18 +12,21 @@ import type {
|
||||
|
||||
const transformOpReturn = (hex: string) => {
|
||||
const [, _len, data] = hex.match(/^6a(?:4c)?([0-9a-f]{2})([0-9a-f]*)$/i) ?? [];
|
||||
|
||||
return data ? `OP_RETURN (${Buffer.from(data, 'hex').toString('ascii')})` : undefined;
|
||||
};
|
||||
|
||||
const parseAddresses = ({ address, addresses, type, hex }: TxOut['scriptPubKey']) => {
|
||||
if (type === 'nulldata') {
|
||||
const opReturn = transformOpReturn(hex);
|
||||
|
||||
return {
|
||||
addresses: opReturn ? [opReturn] : [],
|
||||
isAddress: false,
|
||||
};
|
||||
}
|
||||
const addrs = !address ? addresses || [] : [address];
|
||||
|
||||
return {
|
||||
addresses: addrs,
|
||||
isAddress: addrs.length === 1,
|
||||
@@ -56,6 +59,7 @@ const formatTransaction =
|
||||
const valueIn = vinRegular
|
||||
.map(({ txid, vout }) => getVout(txid, vout).value)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
|
||||
return {
|
||||
txid,
|
||||
hex,
|
||||
|
||||
@@ -5,17 +5,20 @@ export const btcToSat = (btc: number) => Math.round(100000000 * btc).toString();
|
||||
export const addressToScripthash = (address: string, network?: Network) => {
|
||||
const script = a.toOutputScript(address, network);
|
||||
const scripthash = c.sha256(script).reverse().toString('hex');
|
||||
|
||||
return scripthash;
|
||||
};
|
||||
|
||||
export const scriptToScripthash = (hex: string) => {
|
||||
const buffer = Buffer.from(hex, 'hex');
|
||||
|
||||
return c.sha256(buffer).reverse().toString('hex');
|
||||
};
|
||||
|
||||
export const blockheaderToBlockhash = (header: string) => {
|
||||
const buffer = Buffer.from(header, 'hex');
|
||||
const hash = c.hash256(buffer).reverse().toString('hex');
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,8 +25,10 @@ const transformError = (error: any) => {
|
||||
if (error.data) {
|
||||
return new CustomError(code, `${error.name} ${error.data.error_message}`);
|
||||
}
|
||||
|
||||
return new CustomError(code, error.toString());
|
||||
}
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
@@ -55,6 +57,7 @@ const getMempoolAccountInfo = async (api: RippleAPI, account: string) => {
|
||||
ledger_index: 'current',
|
||||
queue: true,
|
||||
});
|
||||
|
||||
return {
|
||||
xrpBalance: info.account_data.Balance,
|
||||
sequence: info.account_data.Sequence,
|
||||
@@ -176,6 +179,7 @@ const getTransaction = async ({ connect, payload }: Request<MessageTypes.GetTran
|
||||
const api = await connect();
|
||||
const rawtx = await api.request('tx', { transaction: payload, binary: false });
|
||||
const tx = utils.transformTransaction(rawtx);
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_TRANSACTION,
|
||||
payload: tx,
|
||||
@@ -211,6 +215,7 @@ const estimateFee = async (request: Request<MessageTypes.EstimateFee>) => {
|
||||
request.payload && Array.isArray(request.payload.blocks)
|
||||
? request.payload.blocks.map(() => ({ feePerUnit: drops }))
|
||||
: [{ feePerUnit: drops }];
|
||||
|
||||
return {
|
||||
type: RESPONSES.ESTIMATE_FEE,
|
||||
payload,
|
||||
@@ -274,6 +279,7 @@ const subscribeAccounts = async (ctx: Context, accounts: SubscriptionAccountInfo
|
||||
accounts_proposed: uniqueAddresses,
|
||||
});
|
||||
}
|
||||
|
||||
return { subscribed: state.getAddresses().length > 0 };
|
||||
};
|
||||
|
||||
@@ -298,6 +304,7 @@ const subscribeAddresses = async (ctx: Context, addresses: string[]) => {
|
||||
|
||||
await api.request('subscribe', request);
|
||||
}
|
||||
|
||||
return { subscribed: state.getAddresses().length > 0 };
|
||||
};
|
||||
|
||||
@@ -307,6 +314,7 @@ const subscribeBlock = async (ctx: Context) => {
|
||||
api.on('ledger', ev => onNewBlock(ctx, ev));
|
||||
ctx.state.addSubscription('ledger');
|
||||
}
|
||||
|
||||
return { subscribed: true };
|
||||
};
|
||||
|
||||
@@ -323,6 +331,7 @@ const subscribe = async (request: Request<MessageTypes.Subscribe>) => {
|
||||
} else {
|
||||
throw new CustomError('invalid_param', '+type');
|
||||
}
|
||||
|
||||
return {
|
||||
type: RESPONSES.SUBSCRIBE,
|
||||
payload: response,
|
||||
@@ -458,6 +467,7 @@ class RippleWorker extends BaseWorker<RippleAPI> {
|
||||
});
|
||||
|
||||
this.post({ id: -1, type: RESPONSES.CONNECTED });
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ const getAllSignatures = async (
|
||||
keepFetching = signatures.length === limit;
|
||||
allSignatures = [...allSignatures, ...signatures];
|
||||
}
|
||||
|
||||
return allSignatures;
|
||||
};
|
||||
|
||||
@@ -71,6 +72,7 @@ const fetchTransactionPage = async (
|
||||
resultArray[chunkIndex] = []; // start a new chunk
|
||||
}
|
||||
resultArray[chunkIndex].push(item);
|
||||
|
||||
return resultArray;
|
||||
}, [] as string[][]);
|
||||
|
||||
@@ -93,6 +95,7 @@ const pushTransaction = async (request: Request<MessageTypes.PushTransaction>) =
|
||||
const rawTx = request.payload.startsWith('0x') ? request.payload.slice(2) : request.payload;
|
||||
const api = await request.connect();
|
||||
const payload = await api.sendRawTransaction(Buffer.from(rawTx, 'hex'));
|
||||
|
||||
return {
|
||||
type: RESPONSES.PUSH_TRANSACTION,
|
||||
payload,
|
||||
@@ -231,6 +234,7 @@ const getInfo = async (request: Request<MessageTypes.GetInfo>) => {
|
||||
version: (await api.getVersion())['solana-core'],
|
||||
decimals: 9,
|
||||
};
|
||||
|
||||
return {
|
||||
type: RESPONSES.GET_INFO,
|
||||
payload: { ...serverInfo },
|
||||
@@ -394,6 +398,7 @@ const subscribeAccounts = async (
|
||||
});
|
||||
state.addAccounts([{ ...a, subscriptionId }]);
|
||||
});
|
||||
|
||||
return { subscribed: newAccounts.length > 0 };
|
||||
};
|
||||
|
||||
@@ -438,6 +443,7 @@ const subscribe = async (request: Request<MessageTypes.Subscribe>) => {
|
||||
default:
|
||||
throw new CustomError('worker_unknown_request', `+${request.type}`);
|
||||
}
|
||||
|
||||
return {
|
||||
type: RESPONSES.SUBSCRIBE,
|
||||
payload: response,
|
||||
@@ -456,6 +462,7 @@ const unsubscribe = (request: Request<MessageTypes.Unsubscribe>) => {
|
||||
default:
|
||||
throw new CustomError('worker_unknown_request', `+${request.type}`);
|
||||
}
|
||||
|
||||
return {
|
||||
type: RESPONSES.UNSUBSCRIBE,
|
||||
payload: { subscribed: request.state.getAccounts().length > 0 },
|
||||
@@ -489,6 +496,7 @@ class SolanaWorker extends BaseWorker<SolanaAPI> {
|
||||
tryConnect(url: string): Promise<SolanaAPI> {
|
||||
const api = new Connection(url, { wsEndpoint: url.replace('https', 'wss') });
|
||||
this.post({ id: -1, type: RESPONSES.CONNECTED });
|
||||
|
||||
return Promise.resolve(api);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,12 @@ export class WorkerState {
|
||||
private validateAddresses(addr: string[]) {
|
||||
if (!Array.isArray(addr)) throw new CustomError('invalid_param', '+addresses');
|
||||
const seen: string[] = [];
|
||||
|
||||
return addr.filter(a => {
|
||||
if (typeof a !== 'string') return false;
|
||||
if (seen.indexOf(a) >= 0) return false;
|
||||
seen.push(a);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -25,6 +27,7 @@ export class WorkerState {
|
||||
addAddresses(addr: string[]) {
|
||||
const unique = this.validateAddresses(addr).filter(a => this.addresses.indexOf(a) < 0);
|
||||
this.addresses = this.addresses.concat(unique);
|
||||
|
||||
return unique;
|
||||
}
|
||||
|
||||
@@ -35,18 +38,22 @@ export class WorkerState {
|
||||
removeAddresses(addr: string[]) {
|
||||
const unique = this.validateAddresses(addr);
|
||||
this.addresses = this.addresses.filter(a => unique.indexOf(a) < 0);
|
||||
|
||||
return this.addresses;
|
||||
}
|
||||
|
||||
private validateAccounts(acc: SubscriptionAccountInfo[]): SubscriptionAccountInfo[] {
|
||||
if (!Array.isArray(acc)) throw new CustomError('invalid_param', '+accounts');
|
||||
const seen: string[] = [];
|
||||
|
||||
return acc.filter(a => {
|
||||
if (a && typeof a === 'object' && typeof a.descriptor === 'string') {
|
||||
if (seen.indexOf(a.descriptor) >= 0) return false;
|
||||
seen.push(a.descriptor);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -54,8 +61,10 @@ export class WorkerState {
|
||||
private getAccountAddresses(acc: SubscriptionAccountInfo) {
|
||||
if (acc.addresses) {
|
||||
const { change, used, unused } = acc.addresses;
|
||||
|
||||
return change.concat(used, unused).map(a => a.address);
|
||||
}
|
||||
|
||||
return [acc.descriptor];
|
||||
}
|
||||
|
||||
@@ -68,6 +77,7 @@ export class WorkerState {
|
||||
[] as string[],
|
||||
);
|
||||
this.addAddresses(addresses);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@@ -80,6 +90,7 @@ export class WorkerState {
|
||||
if (used.find(ad => ad.address === address)) return true;
|
||||
if (unused.find(ad => ad.address === address)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -99,6 +110,7 @@ export class WorkerState {
|
||||
);
|
||||
this.accounts = this.accounts.filter(a => accountsToRemove.indexOf(a) < 0);
|
||||
this.removeAddresses(addressesToRemove);
|
||||
|
||||
return this.accounts;
|
||||
}
|
||||
|
||||
@@ -130,6 +142,7 @@ export class WorkerState {
|
||||
if (obj[key] && typeof obj[key] === 'object') this.removeEmpty(obj[key]);
|
||||
else if (obj[key] === undefined) delete obj[key];
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export const prioritizeEndpoints = (urls: string[]) =>
|
||||
} else if (hostname?.endsWith('.onion')) {
|
||||
priority += 1;
|
||||
}
|
||||
|
||||
return [url, priority];
|
||||
})
|
||||
.sort(([, a], [, b]) => b - a)
|
||||
|
||||
@@ -17,6 +17,7 @@ export const rippleWorkerFactory = () => {
|
||||
// require('../../../lib/workers/ripple/index.js');
|
||||
// });
|
||||
}
|
||||
|
||||
return new Worker('./build/web/ripple-worker.js');
|
||||
};
|
||||
|
||||
@@ -34,6 +35,7 @@ export const blockbookWorkerFactory = () => {
|
||||
// require('../../../lib/workers/blockbook/index.js');
|
||||
// });
|
||||
}
|
||||
|
||||
return new Worker('./build/web/blockbook-worker.js');
|
||||
};
|
||||
|
||||
@@ -47,6 +49,7 @@ export const blockfrostWorkerFactory = () => {
|
||||
});
|
||||
// return new TinyWorker('./build/module/blockfrost-worker.js');
|
||||
}
|
||||
|
||||
return new Worker('./build/web/blockfrost-worker.js');
|
||||
};
|
||||
|
||||
|
||||
@@ -112,8 +112,10 @@ describe('Worker', () => {
|
||||
const blob = new Blob([js], {
|
||||
type: 'application/javascript',
|
||||
});
|
||||
|
||||
return new Worker(URL.createObjectURL(blob));
|
||||
}
|
||||
|
||||
return new TinyWorker(() => {
|
||||
setTimeout(() => {
|
||||
// @ts-expect-error self is not typed
|
||||
@@ -156,8 +158,10 @@ describe('Worker', () => {
|
||||
const blob = new Blob([js], {
|
||||
type: 'application/javascript',
|
||||
});
|
||||
|
||||
return new Worker(URL.createObjectURL(blob));
|
||||
}
|
||||
|
||||
return new TinyWorker(() => {
|
||||
self.onmessage = () => {
|
||||
// @ts-expect-error undefined "x"
|
||||
|
||||
@@ -46,6 +46,7 @@ export class CoinjoinAddressController {
|
||||
|
||||
private deriveMore(type: 'receive' | 'change', from: number, count: number) {
|
||||
const prederived = this.cache?.[`${type}Prederived`];
|
||||
|
||||
return deriveAddresses(prederived, this.xpub, type, from, count, this.network).map(
|
||||
({ address, path }) => ({
|
||||
address,
|
||||
|
||||
@@ -84,6 +84,7 @@ export class CoinjoinBackend extends TypedEmitter<Events> {
|
||||
cache,
|
||||
network: this.network,
|
||||
});
|
||||
|
||||
return Promise.resolve(accountInfo);
|
||||
}
|
||||
|
||||
@@ -122,6 +123,7 @@ export class CoinjoinBackend extends TypedEmitter<Events> {
|
||||
|
||||
if (addressFirstPage.txs === 0) {
|
||||
const networkInfo = await this.client.fetchNetworkInfo();
|
||||
|
||||
return {
|
||||
blockHash: networkInfo.bestHash,
|
||||
blockHeight: networkInfo.bestHeight,
|
||||
@@ -150,6 +152,7 @@ export class CoinjoinBackend extends TypedEmitter<Events> {
|
||||
|
||||
private getLogger(): Logger {
|
||||
const emit = (level: LogLevel) => (payload: string) => this.emit('log', { level, payload });
|
||||
|
||||
return {
|
||||
debug: emit('debug'),
|
||||
info: emit('info'),
|
||||
|
||||
@@ -50,6 +50,7 @@ export class CoinjoinBackendClient {
|
||||
|
||||
fetchBlock(height: number, options?: RequestOptions): Promise<BlockbookBlock> {
|
||||
const identity = this.identitiesBlockbook[height & 0x3]; // Works only when identities.length === 4
|
||||
|
||||
return this.getBlockbookApi(api => api.getBlock(height), { identity, ...options });
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ export class CoinjoinBackendClient {
|
||||
fetchTransaction(txid: string, options?: RequestOptions): Promise<BlockbookTransaction> {
|
||||
const lastCharCode = txid.charCodeAt(txid.length - 1);
|
||||
const identity = this.identitiesBlockbook[lastCharCode & 0x3]; // Works only when identities.length === 4
|
||||
|
||||
return this.getBlockbookApi(api => api.getTransaction(txid), { identity, ...options });
|
||||
}
|
||||
|
||||
@@ -85,8 +87,10 @@ export class CoinjoinBackendClient {
|
||||
if (!blockFiltersBatch.length) return { status: 'up-to-date' };
|
||||
const filters = blockFiltersBatch.map(item => {
|
||||
const [blockHeight, blockHash, filter] = item.split(':');
|
||||
|
||||
return { blockHeight: Number(blockHeight), blockHash, filter };
|
||||
});
|
||||
|
||||
return { status: 'ok', filters, ...rest };
|
||||
})
|
||||
.catch<BlockFilterResponse>(error => {
|
||||
@@ -134,6 +138,7 @@ export class CoinjoinBackendClient {
|
||||
newApi = await this.getBlockbookApi(api => api);
|
||||
} catch {
|
||||
this.emitter.emit('mempoolDisconnected');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,6 +190,7 @@ export class CoinjoinBackendClient {
|
||||
{ identity, ...options }: RequestOptions = {},
|
||||
): Promise<T> {
|
||||
let preferOnion = true;
|
||||
|
||||
return scheduleAction(
|
||||
async () => {
|
||||
const urlIndex = this.blockbookRequestId++ % this.blockbookUrls.length;
|
||||
@@ -203,6 +209,7 @@ export class CoinjoinBackendClient {
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
return callbackFn(api);
|
||||
},
|
||||
{ attempts: 3, timeout: HTTP_REQUEST_TIMEOUT, gap: HTTP_REQUEST_GAP, ...options },
|
||||
|
||||
@@ -128,6 +128,7 @@ export class CoinjoinMempoolController {
|
||||
),
|
||||
);
|
||||
this.lastPurge = new Date().getTime();
|
||||
|
||||
return [...this.mempool.values()];
|
||||
}
|
||||
|
||||
@@ -160,6 +161,7 @@ export class CoinjoinMempoolController {
|
||||
}
|
||||
|
||||
this.lastPurge = new Date().getTime();
|
||||
|
||||
return Array.from(set, txid => this.mempool.get(txid)!);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ export class CoinjoinWebsocketController {
|
||||
this.logger?.debug(`WS CLOSED ${socketId}`);
|
||||
});
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ export const deriveAddresses = (
|
||||
const derived = countNew
|
||||
? deriveNewAddresses(descriptor, type, fromNew, countNew, network)
|
||||
: [];
|
||||
|
||||
return prederived.slice(fromPrederived, fromPrederived + countPrederived).concat(derived);
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const createFilter = (data: Buffer, { P = P_DEFAULT, M = M_DEFAULT }) => {
|
||||
const filter = Golomb.fromNBytes(P, data);
|
||||
// In golomb package, M is hardcoded to 784931. With custom value, m must be calculated separately (as M * n).
|
||||
filter.m = new U64(M).mul(new U64(filter.n));
|
||||
|
||||
return filter;
|
||||
};
|
||||
|
||||
@@ -34,6 +35,7 @@ export const getFilter = (filterHex: string, { P, M, key }: FilterParams = {}) =
|
||||
if (!filterHex) return () => false;
|
||||
const filter = createFilter(Buffer.from(filterHex, 'hex'), { P, M });
|
||||
const keyBuffer = key ? Buffer.from(key, 'hex').subarray(0, KEY_SIZE) : ZERO_KEY;
|
||||
|
||||
return (script: Buffer) => filter.match(keyBuffer, script);
|
||||
};
|
||||
|
||||
@@ -41,5 +43,6 @@ export const getMultiFilter = (filterHex: string, { P, M, key }: FilterParams =
|
||||
if (!filterHex) return () => false;
|
||||
const filter = createFilter(Buffer.from(filterHex, 'hex'), { P, M });
|
||||
const keyBuffer = key ? Buffer.from(key, 'hex').subarray(0, KEY_SIZE) : ZERO_KEY;
|
||||
|
||||
return (scripts: Buffer[]) => !!scripts.length && filter.matchAny(keyBuffer, scripts);
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@ const enhanceAddress =
|
||||
const txs = transactions.filter(tx => doesTxContainAddress(address)(tx.details));
|
||||
const sent = sumAddressValues(txs, address, tx => tx.details.vin);
|
||||
const received = sumAddressValues(txs, address, tx => tx.details.vout);
|
||||
|
||||
return {
|
||||
address,
|
||||
path,
|
||||
|
||||
@@ -22,6 +22,7 @@ const getHeightData = (tx: Transaction) =>
|
||||
|
||||
const getAddressData = (vout: VinVout, paths: AddressPaths) => {
|
||||
const address = vout.addresses?.[0] ?? throwError('Address is missing from tx output');
|
||||
|
||||
return {
|
||||
address,
|
||||
path: paths[address],
|
||||
|
||||
@@ -71,6 +71,7 @@ export class Account {
|
||||
// find inputs/outputs already registered in Round(s)
|
||||
findDetainedElements(rounds: Round[]) {
|
||||
const { accountKey } = this;
|
||||
|
||||
return rounds.flatMap(round => {
|
||||
if (round.Phase > 0) {
|
||||
const registeredInputs = getRoundEvents('InputAdded', round.CoinjoinState.Events);
|
||||
@@ -93,6 +94,7 @@ export class Account {
|
||||
|
||||
return [...inputs, ...outputs];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ export class Alice {
|
||||
type,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
return {
|
||||
accountKey: this.accountKey,
|
||||
path: this.path,
|
||||
|
||||
@@ -52,6 +52,7 @@ export class CoinjoinClient extends TypedEmitter<CoinjoinClientEvents> {
|
||||
if (this.abortController.signal.aborted) {
|
||||
this.abortController = new AbortController();
|
||||
}
|
||||
|
||||
return this.status.start();
|
||||
}
|
||||
|
||||
@@ -188,6 +189,7 @@ export class CoinjoinClient extends TypedEmitter<CoinjoinClientEvents> {
|
||||
// and update fresh data from Status
|
||||
return currentRound.onPhaseChange(round);
|
||||
}
|
||||
|
||||
return [];
|
||||
}),
|
||||
);
|
||||
@@ -268,6 +270,7 @@ export class CoinjoinClient extends TypedEmitter<CoinjoinClientEvents> {
|
||||
private getLogger(): Logger {
|
||||
const emit = (level: LogLevel) => (payload: string) =>
|
||||
this.emit('log', { level, payload: redacted(payload) });
|
||||
|
||||
return {
|
||||
debug: emit('debug'),
|
||||
info: emit('info'),
|
||||
|
||||
@@ -98,6 +98,7 @@ export class CoinjoinPrison extends TypedEmitter<CoinjoinPrisonEvents> {
|
||||
} else {
|
||||
id = inmate.accountKey;
|
||||
}
|
||||
|
||||
return this.inmates.find(i => i.id === id);
|
||||
}
|
||||
|
||||
@@ -144,6 +145,7 @@ export class CoinjoinPrison extends TypedEmitter<CoinjoinPrisonEvents> {
|
||||
// regardless of their sentenceEnd
|
||||
if (!rounds.includes(inmate.roundId)) return false;
|
||||
}
|
||||
|
||||
return inmate.sentenceEnd > now;
|
||||
});
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ export class CoinjoinRound extends TypedEmitter<Events> {
|
||||
const unlock = () => {
|
||||
this.logger.warn(`Aborting round ${this.id}`);
|
||||
abort();
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
@@ -236,6 +237,7 @@ export class CoinjoinRound extends TypedEmitter<Events> {
|
||||
const { info: log } = this.logger;
|
||||
if (this.inputs.length === 0) {
|
||||
log('Trying to process round without inputs');
|
||||
|
||||
return this;
|
||||
}
|
||||
await this.processPhase(accounts);
|
||||
@@ -252,6 +254,7 @@ export class CoinjoinRound extends TypedEmitter<Events> {
|
||||
if (shouldBeExcluded) {
|
||||
input.clearConfirmationInterval();
|
||||
}
|
||||
|
||||
return !shouldBeExcluded;
|
||||
});
|
||||
} else if (this.phase > RoundPhase.InputRegistration) {
|
||||
@@ -322,6 +325,7 @@ export class CoinjoinRound extends TypedEmitter<Events> {
|
||||
this.logger.info(`Requesting ownership for ~~${input.outpoint}~~`);
|
||||
input.setRequest('ownership');
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'ownership',
|
||||
roundId: this.id,
|
||||
@@ -338,6 +342,7 @@ export class CoinjoinRound extends TypedEmitter<Events> {
|
||||
this.logger.info(`Requesting witness for ~~${input.outpoint}~~`);
|
||||
input.setRequest('signature');
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'signature',
|
||||
roundId: this.id,
|
||||
|
||||
@@ -75,6 +75,7 @@ export class Status extends TypedEmitter<StatusEvents> {
|
||||
'warn',
|
||||
`Unexpected phase change: ${nextRound.Id} ${known.Phase} => ${nextRound.Phase}`,
|
||||
);
|
||||
|
||||
// possible corner-case:
|
||||
// - suite fetch the /status, next fetch will be in ~20 sec. + potential network delay
|
||||
// - round is currently in phase "0" but will be changed to "1" in few seconds,
|
||||
@@ -82,6 +83,7 @@ export class Status extends TypedEmitter<StatusEvents> {
|
||||
// - suite fetch the /status, round phase is changed from 0 to 2
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.concat(
|
||||
@@ -192,6 +194,7 @@ export class Status extends TypedEmitter<StatusEvents> {
|
||||
|
||||
this.emit('update', statusEvent);
|
||||
this.rounds = status.RoundStates;
|
||||
|
||||
return statusEvent;
|
||||
}
|
||||
}
|
||||
@@ -254,6 +257,7 @@ export class Status extends TypedEmitter<StatusEvents> {
|
||||
|
||||
// start lifecycle only if status is present
|
||||
this.setStatusTimeout();
|
||||
|
||||
return { success: true as const, ...status, version };
|
||||
} catch (error) {
|
||||
return { success: false as const, error: error.message };
|
||||
|
||||
@@ -27,6 +27,7 @@ const transformVinVout = (vinvout: EnhancedVinVout, network: Network) => {
|
||||
if (vinvout.isAccountOwned) return { Address, Value } as AnalyzeInternalVinVout;
|
||||
|
||||
const ScriptPubKey = addressBjs.toOutputScript(Address, network).toString('hex');
|
||||
|
||||
return {
|
||||
ScriptPubKey,
|
||||
Value,
|
||||
@@ -48,6 +49,7 @@ export const getRawLiquidityClue = (
|
||||
.flatMap(vout => transformVinVout(vout, options.network))
|
||||
.filter(vout => !('address' in vout))
|
||||
.map(o => Number(o.Value));
|
||||
|
||||
return middleware.initLiquidityClue(externalAmounts, {
|
||||
baseUrl: options.middlewareUrl,
|
||||
signal: options.signal,
|
||||
@@ -90,6 +92,7 @@ export const getAnonymityScores = async (
|
||||
return scores.reduce(
|
||||
(dict, { Address, AnonymitySet }) => {
|
||||
dict[Address] = AnonymitySet;
|
||||
|
||||
return dict;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
|
||||
@@ -21,6 +21,7 @@ export const getStatus = async (options: RequestOptions) => {
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ const parseResult = (headers: Headers, text: string) => {
|
||||
// fall down and return text
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
@@ -101,6 +102,7 @@ export const coordinatorRequest = async <R = void>(
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
|
||||
return { response, text };
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export const getRealCredentials = async (
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return data.RealCredentialRequests;
|
||||
};
|
||||
|
||||
@@ -44,6 +45,7 @@ export const getZeroCredentials = async (issuer: IssuerParameter, options: Reque
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return data.ZeroCredentialRequests;
|
||||
};
|
||||
|
||||
@@ -62,6 +64,7 @@ export const getCredentials = async (
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return data.Credentials;
|
||||
};
|
||||
|
||||
@@ -78,6 +81,7 @@ export const getOutputsAmounts = async (
|
||||
options: RequestOptions,
|
||||
) => {
|
||||
const data = await request<{ OutputAmounts: number[] }>('get-outputs-amounts', body, options);
|
||||
|
||||
return data.OutputAmounts;
|
||||
};
|
||||
|
||||
@@ -97,6 +101,7 @@ export const selectInputsForRound = async (
|
||||
options: RequestOptions,
|
||||
) => {
|
||||
const data = await request<{ Indices: number[] }>('select-inputs-for-round', body, options);
|
||||
|
||||
return data.Indices;
|
||||
};
|
||||
|
||||
@@ -115,6 +120,7 @@ export const initLiquidityClue = async (ExternalAmounts: number[], options: Requ
|
||||
{ ExternalAmounts },
|
||||
options,
|
||||
);
|
||||
|
||||
return data.RawLiquidityClue;
|
||||
};
|
||||
|
||||
@@ -129,6 +135,7 @@ export const updateLiquidityClue = async (
|
||||
{ RawLiquidityClue: rawLiquidityClue, MaxSuggestedAmount, ExternalAmounts },
|
||||
options,
|
||||
);
|
||||
|
||||
return data.RawLiquidityClue;
|
||||
};
|
||||
|
||||
@@ -142,6 +149,7 @@ export const getLiquidityClue = async (
|
||||
{ rawLiquidityClue, maxSuggestedAmount },
|
||||
options,
|
||||
);
|
||||
|
||||
return data.LiquidityClue;
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ const confirmInput = async (
|
||||
}
|
||||
if (input.confirmedAmountCredentials && input.confirmedVsizeCredentials) {
|
||||
options.logger.info(`Input ~~${input.outpoint}~~ already confirmed. Skipping.`);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -75,6 +76,7 @@ const confirmInput = async (
|
||||
!confirmationData.RealVsizeCredentials
|
||||
) {
|
||||
logger.info(`Confirmed in phase ${round.phase} ~~${input.outpoint}~~ in ~~${round.id}~~`);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -198,6 +200,7 @@ export const connectionConfirmation = async (
|
||||
if (!input.getConfirmationInterval()) {
|
||||
input.setConfirmationInterval(interval);
|
||||
}
|
||||
|
||||
return interval.promise;
|
||||
}),
|
||||
).then(result =>
|
||||
|
||||
@@ -31,11 +31,13 @@ const registerInput = async (
|
||||
// stop here and request for ownership proof from the wallet
|
||||
if (!input.ownershipProof) {
|
||||
logger.info(`Waiting for ~~${input.outpoint}~~ ownership proof`);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
if (input.registrationData) {
|
||||
logger.info(`Input ~~${input.outpoint}~~ already registered. Skipping.`);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -182,6 +184,7 @@ const registerInput = async (
|
||||
return input;
|
||||
} catch (error) {
|
||||
input.setError(error);
|
||||
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -64,9 +64,11 @@ const getOutputAmounts = async (params: GetOutputAmountsParams) => {
|
||||
{ signal, baseUrl: middlewareUrl },
|
||||
);
|
||||
logger.info(`Decompose amounts: ${outputAmounts.join(',')}`);
|
||||
|
||||
return outputAmounts.map(amount => {
|
||||
const miningFee = Math.floor((outputSize * roundParameters.MiningFeeRate) / 1000);
|
||||
const coordinatorFee = 0; // NOTE: middleware issue https://github.com/zkSNACKs/WalletWasabi/issues/8814 should be `amount > plebsDontPayThreshold ? Math.floor(roundParameters.coordinationFeeRate.rate * amount) : 0` but middleware does not considerate coordinationFeeRate and plebs for external amounts
|
||||
|
||||
return amount + coordinatorFee + miningFee;
|
||||
});
|
||||
};
|
||||
@@ -238,6 +240,7 @@ const findCredentialsForTarget = (
|
||||
if (pair) {
|
||||
return [cre, pair];
|
||||
}
|
||||
|
||||
return [];
|
||||
})
|
||||
.find(pair => pair.length === 2);
|
||||
@@ -405,6 +408,7 @@ export const outputDecomposition = async (
|
||||
if (!input.confirmedAmountCredentials || !input.confirmedVsizeCredentials) {
|
||||
throw new Error(`Missing confirmed credentials for ~~${input.outpoint}~~`);
|
||||
}
|
||||
|
||||
return input.accountKey;
|
||||
},
|
||||
true,
|
||||
@@ -460,6 +464,7 @@ export const outputDecomposition = async (
|
||||
options,
|
||||
result: [],
|
||||
});
|
||||
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
@@ -468,6 +473,7 @@ export const outputDecomposition = async (
|
||||
return Object.keys(groupInputsByAccount).map((accountKey, index) => {
|
||||
if (!joinedCredentials[index])
|
||||
throw new Error(`Missing joined credentials at index ${index}`);
|
||||
|
||||
return {
|
||||
accountKey,
|
||||
outputs: joinedCredentials[index],
|
||||
|
||||
@@ -91,6 +91,7 @@ const registerOutput = async (
|
||||
sentenceEnd: Infinity, // this address should never be recycled
|
||||
},
|
||||
);
|
||||
|
||||
return tryToRegisterOutput(false);
|
||||
}
|
||||
if (error.errorCode === WabiSabiProtocolErrorCode.NotEnoughFunds) {
|
||||
@@ -157,6 +158,7 @@ export const outputRegistration = async (
|
||||
if (!account) throw new Error(`Unknown account ~~${accountKey}~~`);
|
||||
|
||||
const assignedAddresses: AccountAddress[] = [];
|
||||
|
||||
return Promise.all(
|
||||
arrayShuffle(outputs).map(output =>
|
||||
registerOutput(round, account, output, assignedAddresses, options),
|
||||
@@ -180,5 +182,6 @@ export const outputRegistration = async (
|
||||
|
||||
round.inputs.forEach(input => input.setError(new Error(message)));
|
||||
}
|
||||
|
||||
return round;
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ export const getRoundCandidates = ({
|
||||
prison,
|
||||
}: Omit<SelectRoundProps, 'aliceGenerator' | 'accounts' | 'runningAffiliateServer'>) => {
|
||||
const now = Date.now();
|
||||
|
||||
return statusRounds
|
||||
.filter(
|
||||
round =>
|
||||
@@ -100,6 +101,7 @@ export const getAccountCandidates = ({
|
||||
// account was detained
|
||||
if (prison.isDetained(accountKey)) {
|
||||
logger.info(`Account ~~${accountKey}~~ detained`);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -107,6 +109,7 @@ export const getAccountCandidates = ({
|
||||
account.utxos,
|
||||
utxo => {
|
||||
const blamedUtxo = blameOfInputs.find(i => i.id === utxo.outpoint);
|
||||
|
||||
return blamedUtxo?.roundId;
|
||||
},
|
||||
true,
|
||||
@@ -114,6 +117,7 @@ export const getAccountCandidates = ({
|
||||
|
||||
if (Object.keys(blameOfUtxos).length > 0) {
|
||||
logger.info(`Found account candidate for blame round ~~${accountKey}~~`);
|
||||
|
||||
return {
|
||||
...account,
|
||||
blameOf: blameOfUtxos,
|
||||
@@ -130,6 +134,7 @@ export const getAccountCandidates = ({
|
||||
key: account.accountKey,
|
||||
reason: SessionPhase.SkippingRound,
|
||||
});
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -167,6 +172,7 @@ export const getAccountCandidates = ({
|
||||
key: account.accountKey,
|
||||
reason: SessionPhase.BlockedUtxos,
|
||||
});
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -205,6 +211,7 @@ export const getAccountCandidates = ({
|
||||
}
|
||||
|
||||
logger.info(`Found account candidate ~~${accountKey}~~ with ${utxos.length} inputs`);
|
||||
|
||||
return {
|
||||
...account,
|
||||
blameOf: null,
|
||||
@@ -273,8 +280,10 @@ const selectInputsForBlameRound = ({
|
||||
if (inputs.length > 0) {
|
||||
round.inputs.push(...inputs);
|
||||
logger.info(`Created blame round ~~${round.id}~~ with ${round.inputs.length} inputs`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -303,6 +312,7 @@ export const selectInputsForRound = async ({
|
||||
accountCandidates: blameOfAccounts,
|
||||
options,
|
||||
});
|
||||
|
||||
return blameRound;
|
||||
}
|
||||
|
||||
@@ -323,6 +333,7 @@ export const selectInputsForRound = async ({
|
||||
AllowedOutputAmounts: roundParameters.AllowedOutputAmounts,
|
||||
AllowedInputTypes: roundParameters.AllowedInputTypes,
|
||||
};
|
||||
|
||||
return Promise.all(
|
||||
// ...and for each Account
|
||||
normalAccounts.map(account => {
|
||||
@@ -342,6 +353,7 @@ export const selectInputsForRound = async ({
|
||||
logger.info(
|
||||
`Skipping round ~~${round.id}~~ for ~~${account.accountKey}~~. Fees to high ${roundParameters.MiningFeeRate} ${roundParameters.CoordinationFeeRate.Rate}`,
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -372,6 +384,7 @@ export const selectInputsForRound = async ({
|
||||
.then(indices => indices.filter(i => Utxos[i])) // filter valid existing indices
|
||||
.catch(error => {
|
||||
logger.error(`selectInputsForRound failed ${error.message}`);
|
||||
|
||||
return [] as number[];
|
||||
});
|
||||
}),
|
||||
@@ -384,6 +397,7 @@ export const selectInputsForRound = async ({
|
||||
const maxUtxosInRound = Math.max(...sumUtxosInRounds);
|
||||
if (maxUtxosInRound < 1) {
|
||||
logger.info('No results from selectInputsForRound');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -450,6 +464,7 @@ export const selectRound = async ({
|
||||
phase: SessionPhase.AffiliateServerOffline,
|
||||
accountKeys: unregisteredAccountKeys,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -463,6 +478,7 @@ export const selectRound = async ({
|
||||
});
|
||||
if (roundCandidates.length < 1) {
|
||||
logger.info('No suitable rounds');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -477,6 +493,7 @@ export const selectRound = async ({
|
||||
|
||||
if (accountCandidates.length < 1) {
|
||||
logger.info('No suitable accounts');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -494,9 +511,11 @@ export const selectRound = async ({
|
||||
phase: SessionPhase.RetryingRoundPairing,
|
||||
accountKeys: unregisteredAccountKeys,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Created new round ~~${newRound.id}~~ with ${newRound.inputs.length} inputs`);
|
||||
|
||||
return newRound;
|
||||
};
|
||||
|
||||
@@ -52,6 +52,7 @@ const getTransactionData = (
|
||||
const { index, hash } = readOutpoint(Coin.Outpoint);
|
||||
const internal = myInputsInRound.find(a => compareOutpoint(a.outpoint, Coin.Outpoint));
|
||||
const address = getAddressFromScriptPubKey(Coin.TxOut.ScriptPubKey, options.network);
|
||||
|
||||
return {
|
||||
path: internal?.path,
|
||||
outpoint: internal?.outpoint || Coin.Outpoint, // NOTE: internal outpoints are in lowercase, coordinators in uppercase
|
||||
@@ -72,6 +73,7 @@ const getTransactionData = (
|
||||
o => Output.ScriptPubKey === o.scriptPubKey,
|
||||
);
|
||||
const address = getAddressFromScriptPubKey(Output.ScriptPubKey, options.network);
|
||||
|
||||
return {
|
||||
path: internalOutput?.path,
|
||||
address,
|
||||
@@ -98,6 +100,7 @@ const updateRawLiquidityClue = async (
|
||||
const externalAmounts = tx.outputs
|
||||
.filter(o => !account.changeAddresses.find(addr => addr.address === o.address))
|
||||
.map(o => o.amount);
|
||||
|
||||
return middleware.updateLiquidityClue(
|
||||
account.rawLiquidityClue,
|
||||
round.roundParameters.MaxSuggestedAmount,
|
||||
@@ -174,12 +177,14 @@ export const transactionSigning = async (
|
||||
inputsWithError.forEach(input => {
|
||||
logger.error(`Trying to sign input with assigned error ${input.error?.message}`);
|
||||
});
|
||||
|
||||
return round;
|
||||
}
|
||||
|
||||
const alreadyRequested = round.inputs.some(input => input.requested?.type === 'signature');
|
||||
if (alreadyRequested) {
|
||||
logger.error(`Signature request was not fulfilled`);
|
||||
|
||||
return round;
|
||||
}
|
||||
|
||||
@@ -187,6 +192,7 @@ export const transactionSigning = async (
|
||||
logger.warn(`Missing affiliate request. Waiting for status`);
|
||||
round.setSessionPhase(SessionPhase.AwaitingCoinjoinTransaction);
|
||||
round.transactionSignTries.push(Date.now());
|
||||
|
||||
return round;
|
||||
}
|
||||
|
||||
@@ -194,6 +200,7 @@ export const transactionSigning = async (
|
||||
const resolvedTime = Math.max(
|
||||
...round.inputs.map(i => {
|
||||
const res = i.getResolvedRequest('signature');
|
||||
|
||||
return res?.timestamp || 0;
|
||||
}),
|
||||
);
|
||||
@@ -211,6 +218,7 @@ export const transactionSigning = async (
|
||||
);
|
||||
round.transactionData = transactionData;
|
||||
round.liquidityClues = liquidityClues;
|
||||
|
||||
return round;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,12 +63,14 @@ export function prefixScriptPubKey(scriptPubKey: string, useHex: false): Buffer;
|
||||
export function prefixScriptPubKey(scriptPubKey: string, useHex = true) {
|
||||
const [OP, hash] = scriptPubKey.split(' ');
|
||||
const script = bscript.fromASM(`OP_${OP} ${hash}`);
|
||||
|
||||
return useHex ? script.toString('hex') : script;
|
||||
}
|
||||
|
||||
// return address from WabiSabi.scriptPubKey
|
||||
export const getAddressFromScriptPubKey = (scriptPubKey: string, network: Network) => {
|
||||
const script = prefixScriptPubKey(scriptPubKey, false);
|
||||
|
||||
return baddress.fromOutputScript(script, network);
|
||||
};
|
||||
|
||||
@@ -88,6 +90,7 @@ export const getOutputSize = (type: AllowedScriptTypes) => {
|
||||
|
||||
export const getExternalOutputSize = (scriptPubKey: string) => {
|
||||
const type = getScriptTypeFromScriptPubKey(scriptPubKey);
|
||||
|
||||
return getOutputSize(type);
|
||||
};
|
||||
|
||||
@@ -97,6 +100,7 @@ export const readOutpoint = (outpoint: string) => {
|
||||
const txid = reader.readSlice(32);
|
||||
const index = reader.readUInt32();
|
||||
const hash = bUtils.reverseBuffer(txid).toString('hex');
|
||||
|
||||
return { index, hash, txid: txid.toString('hex') };
|
||||
};
|
||||
|
||||
@@ -111,6 +115,7 @@ const compareByteArray = (left: Buffer, right: Buffer) => {
|
||||
if (left[i] < right[i]) return -1;
|
||||
if (left[i] > right[i]) return 1;
|
||||
}
|
||||
|
||||
return left.length - right.length;
|
||||
};
|
||||
|
||||
@@ -122,8 +127,10 @@ export const mergePubkeys = (outputs: CoinjoinOutputAddedEvent[]) =>
|
||||
if (duplicates.length > 1) {
|
||||
if (a.find(o => o.Output.ScriptPubKey === item.Output.ScriptPubKey)) return a;
|
||||
const Value = duplicates.reduce((v, b) => v + b.Output.Value, 0);
|
||||
|
||||
return a.concat({ ...item, Output: { ...item.Output, Value } });
|
||||
}
|
||||
|
||||
return a.concat(item);
|
||||
}, [] as CoinjoinOutputAddedEvent[]);
|
||||
|
||||
@@ -140,6 +147,7 @@ export const sortInputs = (a: CoinjoinInput, b: CoinjoinInput) => {
|
||||
export const sortOutputs = (a: CoinjoinOutput, b: CoinjoinOutput) => {
|
||||
if (a.Value === b.Value)
|
||||
return compareByteArray(Buffer.from(a.ScriptPubKey), Buffer.from(b.ScriptPubKey));
|
||||
|
||||
return b.Value - a.Value;
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export const patchResponse = (obj: any) => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -54,11 +55,13 @@ const createHeaders = (options: RequestOptions) => {
|
||||
if (typeof options.userAgent === 'string') {
|
||||
headers['User-Agent'] = options.userAgent || 'Trezor Suite';
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
export const httpGet = (url: string, query?: Record<string, any>, options: RequestOptions = {}) => {
|
||||
const queryString = query ? `?${new URLSearchParams(query)}` : '';
|
||||
|
||||
return fetch(`${url}${queryString}`, {
|
||||
method: 'GET',
|
||||
signal: options.signal,
|
||||
@@ -77,5 +80,6 @@ export const httpPost = (url: string, body?: Record<string, any>, options: Reque
|
||||
// Randomize identity password to reset TOR circuit for this identity
|
||||
export const resetIdentityCircuit = (identity: string) => {
|
||||
const [user] = identity.split(':');
|
||||
|
||||
return `${user}:${getWeakRandomId(16)}`;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ export const redacted = (message: string) =>
|
||||
match.length - 2,
|
||||
)}`;
|
||||
}
|
||||
|
||||
return `[redacted]`;
|
||||
})
|
||||
: `${message}`;
|
||||
|
||||
@@ -31,6 +31,7 @@ export const getRoundParameters = (round: Round) => {
|
||||
if (events.length < 1) return;
|
||||
|
||||
const [{ RoundParameters }] = events;
|
||||
|
||||
return RoundParameters;
|
||||
};
|
||||
|
||||
@@ -39,6 +40,7 @@ export const getCommitmentData = (identifier: string, roundId: string) => {
|
||||
const name = Buffer.from(identifier);
|
||||
const len = Buffer.allocUnsafe(1);
|
||||
len.writeUInt8(name.length, 0);
|
||||
|
||||
return Buffer.concat([len, name, Buffer.from(roundId, 'hex')]).toString('hex');
|
||||
};
|
||||
|
||||
@@ -104,6 +106,7 @@ export const getCoinjoinRoundDeadlines = (round: PartialCoinjoinRound) => {
|
||||
case RoundPhase.InputRegistration: {
|
||||
const deadline =
|
||||
new Date(round.InputRegistrationEnd).getTime() + ROUND_REGISTRATION_END_OFFSET;
|
||||
|
||||
return {
|
||||
phaseDeadline: deadline,
|
||||
roundDeadline:
|
||||
@@ -116,6 +119,7 @@ export const getCoinjoinRoundDeadlines = (round: PartialCoinjoinRound) => {
|
||||
case RoundPhase.ConnectionConfirmation: {
|
||||
const deadline =
|
||||
now + readTimeSpan(round.RoundParameters.ConnectionConfirmationTimeout);
|
||||
|
||||
return {
|
||||
phaseDeadline: deadline,
|
||||
roundDeadline:
|
||||
@@ -126,6 +130,7 @@ export const getCoinjoinRoundDeadlines = (round: PartialCoinjoinRound) => {
|
||||
}
|
||||
case RoundPhase.OutputRegistration: {
|
||||
const deadline = now + readTimeSpan(round.RoundParameters.OutputRegistrationTimeout);
|
||||
|
||||
return {
|
||||
phaseDeadline: deadline,
|
||||
roundDeadline:
|
||||
@@ -135,6 +140,7 @@ export const getCoinjoinRoundDeadlines = (round: PartialCoinjoinRound) => {
|
||||
case RoundPhase.TransactionSigning:
|
||||
case RoundPhase.Ended: {
|
||||
const deadline = now + readTimeSpan(round.RoundParameters.TransactionSigningTimeout);
|
||||
|
||||
return {
|
||||
phaseDeadline: deadline,
|
||||
roundDeadline: deadline,
|
||||
@@ -165,6 +171,7 @@ export const findNearestDeadline = (rounds: Round[]) => {
|
||||
const deadlines = rounds.map(r => {
|
||||
const phaseDeadline = estimatePhaseDeadline(r);
|
||||
const timeLeft = phaseDeadline ? new Date(phaseDeadline).getTime() - now : 0;
|
||||
|
||||
return timeLeft > 0 ? timeLeft : now;
|
||||
});
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ describe('CoinjoinAddressController', () => {
|
||||
controller.analyze(
|
||||
({ address }) => {
|
||||
const index = SEGWIT_RECEIVE_ADDRESSES.indexOf(address);
|
||||
|
||||
return index % 2 === 0 ? [index] : [];
|
||||
},
|
||||
(txs: number[]) => result.push(...txs),
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('CoinjoinBackendClient', () => {
|
||||
let lastBackend = '';
|
||||
jest.spyOn((client as any).websockets, 'getOrCreate').mockImplementation(args => {
|
||||
[lastBackend] = (args as any).url.split('/');
|
||||
|
||||
return new Proxy({}, { get: (_, b, c) => b !== 'then' && (() => c) });
|
||||
});
|
||||
|
||||
@@ -42,6 +43,7 @@ describe('CoinjoinBackendClient', () => {
|
||||
jest.spyOn((client as any).websockets, 'getOrCreate').mockImplementation(args => {
|
||||
const { identity } = args as any;
|
||||
identities.push(identity);
|
||||
|
||||
return Promise.reject(new Error(identity === 'is' ? 'unknown' : WS_ERROR_403));
|
||||
});
|
||||
|
||||
@@ -85,6 +87,7 @@ describe('CoinjoinBackendClient', () => {
|
||||
const { url, identity } = args as any;
|
||||
urls.push(url);
|
||||
identities.push(identity);
|
||||
|
||||
return Promise.reject(
|
||||
new Error(url.includes('.onion') ? WS_ERROR_TIMEOUT : WS_ERROR_403),
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@ describe('CoinjoinMempoolController', () => {
|
||||
analyze: (getTxs, onTxs) => {
|
||||
const txs = getTxs({ address: ADDRESS } as AccountAddress);
|
||||
onTxs?.(txs);
|
||||
|
||||
return { receive: [], change: [] };
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as CONSTANTS from '../../src/constants';
|
||||
// mock random delay function
|
||||
jest.mock('@trezor/utils', () => {
|
||||
const originalModule = jest.requireActual('@trezor/utils');
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
@@ -18,6 +19,7 @@ jest.mock('@trezor/utils', () => {
|
||||
// mock ROUND_PHASE_PROCESS_TIMEOUT, use getter to mock individually for each test
|
||||
jest.mock('../../src/constants', () => {
|
||||
const originalModule = jest.requireActual('../../src/constants');
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
|
||||
@@ -15,6 +15,7 @@ const fastForward = (time: number) => jest.advanceTimersByTimeAsync(time);
|
||||
// use getters to allow mocking different values in each test case
|
||||
jest.mock('../../src/constants', () => {
|
||||
const originalModule = jest.requireActual('../../src/constants');
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
@@ -64,6 +65,7 @@ describe('Status', () => {
|
||||
if (url === 'status') {
|
||||
coordinatorRequestSpy();
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
...STATUS_EVENT,
|
||||
RoundStates: [{ ...DEFAULT_ROUND }],
|
||||
@@ -121,6 +123,7 @@ describe('Status', () => {
|
||||
if (url === 'status') {
|
||||
coordinatorRequestSpy();
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
...STATUS_EVENT,
|
||||
RoundStates: [{ ...DEFAULT_ROUND, Phase: coordinatorRequestSpy.mock.calls.length }], // increment phase on each request to trigger update event
|
||||
@@ -164,6 +167,7 @@ describe('Status', () => {
|
||||
identities.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
...STATUS_EVENT,
|
||||
RoundStates: [{ ...DEFAULT_ROUND }],
|
||||
@@ -282,6 +286,7 @@ describe('Status', () => {
|
||||
if (url === 'status') {
|
||||
coordinatorRequestSpy();
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
...STATUS_EVENT,
|
||||
RoundStates: [{ ...round }], // NOTE: always return new reference for the Round from mock
|
||||
|
||||
@@ -10,6 +10,7 @@ let server: Awaited<ReturnType<typeof createServer>>;
|
||||
|
||||
jest.mock('@trezor/utils', () => {
|
||||
const originalModule = jest.requireActual('@trezor/utils');
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
@@ -187,6 +188,7 @@ describe('connectionConfirmation', () => {
|
||||
if (i > 0) {
|
||||
return a - timestamps[i - 1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
})
|
||||
.forEach(ts => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createCoinjoinRound } from '../fixtures/round.fixture';
|
||||
// mock random delay function
|
||||
jest.mock('@trezor/utils', () => {
|
||||
const originalModule = jest.requireActual('@trezor/utils');
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
@@ -211,6 +212,7 @@ describe('inputRegistration', () => {
|
||||
if (url.endsWith('/input-registration')) {
|
||||
// respond after phaseDeadline
|
||||
setTimeout(resolve, 4000);
|
||||
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
|
||||
@@ -7,6 +7,7 @@ import { createCoinjoinRound } from '../fixtures/round.fixture';
|
||||
// mock random delay function
|
||||
jest.mock('@trezor/utils', () => {
|
||||
const originalModule = jest.requireActual('@trezor/utils');
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
|
||||
@@ -357,6 +357,7 @@ describe('selectRound', () => {
|
||||
if (url.endsWith('/select-inputs-for-round')) {
|
||||
const Indices = data.Utxos.flatMap((utxo: any, i: number) => {
|
||||
if (utxo.Amount < data.MiningFeeRate) return [];
|
||||
|
||||
return i;
|
||||
});
|
||||
resolve({ Indices });
|
||||
@@ -421,6 +422,7 @@ describe('selectRound', () => {
|
||||
spy();
|
||||
const Indices = data.Utxos.flatMap((utxo: any, i: number) => {
|
||||
if (utxo.Amount < 1000 + data.MiningFeeRate) return [];
|
||||
|
||||
return i;
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createCoinjoinRound } from '../fixtures/round.fixture';
|
||||
// mock random delay function
|
||||
jest.mock('@trezor/utils', () => {
|
||||
const originalModule = jest.requireActual('@trezor/utils');
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
|
||||
@@ -34,5 +34,6 @@ export const mockFilterSequence = (
|
||||
prevHash: filters[i - 1]?.blockHash ?? baseHash,
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
};
|
||||
|
||||
@@ -19,5 +19,6 @@ export const createInput = (
|
||||
input[key] = options[key];
|
||||
});
|
||||
}
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
@@ -92,6 +92,7 @@ export class MockBackendClient extends CoinjoinBackendClient {
|
||||
.slice(from, from + count)
|
||||
.map(({ height, hash, filter }) => `${height}:${hash}:${filter}`),
|
||||
});
|
||||
|
||||
return Promise.reject(new Error('Block not found'));
|
||||
}
|
||||
// no default
|
||||
|
||||
@@ -10,6 +10,7 @@ export class MockFilterClient implements FilterClient {
|
||||
|
||||
fetchNetworkInfo(): ReturnType<FilterClient['fetchNetworkInfo']> {
|
||||
const tip = this.filters[this.filters.length - 1];
|
||||
|
||||
return Promise.resolve({ bestHeight: tip.blockHeight } as any);
|
||||
}
|
||||
|
||||
@@ -22,6 +23,7 @@ export class MockFilterClient implements FilterClient {
|
||||
return Promise.resolve({ status: 'up-to-date' });
|
||||
}
|
||||
const from = this.filters.findIndex(f => f.prevHash === knownHash);
|
||||
|
||||
return from < 0
|
||||
? Promise.resolve({ status: 'not-found' })
|
||||
: Promise.resolve({
|
||||
|
||||
@@ -26,6 +26,7 @@ export class MockMempoolClient implements MempoolClient {
|
||||
|
||||
fetchTransaction(txid: string) {
|
||||
const tx = this.mempoolTxs.find(t => t.txid === txid);
|
||||
|
||||
return tx
|
||||
? Promise.resolve(tx as unknown as BlockbookTransaction)
|
||||
: Promise.reject(new Error('not found'));
|
||||
@@ -35,11 +36,13 @@ export class MockMempoolClient implements MempoolClient {
|
||||
|
||||
subscribeMempoolTxs(listener: (tx: BlockbookTransaction) => void) {
|
||||
this.listener = listener;
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
unsubscribeMempoolTxs(_listener: (tx: BlockbookTransaction) => void) {
|
||||
this.listener = undefined;
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ const DEFAULT = {
|
||||
if (!data || !data.AmountsToRequest) {
|
||||
return { RealCredentialRequests: {} };
|
||||
}
|
||||
|
||||
return {
|
||||
RealCredentialRequests: {
|
||||
CredentialsRequest: {
|
||||
@@ -52,6 +53,7 @@ const DEFAULT = {
|
||||
if (!data || !data.CredentialsResponse) {
|
||||
return { Credentials: [{}, {}] };
|
||||
}
|
||||
|
||||
return {
|
||||
Credentials: data.CredentialsResponse,
|
||||
};
|
||||
@@ -93,6 +95,7 @@ const DEFAULT = {
|
||||
if (!data || !data.RealAmountCredentialRequests) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
RealAmountCredentials: data.RealAmountCredentialRequests.Requested.map((a: number) => ({
|
||||
Value: a,
|
||||
@@ -150,6 +153,7 @@ const handleRequest = (
|
||||
response.setHeader('Content-Type', 'application/json');
|
||||
response.write(JSON.stringify(testResponse));
|
||||
response.end();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ const CACHE_PARAMS = `${CACHE_DIR}/anonymityScoreParams.json`;
|
||||
const originalWrite = req.write.bind(req);
|
||||
req.write = (chunk: Buffer) => {
|
||||
chunks.push(chunk);
|
||||
|
||||
return originalWrite(chunk);
|
||||
};
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ https.request = (...args) => {
|
||||
response.on('response', res => {
|
||||
res.on('data', (data: Buffer) => (bytes += data.byteLength));
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -68,6 +69,7 @@ http.request = (...args) => {
|
||||
response.on('response', res => {
|
||||
res.on('data', (data: Buffer) => (bytes += data.byteLength));
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -95,8 +97,10 @@ const filtersFromWasabi = async (hash: string) => {
|
||||
const buffer = await response.buffer();
|
||||
const { filters }: { filters: string[] } = JSON.parse(buffer.toString());
|
||||
log(hash, bytes, buffer.byteLength);
|
||||
|
||||
return filters.map(data => {
|
||||
const [_blockHeight, blockHash, _filter, _prevHash, _blockTime] = data.split(':');
|
||||
|
||||
return blockHash;
|
||||
});
|
||||
}
|
||||
@@ -132,6 +136,7 @@ const getWebsocket = async () => {
|
||||
return (bestKnownBlockHash: string) => {
|
||||
compressed = 0;
|
||||
const reqID = (messageID++).toString();
|
||||
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('timeout')), TIMEOUT);
|
||||
ws.once('message', (message: string) => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { DEFAULT_ROUND, STATUS_EVENT, STATUS_TRANSFORMED } from '../fixtures/rou
|
||||
// mock random delay function
|
||||
jest.mock('@trezor/utils', () => {
|
||||
const originalModule = jest.requireActual('@trezor/utils');
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
|
||||
@@ -166,6 +166,7 @@ export const ConfirmOnDevice = ({
|
||||
}: ConfirmOnDeviceProps) => {
|
||||
const hasSteps = steps && activeStep !== undefined;
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
animation={isConfirmed ? AnimationDirection.Down : AnimationDirection.Up}
|
||||
|
||||
@@ -177,6 +177,7 @@ export const Dropdown = forwardRef(
|
||||
// do not loose focus when clicking within the menu
|
||||
if (!content && document.activeElement === menuRef.current) {
|
||||
toggleRef.current?.focus();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ const getColor = (score: OptionalZXCVBNScore, password: string) => {
|
||||
|
||||
const getPasswordScore = async (password: string) => {
|
||||
const zxcvbn = await import(/* webpackChunkName: "zxcvbn" */ 'zxcvbn');
|
||||
|
||||
return zxcvbn.default(password).score;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ export default {
|
||||
|
||||
export const All: StoryFn = () => {
|
||||
const flags = Object.keys(FLAGS) as FlagType[];
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
{flags.map(country => (
|
||||
|
||||
@@ -281,6 +281,7 @@ export const Select = ({
|
||||
selectRef.current?.blur();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[onChange, menuIsOpen],
|
||||
|
||||
@@ -28,5 +28,6 @@ export const shimmerEffect = css<{ elevation: Elevation }>`
|
||||
export const getValue = (value: string | number | undefined) => {
|
||||
if (!value) return null;
|
||||
if (typeof value === 'number') return `${value}px`;
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ type EaringType = keyof typeof motionEasing;
|
||||
export const motionEasingStrings = Object.entries(motionEasing).reduce(
|
||||
(acc: Record<EaringType, string>, [key, value]) => {
|
||||
acc[key as EaringType] = `cubic-bezier(${value.join(',')})`;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<EaringType, string>,
|
||||
|
||||
@@ -12,6 +12,7 @@ const ThemeProvider = ({ children, theme }: ThemeProviderProps) => {
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export const getInputColor = (theme: DefaultTheme, { checked, disabled }: InputC
|
||||
if (!checked) {
|
||||
return theme.backgroundNeutralDisabled;
|
||||
}
|
||||
|
||||
return disabled ? theme.backgroundPrimarySubtleOnElevation0 : theme.backgroundPrimaryDefault;
|
||||
};
|
||||
|
||||
@@ -21,6 +22,7 @@ export const getLabelColor = (theme: DefaultTheme, { alert, disabled }: LabelCol
|
||||
if (alert) {
|
||||
return theme.borderAlertRed;
|
||||
}
|
||||
|
||||
return disabled ? theme.textDisabled : theme.textDefault;
|
||||
};
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ export abstract class AbstractMessageChannel<
|
||||
this.handshakeWithPeer();
|
||||
}
|
||||
}
|
||||
|
||||
return this.handshakeFinished.promise;
|
||||
}
|
||||
|
||||
@@ -112,6 +113,7 @@ export abstract class AbstractMessageChannel<
|
||||
*/
|
||||
protected handshakeWithPeer(): Promise<void> {
|
||||
this.logger?.log(this.channel.here, 'handshake');
|
||||
|
||||
return scheduleAction(
|
||||
async () => {
|
||||
this.postMessage(
|
||||
@@ -169,10 +171,12 @@ export abstract class AbstractMessageChannel<
|
||||
if (!this.legacyMode) {
|
||||
if (!channel?.peer || channel.peer !== this.channel.here) {
|
||||
this.logger?.warn('to wrong peer', channel?.peer, 'should be', this.channel.here);
|
||||
|
||||
return;
|
||||
}
|
||||
if (!channel?.here || this.channel.peer !== channel.here) {
|
||||
this.logger?.warn('from wrong peer', channel?.here, 'should be', this.channel.peer);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -189,10 +193,12 @@ export abstract class AbstractMessageChannel<
|
||||
// When received channel-handshake-request in lazyHandshake mode we start from this side.
|
||||
this.handshakeWithPeer();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (type === 'channel-handshake-confirm') {
|
||||
this.handshakeFinished?.resolve(undefined);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -222,6 +228,7 @@ export abstract class AbstractMessageChannel<
|
||||
this.messagesQueue.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ class Storage extends TypedEmitter<Events> {
|
||||
} catch (err) {
|
||||
// memory storage is fallback of the last resort
|
||||
console.warn('long term storage not available');
|
||||
|
||||
return memoryStorage;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user