Liquid support for unblinding transactions. (#588)

* Add docker file to generate wallycore wasm js lib.

* Add unblinded liquid transactions.

* Add background to unblided transactions.

* Check liquid network to try to unblind tx.

Ww don't want to try to unblind transactions in other networks.

Co-authored-by: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com>

* Delete libwally-core dockerfile.

* Delete wallycore.html.

* Fix validation unblind tx.
Fix lint.
Add errorUnblinded.
Add vin.prevout unblinded tx.

* Add e2e testing to liquids unblinded tx.

* Load libwally.js dynamically.

* Fix table size.

* Add Blockstream License to libwally and wallycore.

Co-authored-by: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com>
This commit is contained in:
Miguel Medeiros 2021-07-06 13:56:32 -03:00 committed by GitHub
parent 3ae3df6722
commit 9dae7020c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1408 additions and 184 deletions

View File

@ -44,38 +44,77 @@ describe('Liquid', () => {
});
describe('assets', () => {
it('shows the assets screen', () => {
cy.visit('/liquid');
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('table tr').should('have.length', 5);
});
});
it('allows searching assets', () => {
cy.visit('/liquid');
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
cy.get('table tr').should('have.length', 1);
it('shows the assets screen', () => {
cy.visit('/liquid');
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('table tr').should('have.length', 5);
});
});
});
it('shows a specific asset ID', () => {
cy.visit('/liquid');
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(4) a').click();
it('allows searching assets', () => {
cy.visit('/liquid');
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => {
cy.get('table tr').should('have.length', 1);
});
});
});
});
it('shows a specific asset issuance TX', () => {
cy.visit('/liquid');
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(5) a').click();
it('shows a specific asset ID', () => {
cy.visit('/liquid');
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(4) a').click();
});
});
});
it('shows a specific asset issuance TX', () => {
cy.visit('/liquid');
cy.get('li:nth-of-type(5) > a').click().then(() => {
cy.get('.container-xl input').click().type('Liquid CAD').then(() => {
cy.get('table tr td:nth-of-type(5) a').click();
});
});
});
});
});
describe('unblinded TX', () => {
it('show unblinded TX', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a');
cy.get('#table-tx-vin tr').should('have.class', 'assetBox');
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show empty unblinded TX', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=');
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
});
it('show invalid unblinded TX hex', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123');
cy.get('#table-tx-vin tr').should('have.class', '');
cy.get('#table-tx-vout tr').should('have.class', '');
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data (invalid hex)');
});
it('show first unblinded vout', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc');
cy.get('#table-tx-vout tr:first-child()').should('have.class', 'assetBox');
});
it('show second unblinded vout', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a');
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
});
it('show invalid error unblinded TX', () => {
cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c');
cy.get('#table-tx-vout tr').should('have.class', 'assetBox');
cy.get('.error-unblinded' ).contains('Error: Invalid blinding data.');
});
});
});

View File

@ -0,0 +1,184 @@
/*
The MIT License (MIT)
Copyright 2021 Blockstream Corp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const WALLY_OK = 0,
ASSET_COMMITMENT_LEN = 33,
ASSET_GENERATOR_LEN = 33,
ASSET_TAG_LEN = 32,
BLINDING_FACTOR_LEN = 32;
const WASM_URL = `./resources/wallycore/wallycore.js`;
let load_promise, Module;
export function load() {
return (
load_promise ||
(load_promise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = WASM_URL;
script.addEventListener("error", reject);
script.addEventListener("load", () =>
InitWally().then((module) => {
Module = module;
resolve();
}, reject)
);
document.body.appendChild(script);
}))
);
}
// Simple wrapper to execute both asset_generator_from_bytes and asset_value_commitment,
// with hex conversions
export function generate_commitments(
value,
asset_hex,
value_blinder_hex,
asset_blinder_hex
) {
const asset = parseHex(asset_hex, ASSET_TAG_LEN),
value_blinder = parseHex(value_blinder_hex, BLINDING_FACTOR_LEN),
asset_blinder = parseHex(asset_blinder_hex, BLINDING_FACTOR_LEN);
const asset_commitment = asset_generator_from_bytes(asset, asset_blinder),
value_commitment = asset_value_commitment(
value,
value_blinder,
asset_commitment
);
return {
asset_commitment: encodeHex(asset_commitment),
value_commitment: encodeHex(value_commitment),
};
}
export function asset_generator_from_bytes(asset, asset_blinder) {
const asset_commitment_ptr = Module._malloc(ASSET_GENERATOR_LEN);
checkCode(
Module.ccall(
"wally_asset_generator_from_bytes",
"number",
["array", "number", "array", "number", "number", "number"],
[
asset,
asset.length,
asset_blinder,
asset_blinder.length,
asset_commitment_ptr,
ASSET_GENERATOR_LEN,
]
)
);
const asset_commitment = readBytes(asset_commitment_ptr, ASSET_GENERATOR_LEN);
Module._free(asset_commitment_ptr);
return asset_commitment;
}
export function asset_value_commitment(value, value_blinder, asset_commitment) {
// Emscripten transforms int64 function arguments into two int32 arguments, see:
// https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-pass-int64-t-and-uint64-t-values-from-js-into-wasm-functions
const [value_lo, value_hi] = split_int52_lo_hi(value);
const value_commitment_ptr = Module._malloc(ASSET_COMMITMENT_LEN);
checkCode(
Module.ccall(
"wally_asset_value_commitment",
"number",
[
"number",
"number",
"array",
"number",
"array",
"number",
"number",
"number",
],
[
value_lo,
value_hi,
value_blinder,
value_blinder.length,
asset_commitment,
asset_commitment.length,
value_commitment_ptr,
ASSET_COMMITMENT_LEN,
]
)
);
const value_commitment = readBytes(
value_commitment_ptr,
ASSET_COMMITMENT_LEN
);
Module._free(value_commitment_ptr);
return value_commitment;
}
function checkCode(code) {
if (code != WALLY_OK) throw new Error(`libwally failed with code ${code}`);
}
function readBytes(ptr, size) {
const bytes = new Uint8Array(size);
for (let i = 0; i < size; i++) bytes[i] = Module.getValue(ptr + i, "i8");
return bytes;
}
// Split a 52-bit JavaScript number into two 32-bits numbers for the low and high bits
// https://stackoverflow.com/a/19274574
function split_int52_lo_hi(i) {
let lo = i | 0;
if (lo < 0) lo += 4294967296;
let hi = i - lo;
hi /= 4294967296;
if (hi < 0 || hi >= 1048576) throw new Error("not an int52: " + i);
return [lo, hi];
}
function encodeHex(bytes) {
// return Buffer.from(bytes).toString("hex");
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
// Parse hex string encoded in *reverse*
function parseHex(str, expected_size) {
if (!/^([0-9a-f]{2})+$/.test(str))
throw new Error("Invalid blinders (invalid hex)");
if (str.length != expected_size * 2)
throw new Error("Invalid blinders (invalid length)");
return new Uint8Array(
str
.match(/.{2}/g)
.map((hex_byte) => parseInt(hex_byte, 16))
.reverse()
);
}

View File

@ -9,33 +9,33 @@
</a>
</div>
<div>
<div class="title">
<h1 i18n="shared.transaction">Transaction</h1>
</div>
<div>
<div class="title">
<h1 i18n="shared.transaction">Transaction</h1>
</div>
<div class="tx-link">
<a [routerLink]="['/tx/' | relativeUrl, txId]">
<span class="d-inline d-lg-none">{{ txId | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ txId }}</span>
</a>
<app-clipboard [text]="txId"></app-clipboard>
</div>
<div class="tx-link">
<a [routerLink]="['/tx/' | relativeUrl, txId]">
<span class="d-inline d-lg-none">{{ txId | shortenString : 24 }}</span>
<span class="d-none d-lg-inline">{{ txId }}</span>
</a>
<app-clipboard [text]="txId"></app-clipboard>
</div>
<div class="container-buttons">
<ng-template [ngIf]="tx?.status?.confirmed">
<button *ngIf="latestBlock" type="button" class="btn btn-sm btn-success">
<ng-container *ngTemplateOutlet="latestBlock.height - tx.status.block_height + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.status.block_height + 1}"></ng-container>
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
</button>
</ng-template>
<ng-template [ngIf]="tx && !tx?.status.confirmed">
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
</ng-template>
<div class="container-buttons">
<ng-template [ngIf]="tx?.status?.confirmed">
<button *ngIf="latestBlock" type="button" class="btn btn-sm btn-success">
<ng-container *ngTemplateOutlet="latestBlock.height - tx.status.block_height + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.status.block_height + 1}"></ng-container>
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
</button>
</ng-template>
<ng-template [ngIf]="tx && !tx?.status.confirmed">
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
</ng-template>
</div>
</div>
</div>
</div>
<div class="clearfix"></div>
<ng-template [ngIf]="!isLoadingTx && !error">
@ -198,7 +198,7 @@
<div class="clearfix"></div>
<app-transactions-list #txList [transactions]="[tx]" [transactionPage]="true"></app-transactions-list>
<app-transactions-list #txList [transactions]="[tx]" [errorUnblinded]="errorUnblinded" [transactionPage]="true"></app-transactions-list>
<h2 class="text-left" i18n="transaction.details">Details</h2>
<div class="box">

View File

@ -1,7 +1,13 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap, filter, catchError, retryWhen, delay } from 'rxjs/operators';
import {
switchMap,
filter,
catchError,
retryWhen,
delay,
} from 'rxjs/operators';
import { Transaction, Block } from '../../interfaces/electrs.interface';
import { of, merge, Subscription, Observable, Subject } from 'rxjs';
import { StateService } from '../../services/state.service';
@ -14,7 +20,7 @@ import { CpfpInfo } from 'src/app/interfaces/node-api.interface';
@Component({
selector: 'app-transaction',
templateUrl: './transaction.component.html',
styleUrls: ['./transaction.component.scss']
styleUrls: ['./transaction.component.scss'],
})
export class TransactionComponent implements OnInit, OnDestroy {
network = '';
@ -23,6 +29,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
txInBlockIndex: number;
isLoadingTx = true;
error: any = undefined;
errorUnblinded: any = undefined;
waitingForTransaction = false;
latestBlock: Block;
transactionTime = -1;
@ -32,6 +39,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
cpfpInfo: CpfpInfo | null;
showCpfpDetails = false;
fetchCpfp$ = new Subject<string>();
commitments: Map<any, any>;
constructor(
private route: ActivatedRoute,
@ -40,28 +48,36 @@ export class TransactionComponent implements OnInit, OnDestroy {
private websocketService: WebsocketService,
private audioService: AudioService,
private apiService: ApiService,
private seoService: SeoService,
) { }
private seoService: SeoService
) {}
ngOnInit() {
this.websocketService.want(['blocks', 'mempool-blocks']);
this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.stateService.networkChanged$.subscribe(
(network) => (this.network = network)
);
this.fetchCpfpSubscription = this.fetchCpfp$
.pipe(
switchMap((txId) => this.apiService.getCpfpinfo$(txId)
.pipe(
retryWhen((errors) => errors.pipe(delay(2000)))
)
),
switchMap((txId) =>
this.apiService
.getCpfpinfo$(txId)
.pipe(retryWhen((errors) => errors.pipe(delay(2000))))
)
)
.subscribe((cpfpInfo) => {
if (!this.tx) {
return;
}
const lowerFeeParents = cpfpInfo.ancestors.filter((parent) => (parent.fee / (parent.weight / 4)) < this.tx.feePerVsize);
let totalWeight = this.tx.weight + lowerFeeParents.reduce((prev, val) => prev + val.weight, 0);
let totalFees = this.tx.fee + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0);
const lowerFeeParents = cpfpInfo.ancestors.filter(
(parent) => parent.fee / (parent.weight / 4) < this.tx.feePerVsize
);
let totalWeight =
this.tx.weight +
lowerFeeParents.reduce((prev, val) => prev + val.weight, 0);
let totalFees =
this.tx.fee +
lowerFeeParents.reduce((prev, val) => prev + val.fee, 0);
if (cpfpInfo.bestDescendant) {
totalWeight += cpfpInfo.bestDescendant.weight;
@ -69,98 +85,116 @@ export class TransactionComponent implements OnInit, OnDestroy {
}
this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
this.stateService.markBlock$.next({ txFeePerVSize: this.tx.effectiveFeePerVsize });
this.stateService.markBlock$.next({
txFeePerVSize: this.tx.effectiveFeePerVsize,
});
this.cpfpInfo = cpfpInfo;
});
this.subscription = this.route.paramMap.pipe(
switchMap((params: ParamMap) => {
this.txId = params.get('id') || '';
this.seoService.setTitle($localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`);
this.resetTransaction();
return merge(
of(true),
this.stateService.connectionState$.pipe(
filter((state) => state === 2 && this.tx && !this.tx.status.confirmed)
),
);
}),
switchMap(() => {
let transactionObservable$: Observable<Transaction>;
if (history.state.data) {
transactionObservable$ = of(history.state.data);
} else {
transactionObservable$ = this.electrsApiService.getTransaction$(this.txId).pipe(
catchError(this.handleLoadElectrsTransactionError.bind(this))
this.subscription = this.route.paramMap
.pipe(
switchMap(async (params: ParamMap) => {
this.txId = params.get('id') || '';
await this.checkUnblindedTx();
this.seoService.setTitle(
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
);
}
return merge(
transactionObservable$,
this.stateService.mempoolTransactions$
);
})
)
.subscribe((tx: Transaction) => {
if (!tx) {
return;
}
this.tx = tx;
if (tx.fee === undefined) {
this.tx.fee = 0;
}
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
this.isLoadingTx = false;
this.error = undefined;
this.waitingForTransaction = false;
this.setMempoolBlocksSubscription();
this.resetTransaction();
return merge(
of(true),
this.stateService.connectionState$.pipe(
filter(
(state) => state === 2 && this.tx && !this.tx.status.confirmed
)
)
);
}),
switchMap(() => {
let transactionObservable$: Observable<Transaction>;
if (history.state.data) {
transactionObservable$ = of(history.state.data);
} else {
transactionObservable$ = this.electrsApiService
.getTransaction$(this.txId)
.pipe(
catchError(this.handleLoadElectrsTransactionError.bind(this))
);
}
return merge(
transactionObservable$,
this.stateService.mempoolTransactions$
);
})
)
.subscribe(
async (tx: Transaction) => {
if (!tx) {
return;
}
this.tx = tx;
if (tx.fee === undefined) {
this.tx.fee = 0;
}
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
this.isLoadingTx = false;
this.error = undefined;
this.waitingForTransaction = false;
this.setMempoolBlocksSubscription();
if (!tx.status.confirmed) {
this.websocketService.startTrackTransaction(tx.txid);
if (!tx.status.confirmed) {
this.websocketService.startTrackTransaction(tx.txid);
if (tx.firstSeen) {
this.transactionTime = tx.firstSeen;
} else {
this.getTransactionTime();
}
}
if (tx.firstSeen) {
this.transactionTime = tx.firstSeen;
} else {
this.getTransactionTime();
}
}
if (this.tx.status.confirmed) {
this.stateService.markBlock$.next({ blockHeight: tx.status.block_height });
} else {
if (tx.cpfpChecked) {
this.stateService.markBlock$.next({ txFeePerVSize: tx.effectiveFeePerVsize });
this.cpfpInfo = {
ancestors: tx.ancestors,
bestDescendant: tx.bestDescendant,
};
} else {
this.fetchCpfp$.next(this.tx.txid);
if (this.tx.status.confirmed) {
this.stateService.markBlock$.next({
blockHeight: tx.status.block_height,
});
} else {
if (tx.cpfpChecked) {
this.stateService.markBlock$.next({
txFeePerVSize: tx.effectiveFeePerVsize,
});
this.cpfpInfo = {
ancestors: tx.ancestors,
bestDescendant: tx.bestDescendant,
};
} else {
this.fetchCpfp$.next(this.tx.txid);
}
}
await this.checkUnblindedTx();
},
(error) => {
this.error = error;
this.isLoadingTx = false;
}
);
this.stateService.blocks$.subscribe(([block, txConfirmed]) => {
this.latestBlock = block;
if (txConfirmed && this.tx) {
this.tx.status = {
confirmed: true,
block_height: block.height,
block_hash: block.id,
block_time: block.timestamp,
};
this.stateService.markBlock$.next({ blockHeight: block.height });
this.audioService.playSound('magic');
}
},
(error) => {
this.error = error;
this.isLoadingTx = false;
});
this.stateService.blocks$
.subscribe(([block, txConfirmed]) => {
this.latestBlock = block;
if (txConfirmed && this.tx) {
this.tx.status = {
confirmed: true,
block_height: block.height,
block_hash: block.id,
block_time: block.timestamp,
};
this.stateService.markBlock$.next({ blockHeight: block.height });
this.audioService.playSound('magic');
}
});
this.stateService.txReplaced$
.subscribe((rbfTransaction) => this.rbfTransaction = rbfTransaction);
this.stateService.txReplaced$.subscribe(
(rbfTransaction) => (this.rbfTransaction = rbfTransaction)
);
}
handleLoadElectrsTransactionError(error: any): Observable<any> {
@ -174,26 +208,30 @@ export class TransactionComponent implements OnInit, OnDestroy {
}
setMempoolBlocksSubscription() {
this.stateService.mempoolBlocks$
.subscribe((mempoolBlocks) => {
if (!this.tx) {
return;
}
this.stateService.mempoolBlocks$.subscribe((mempoolBlocks) => {
if (!this.tx) {
return;
}
const txFeePerVSize = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
const txFeePerVSize =
this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
for (const block of mempoolBlocks) {
for (let i = 0; i < block.feeRange.length - 1; i++) {
if (txFeePerVSize <= block.feeRange[i + 1] && txFeePerVSize >= block.feeRange[i]) {
this.txInBlockIndex = mempoolBlocks.indexOf(block);
}
for (const block of mempoolBlocks) {
for (let i = 0; i < block.feeRange.length - 1; i++) {
if (
txFeePerVSize <= block.feeRange[i + 1] &&
txFeePerVSize >= block.feeRange[i]
) {
this.txInBlockIndex = mempoolBlocks.indexOf(block);
}
}
});
}
});
}
getTransactionTime() {
this.apiService.getTransactionTimes$([this.tx.txid])
this.apiService
.getTransactionTimes$([this.tx.txid])
.subscribe((transactionTimes) => {
this.transactionTime = transactionTimes[0];
});
@ -226,4 +264,145 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.fetchCpfpSubscription.unsubscribe();
this.leaveTransaction();
}
// Parse the blinders data from a string encoded as a comma separated list, in the following format:
// <value_in_satoshis>,<asset_tag_hex>,<amount_blinder_hex>,<asset_blinder_hex>
// This can be repeated with a comma separator to specify blinders for multiple outputs.
parseBlinders(str: string) {
const parts = str.split(',');
const blinders = [];
while (parts.length) {
blinders.push({
value: this.verifyNum(parts.shift()),
asset: this.verifyHex32(parts.shift()),
value_blinder: this.verifyHex32(parts.shift()),
asset_blinder: this.verifyHex32(parts.shift()),
});
}
return blinders;
}
verifyNum(num: string) {
if (!+num) {
throw new Error('Invalid blinding data (invalid number)');
}
return +num;
}
verifyHex32(str: string) {
if (!str || !/^[0-9a-f]{64}$/i.test(str)) {
throw new Error('Invalid blinding data (invalid hex)');
}
return str;
}
async makeCommitmentMap(blinders: any) {
const libwally = await import('./libwally.js');
await libwally.load();
const commitments = new Map();
blinders.forEach(b => {
const { asset_commitment, value_commitment } =
libwally.generate_commitments(b.value, b.asset, b.value_blinder, b.asset_blinder);
commitments.set(`${asset_commitment}:${value_commitment}`, {
asset: b.asset,
value: b.value,
});
});
return commitments;
}
// Look for the given output, returning an { value, asset } object
find(vout: any) {
return vout.assetcommitment && vout.valuecommitment &&
this.commitments.get(`${vout.assetcommitment}:${vout.valuecommitment}`);
}
// Lookup all transaction inputs/outputs and attach the unblinded data
tryUnblindTx(tx: any) {
if (tx) {
if (tx._unblinded) { return tx._unblinded; }
let matched = 0;
if (tx.vout !== undefined) {
tx.vout.forEach(vout => matched += +this.tryUnblindOut(vout));
tx.vin.filter(vin => vin.prevout).forEach(vin => matched += +this.tryUnblindOut(vin.prevout));
}
if (this.commitments !== undefined) {
tx._unblinded = { matched, total: this.commitments.size };
this.deduceBlinded(tx);
if (matched < this.commitments.size) {
this.errorUnblinded = `Error: Invalid blinding data.`;
}
tx._deduced = false; // invalidate cache so deduction is attempted again
return tx._unblinded;
}
}
}
// Look the given output and attach the unblinded data
tryUnblindOut(vout: any) {
const unblinded = this.find(vout);
if (unblinded) { Object.assign(vout, unblinded); }
return !!unblinded;
}
// Attempt to deduce the blinded input/output based on the available information
deduceBlinded(tx: any) {
if (tx._deduced) { return; }
tx._deduced = true;
// Find ins/outs with unknown amounts (blinded ant not revealed via the `#blinded` hash fragment)
const unknownIns = tx.vin.filter(vin => vin.prevout && vin.prevout.value == null);
const unknownOuts = tx.vout.filter(vout => vout.value == null);
// If the transaction has a single unknown input/output, we can deduce its asset/amount
// based on the other known inputs/outputs.
if (unknownIns.length + unknownOuts.length === 1) {
// Keep a per-asset tally of all known input amounts, minus all known output amounts
const totals = new Map();
tx.vin.filter(vin => vin.prevout && vin.prevout.value != null)
.forEach(({ prevout }) =>
totals.set(prevout.asset, (totals.get(prevout.asset) || 0) + prevout.value));
tx.vout.filter(vout => vout.value != null)
.forEach(vout =>
totals.set(vout.asset, (totals.get(vout.asset) || 0) - vout.value));
// There should only be a single asset where the inputs and outputs amounts mismatch,
// which is the asset of the blinded input/output
const remainder = Array.from(totals.entries()).filter(([ asset, value ]) => value !== 0);
if (remainder.length !== 1) { throw new Error('unexpected remainder while deducing blinded tx'); }
const [ blindedAsset, blindedValue ] = remainder[0];
// A positive remainder (when known in > known out) is the asset/amount of the unknown blinded output,
// a negative one is the input.
if (blindedValue > 0) {
if (!unknownOuts.length) { throw new Error('expected unknown output'); }
unknownOuts[0].asset = blindedAsset;
unknownOuts[0].value = blindedValue;
} else {
if (!unknownIns.length) { throw new Error('expected unknown input'); }
unknownIns[0].prevout.asset = blindedAsset;
unknownIns[0].prevout.value = blindedValue * -1;
}
}
}
async checkUnblindedTx() {
try {
if (this.network === 'liquid') {
const windowLocationHash = window.location.hash.substring('#blinded='.length);
if (windowLocationHash.length > 0) {
const blinders = this.parseBlinders(windowLocationHash);
if (blinders) {
this.commitments = await this.makeCommitmentMap(blinders);
this.tryUnblindTx(this.tx);
}
}
}
} catch (error) {
this.errorUnblinded = error;
}
}
}

View File

@ -12,13 +12,16 @@
</div>
<div class="clearfix"></div>
</div>
<div class="header-bg box" infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()">
<div *ngIf="errorUnblinded" class="error-unblinded">{{ errorUnblinded }}</div>
<div class="row">
<div class="col">
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
<table class="table table-borderless smaller-text table-sm" style="margin: 0;" id="table-tx-vin">
<tbody>
<ng-template ngFor let-vin [ngForOf]="tx['@vinLimit'] ? tx.vin.slice(0, 10) : tx.vin" [ngForTrackBy]="trackByIndexFn">
<tr>
<tr [ngClass]="assetsMinimal && assetsMinimal[vin.prevout.asset] && vin.prevout.scriptpubkey_address ? 'assetBox' : ''">
<td class="arrow-td">
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
<i class="arrow grey"></i>
@ -66,7 +69,7 @@
</td>
</tr>
<tr *ngIf="displayDetails">
<td colspan="3">
<td colspan="3" class="details-container" >
<table class="table table-striped table-borderless details-table mb-3">
<tbody>
<ng-template [ngIf]="vin.scriptsig">
@ -114,10 +117,10 @@
</div>
<div class="w-100 d-block d-md-none"></div>
<div class="col mobile-bottomcol">
<table class="table table-borderless smaller-text table-xs" style="margin: 0;">
<table class="table table-borderless smaller-text table-sm" style="margin: 0;" id="table-tx-vout">
<tbody>
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] ? tx.vout.slice(0, 10) : tx.vout" [ngForTrackBy]="trackByIndexFn">
<tr>
<tr [ngClass]="assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address ? 'assetBox' : ''">
<td>
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
@ -145,7 +148,7 @@
<ng-template [ngIf]="vout.asset && vout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
<div *ngIf="assetsMinimal && assetsMinimal[vout.asset]">
<ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: vout }"></ng-container>
</div>
</div>
</ng-template>
<ng-template #defaultOutput>
<app-amount [satoshis]="vout.value"></app-amount>
@ -162,7 +165,7 @@
</td>
</tr>
<tr *ngIf="displayDetails">
<td colspan="3">
<td colspan="3" class=" details-container" >
<table class="table table-striped table-borderless details-table mb-3">
<tbody>
<tr *ngIf="vout.scriptpubkey_type">
@ -235,5 +238,4 @@
{{ assetsMinimal[item.asset][0] }}
<br />
<a [routerLink]="['/asset/' | relativeUrl, item.asset]">{{ item.asset | shortenString : 13 }}</a>
<br /><br />
</ng-template>

View File

@ -4,9 +4,10 @@
.arrow {
display: inline-block!important;
position: relative;
position: absolute;
width: 14px;
height: 22px;
margin-top: 10px;
margin-left: -5px;
box-sizing: content-box
}
@ -66,24 +67,6 @@
}
}
.details-table {
margin-top: 5px;
}
.details-table td {
padding: 0.75rem;
&:first-child {
white-space: pre-wrap;
}
}
.details-table td:nth-child(2) {
word-break: break-all;
white-space: normal;
font-family: "Courier New", Courier, monospace;
font-size: 12px;
}
.smaller-text {
font-size: 12px;
@media (min-width: 576px) {
@ -137,4 +120,36 @@
padding: 10px;
margin-bottom: 10px;
margin-top: 10px;
}
.assetBox {
background-color: #653b9c90;
}
.details-container {
padding: 0px;
tr td {
padding: 0.75rem;
font-size: 12px;
&:first-child {
color: #ffffff66;
white-space: pre-wrap;
@media (min-width: 476px) {
white-space: nowrap;
}
}
&:nth-child(2) {
word-break: break-all;
white-space: normal;
font-family: "Courier New", Courier, monospace;
}
}
}
.error-unblinded {
display: block;
width: 100%;
color: #d43131;
text-align: right;
margin-top: 0px;
margin-bottom: 10px;
}

View File

@ -21,6 +21,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
@Input() transactions: Transaction[];
@Input() showConfirmations = false;
@Input() transactionPage = false;
@Input() errorUnblinded = false;
@Output() loadMore = new EventEmitter();

View File

@ -17,6 +17,8 @@ export interface Transaction {
bestDescendant?: BestDescendant | null;
cpfpChecked?: boolean;
deleteAfter?: number;
_unblinded?: any;
_deduced?: boolean;
}
interface Ancestor {

View File

@ -0,0 +1,802 @@
/*
The MIT License (MIT)
Copyright 2021 Blockstream Corp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE
*/
var InitWally = (function () {
var _scriptDir =
typeof document !== "undefined" && document.currentScript
? document.currentScript.src
: undefined;
if (typeof __filename !== "undefined") _scriptDir = _scriptDir || __filename;
return function (InitWally) {
InitWally = InitWally || {};
var Module = typeof InitWally !== "undefined" ? InitWally : {};
var readyPromiseResolve, readyPromiseReject;
Module["ready"] = new Promise(function (resolve, reject) {
readyPromiseResolve = resolve;
readyPromiseReject = reject;
});
var moduleOverrides = {};
var key;
for (key in Module) {
if (Module.hasOwnProperty(key)) {
moduleOverrides[key] = Module[key];
}
}
var arguments_ = [];
var thisProgram = "./this.program";
var quit_ = function (status, toThrow) {
throw toThrow;
};
var ENVIRONMENT_IS_WEB = false;
var ENVIRONMENT_IS_WORKER = false;
var ENVIRONMENT_IS_NODE = false;
var ENVIRONMENT_IS_SHELL = false;
ENVIRONMENT_IS_WEB = typeof window === "object";
ENVIRONMENT_IS_WORKER = typeof importScripts === "function";
ENVIRONMENT_IS_NODE =
typeof process === "object" &&
typeof process.versions === "object" &&
typeof process.versions.node === "string";
ENVIRONMENT_IS_SHELL =
!ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
var scriptDirectory = "";
function locateFile(path) {
if (Module["locateFile"]) {
return Module["locateFile"](path, scriptDirectory);
}
return scriptDirectory + path;
}
var read_, readAsync, readBinary, setWindowTitle;
var nodeFS;
var nodePath;
if (ENVIRONMENT_IS_NODE) {
if (ENVIRONMENT_IS_WORKER) {
scriptDirectory = require("path").dirname(scriptDirectory) + "/";
} else {
scriptDirectory = __dirname + "/";
}
read_ = function shell_read(filename, binary) {
if (!nodeFS) nodeFS = require("fs");
if (!nodePath) nodePath = require("path");
filename = nodePath["normalize"](filename);
return nodeFS["readFileSync"](filename, binary ? null : "utf8");
};
readBinary = function readBinary(filename) {
var ret = read_(filename, true);
if (!ret.buffer) {
ret = new Uint8Array(ret);
}
assert(ret.buffer);
return ret;
};
if (process["argv"].length > 1) {
thisProgram = process["argv"][1].replace(/\\/g, "/");
}
arguments_ = process["argv"].slice(2);
process["on"]("uncaughtException", function (ex) {
if (!(ex instanceof ExitStatus)) {
throw ex;
}
});
process["on"]("unhandledRejection", abort);
quit_ = function (status) {
process["exit"](status);
};
Module["inspect"] = function () {
return "[Emscripten Module object]";
};
} else if (ENVIRONMENT_IS_SHELL) {
if (typeof read != "undefined") {
read_ = function shell_read(f) {
return read(f);
};
}
readBinary = function readBinary(f) {
var data;
if (typeof readbuffer === "function") {
return new Uint8Array(readbuffer(f));
}
data = read(f, "binary");
assert(typeof data === "object");
return data;
};
if (typeof scriptArgs != "undefined") {
arguments_ = scriptArgs;
} else if (typeof arguments != "undefined") {
arguments_ = arguments;
}
if (typeof quit === "function") {
quit_ = function (status) {
quit(status);
};
}
if (typeof print !== "undefined") {
if (typeof console === "undefined") console = {};
console.log = print;
console.warn = console.error =
typeof printErr !== "undefined" ? printErr : print;
}
} else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
if (ENVIRONMENT_IS_WORKER) {
scriptDirectory = self.location.href;
} else if (typeof document !== "undefined" && document.currentScript) {
scriptDirectory = document.currentScript.src;
}
if (_scriptDir) {
scriptDirectory = _scriptDir;
}
if (scriptDirectory.indexOf("blob:") !== 0) {
scriptDirectory = scriptDirectory.substr(
0,
scriptDirectory.lastIndexOf("/") + 1
);
} else {
scriptDirectory = "";
}
{
read_ = function shell_read(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.send(null);
return xhr.responseText;
};
if (ENVIRONMENT_IS_WORKER) {
readBinary = function readBinary(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.responseType = "arraybuffer";
xhr.send(null);
return new Uint8Array(xhr.response);
};
}
readAsync = function readAsync(url, onload, onerror) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.onload = function xhr_onload() {
if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) {
onload(xhr.response);
return;
}
onerror();
};
xhr.onerror = onerror;
xhr.send(null);
};
}
setWindowTitle = function (title) {
document.title = title;
};
} else {
}
var out = Module["print"] || console.log.bind(console);
var err = Module["printErr"] || console.warn.bind(console);
for (key in moduleOverrides) {
if (moduleOverrides.hasOwnProperty(key)) {
Module[key] = moduleOverrides[key];
}
}
moduleOverrides = null;
if (Module["arguments"]) arguments_ = Module["arguments"];
if (Module["thisProgram"]) thisProgram = Module["thisProgram"];
if (Module["quit"]) quit_ = Module["quit"];
var wasmBinary;
if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"];
var noExitRuntime;
if (Module["noExitRuntime"]) noExitRuntime = Module["noExitRuntime"];
if (typeof WebAssembly !== "object") {
abort("no native wasm support detected");
}
function getValue(ptr, type, noSafe) {
type = type || "i8";
if (type.charAt(type.length - 1) === "*") type = "i32";
switch (type) {
case "i1":
return HEAP8[ptr >> 0];
case "i8":
return HEAP8[ptr >> 0];
case "i16":
return HEAP16[ptr >> 1];
case "i32":
return HEAP32[ptr >> 2];
case "i64":
return HEAP32[ptr >> 2];
case "float":
return HEAPF32[ptr >> 2];
case "double":
return HEAPF64[ptr >> 3];
default:
abort("invalid type for getValue: " + type);
}
return null;
}
var wasmMemory;
var ABORT = false;
var EXITSTATUS = 0;
function assert(condition, text) {
if (!condition) {
abort("Assertion failed: " + text);
}
}
function getCFunc(ident) {
var func = Module["_" + ident];
assert(
func,
"Cannot call unknown function " + ident + ", make sure it is exported"
);
return func;
}
function ccall(ident, returnType, argTypes, args, opts) {
var toC = {
string: function (str) {
var ret = 0;
if (str !== null && str !== undefined && str !== 0) {
var len = (str.length << 2) + 1;
ret = stackAlloc(len);
stringToUTF8(str, ret, len);
}
return ret;
},
array: function (arr) {
var ret = stackAlloc(arr.length);
writeArrayToMemory(arr, ret);
return ret;
},
};
function convertReturnValue(ret) {
if (returnType === "string") return UTF8ToString(ret);
if (returnType === "boolean") return Boolean(ret);
return ret;
}
var func = getCFunc(ident);
var cArgs = [];
var stack = 0;
if (args) {
for (var i = 0; i < args.length; i++) {
var converter = toC[argTypes[i]];
if (converter) {
if (stack === 0) stack = stackSave();
cArgs[i] = converter(args[i]);
} else {
cArgs[i] = args[i];
}
}
}
var ret = func.apply(null, cArgs);
ret = convertReturnValue(ret);
if (stack !== 0) stackRestore(stack);
return ret;
}
var UTF8Decoder =
typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined;
function UTF8ArrayToString(heap, idx, maxBytesToRead) {
var endIdx = idx + maxBytesToRead;
var endPtr = idx;
while (heap[endPtr] && !(endPtr >= endIdx)) ++endPtr;
if (endPtr - idx > 16 && heap.subarray && UTF8Decoder) {
return UTF8Decoder.decode(heap.subarray(idx, endPtr));
} else {
var str = "";
while (idx < endPtr) {
var u0 = heap[idx++];
if (!(u0 & 128)) {
str += String.fromCharCode(u0);
continue;
}
var u1 = heap[idx++] & 63;
if ((u0 & 224) == 192) {
str += String.fromCharCode(((u0 & 31) << 6) | u1);
continue;
}
var u2 = heap[idx++] & 63;
if ((u0 & 240) == 224) {
u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
} else {
u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heap[idx++] & 63);
}
if (u0 < 65536) {
str += String.fromCharCode(u0);
} else {
var ch = u0 - 65536;
str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
}
}
}
return str;
}
function UTF8ToString(ptr, maxBytesToRead) {
return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
}
function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) {
if (!(maxBytesToWrite > 0)) return 0;
var startIdx = outIdx;
var endIdx = outIdx + maxBytesToWrite - 1;
for (var i = 0; i < str.length; ++i) {
var u = str.charCodeAt(i);
if (u >= 55296 && u <= 57343) {
var u1 = str.charCodeAt(++i);
u = (65536 + ((u & 1023) << 10)) | (u1 & 1023);
}
if (u <= 127) {
if (outIdx >= endIdx) break;
heap[outIdx++] = u;
} else if (u <= 2047) {
if (outIdx + 1 >= endIdx) break;
heap[outIdx++] = 192 | (u >> 6);
heap[outIdx++] = 128 | (u & 63);
} else if (u <= 65535) {
if (outIdx + 2 >= endIdx) break;
heap[outIdx++] = 224 | (u >> 12);
heap[outIdx++] = 128 | ((u >> 6) & 63);
heap[outIdx++] = 128 | (u & 63);
} else {
if (outIdx + 3 >= endIdx) break;
heap[outIdx++] = 240 | (u >> 18);
heap[outIdx++] = 128 | ((u >> 12) & 63);
heap[outIdx++] = 128 | ((u >> 6) & 63);
heap[outIdx++] = 128 | (u & 63);
}
}
heap[outIdx] = 0;
return outIdx - startIdx;
}
function stringToUTF8(str, outPtr, maxBytesToWrite) {
return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
}
function writeArrayToMemory(array, buffer) {
HEAP8.set(array, buffer);
}
var buffer,
HEAP8,
HEAPU8,
HEAP16,
HEAPU16,
HEAP32,
HEAPU32,
HEAPF32,
HEAPF64;
function updateGlobalBufferAndViews(buf) {
buffer = buf;
Module["HEAP8"] = HEAP8 = new Int8Array(buf);
Module["HEAP16"] = HEAP16 = new Int16Array(buf);
Module["HEAP32"] = HEAP32 = new Int32Array(buf);
Module["HEAPU8"] = HEAPU8 = new Uint8Array(buf);
Module["HEAPU16"] = HEAPU16 = new Uint16Array(buf);
Module["HEAPU32"] = HEAPU32 = new Uint32Array(buf);
Module["HEAPF32"] = HEAPF32 = new Float32Array(buf);
Module["HEAPF64"] = HEAPF64 = new Float64Array(buf);
}
var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 16777216;
if (Module["wasmMemory"]) {
wasmMemory = Module["wasmMemory"];
} else {
wasmMemory = new WebAssembly.Memory({
initial: INITIAL_MEMORY / 65536,
maximum: INITIAL_MEMORY / 65536,
});
}
if (wasmMemory) {
buffer = wasmMemory.buffer;
}
INITIAL_MEMORY = buffer.byteLength;
updateGlobalBufferAndViews(buffer);
var wasmTable;
var __ATPRERUN__ = [];
var __ATINIT__ = [];
var __ATMAIN__ = [];
var __ATPOSTRUN__ = [];
var runtimeInitialized = false;
function preRun() {
if (Module["preRun"]) {
if (typeof Module["preRun"] == "function")
Module["preRun"] = [Module["preRun"]];
while (Module["preRun"].length) {
addOnPreRun(Module["preRun"].shift());
}
}
callRuntimeCallbacks(__ATPRERUN__);
}
function initRuntime() {
runtimeInitialized = true;
callRuntimeCallbacks(__ATINIT__);
}
function preMain() {
callRuntimeCallbacks(__ATMAIN__);
}
function postRun() {
if (Module["postRun"]) {
if (typeof Module["postRun"] == "function")
Module["postRun"] = [Module["postRun"]];
while (Module["postRun"].length) {
addOnPostRun(Module["postRun"].shift());
}
}
callRuntimeCallbacks(__ATPOSTRUN__);
}
function addOnPreRun(cb) {
__ATPRERUN__.unshift(cb);
}
function addOnPostRun(cb) {
__ATPOSTRUN__.unshift(cb);
}
var runDependencies = 0;
var runDependencyWatcher = null;
var dependenciesFulfilled = null;
function addRunDependency(id) {
runDependencies++;
if (Module["monitorRunDependencies"]) {
Module["monitorRunDependencies"](runDependencies);
}
}
function removeRunDependency(id) {
runDependencies--;
if (Module["monitorRunDependencies"]) {
Module["monitorRunDependencies"](runDependencies);
}
if (runDependencies == 0) {
if (runDependencyWatcher !== null) {
clearInterval(runDependencyWatcher);
runDependencyWatcher = null;
}
if (dependenciesFulfilled) {
var callback = dependenciesFulfilled;
dependenciesFulfilled = null;
callback();
}
}
}
Module["preloadedImages"] = {};
Module["preloadedAudios"] = {};
function abort(what) {
if (Module["onAbort"]) {
Module["onAbort"](what);
}
what += "";
err(what);
ABORT = true;
EXITSTATUS = 1;
what = "abort(" + what + "). Build with -s ASSERTIONS=1 for more info.";
var e = new WebAssembly.RuntimeError(what);
readyPromiseReject(e);
throw e;
}
function hasPrefix(str, prefix) {
return String.prototype.startsWith
? str.startsWith(prefix)
: str.indexOf(prefix) === 0;
}
var dataURIPrefix = "data:application/octet-stream;base64,";
function isDataURI(filename) {
return hasPrefix(filename, dataURIPrefix);
}
var fileURIPrefix = "file://";
function isFileURI(filename) {
return hasPrefix(filename, fileURIPrefix);
}
var wasmBinaryFile = "wallycore.wasm";
if (!isDataURI(wasmBinaryFile)) {
wasmBinaryFile = locateFile(wasmBinaryFile);
}
function getBinary() {
try {
if (wasmBinary) {
return new Uint8Array(wasmBinary);
}
if (readBinary) {
return readBinary(wasmBinaryFile);
} else {
throw "both async and sync fetching of the wasm failed";
}
} catch (err) {
abort(err);
}
}
function getBinaryPromise() {
if (
!wasmBinary &&
(ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) &&
typeof fetch === "function" &&
!isFileURI(wasmBinaryFile)
) {
return fetch(wasmBinaryFile, { credentials: "same-origin" })
.then(function (response) {
if (!response["ok"]) {
throw (
"failed to load wasm binary file at '" + wasmBinaryFile + "'"
);
}
return response["arrayBuffer"]();
})
.catch(function () {
return getBinary();
});
}
return Promise.resolve().then(getBinary);
}
function createWasm() {
var info = { a: asmLibraryArg };
function receiveInstance(instance, module) {
var exports = instance.exports;
Module["asm"] = exports;
wasmTable = Module["asm"]["h"];
removeRunDependency("wasm-instantiate");
}
addRunDependency("wasm-instantiate");
function receiveInstantiatedSource(output) {
receiveInstance(output["instance"]);
}
function instantiateArrayBuffer(receiver) {
return getBinaryPromise()
.then(function (binary) {
return WebAssembly.instantiate(binary, info);
})
.then(receiver, function (reason) {
err("failed to asynchronously prepare wasm: " + reason);
abort(reason);
});
}
function instantiateAsync() {
if (
!wasmBinary &&
typeof WebAssembly.instantiateStreaming === "function" &&
!isDataURI(wasmBinaryFile) &&
!isFileURI(wasmBinaryFile) &&
typeof fetch === "function"
) {
return fetch(wasmBinaryFile, { credentials: "same-origin" }).then(
function (response) {
var result = WebAssembly.instantiateStreaming(response, info);
return result.then(receiveInstantiatedSource, function (reason) {
err("wasm streaming compile failed: " + reason);
err("falling back to ArrayBuffer instantiation");
return instantiateArrayBuffer(receiveInstantiatedSource);
});
}
);
} else {
return instantiateArrayBuffer(receiveInstantiatedSource);
}
}
if (Module["instantiateWasm"]) {
try {
var exports = Module["instantiateWasm"](info, receiveInstance);
return exports;
} catch (e) {
err("Module.instantiateWasm callback failed with error: " + e);
return false;
}
}
instantiateAsync().catch(readyPromiseReject);
return {};
}
function callRuntimeCallbacks(callbacks) {
while (callbacks.length > 0) {
var callback = callbacks.shift();
if (typeof callback == "function") {
callback(Module);
continue;
}
var func = callback.func;
if (typeof func === "number") {
if (callback.arg === undefined) {
wasmTable.get(func)();
} else {
wasmTable.get(func)(callback.arg);
}
} else {
func(callback.arg === undefined ? null : callback.arg);
}
}
}
function _abort() {
abort();
}
function _emscripten_memcpy_big(dest, src, num) {
HEAPU8.copyWithin(dest, src, src + num);
}
function abortOnCannotGrowMemory(requestedSize) {
abort("OOM");
}
function _emscripten_resize_heap(requestedSize) {
requestedSize = requestedSize >>> 0;
abortOnCannotGrowMemory(requestedSize);
}
var SYSCALLS = {
mappings: {},
buffers: [null, [], []],
printChar: function (stream, curr) {
var buffer = SYSCALLS.buffers[stream];
if (curr === 0 || curr === 10) {
(stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0));
buffer.length = 0;
} else {
buffer.push(curr);
}
},
varargs: undefined,
get: function () {
SYSCALLS.varargs += 4;
var ret = HEAP32[(SYSCALLS.varargs - 4) >> 2];
return ret;
},
getStr: function (ptr) {
var ret = UTF8ToString(ptr);
return ret;
},
get64: function (low, high) {
return low;
},
};
function _fd_close(fd) {
return 0;
}
function _fd_seek(fd, offset_low, offset_high, whence, newOffset) {}
function _fd_write(fd, iov, iovcnt, pnum) {
var num = 0;
for (var i = 0; i < iovcnt; i++) {
var ptr = HEAP32[(iov + i * 8) >> 2];
var len = HEAP32[(iov + (i * 8 + 4)) >> 2];
for (var j = 0; j < len; j++) {
SYSCALLS.printChar(fd, HEAPU8[ptr + j]);
}
num += len;
}
HEAP32[pnum >> 2] = num;
return 0;
}
__ATINIT__.push({
func: function () {
___wasm_call_ctors();
},
});
var asmLibraryArg = {
b: _abort,
e: _emscripten_memcpy_big,
f: _emscripten_resize_heap,
g: _fd_close,
d: _fd_seek,
c: _fd_write,
a: wasmMemory,
};
var asm = createWasm();
var ___wasm_call_ctors = (Module["___wasm_call_ctors"] = function () {
return (___wasm_call_ctors = Module["___wasm_call_ctors"] =
Module["asm"]["i"]).apply(null, arguments);
});
var _wally_asset_generator_from_bytes = (Module[
"_wally_asset_generator_from_bytes"
] = function () {
return (_wally_asset_generator_from_bytes = Module[
"_wally_asset_generator_from_bytes"
] =
Module["asm"]["j"]).apply(null, arguments);
});
var _wally_asset_value_commitment = (Module[
"_wally_asset_value_commitment"
] = function () {
return (_wally_asset_value_commitment = Module[
"_wally_asset_value_commitment"
] =
Module["asm"]["k"]).apply(null, arguments);
});
var _wally_init = (Module["_wally_init"] = function () {
return (_wally_init = Module["_wally_init"] = Module["asm"]["l"]).apply(
null,
arguments
);
});
var _malloc = (Module["_malloc"] = function () {
return (_malloc = Module["_malloc"] = Module["asm"]["m"]).apply(
null,
arguments
);
});
var _free = (Module["_free"] = function () {
return (_free = Module["_free"] = Module["asm"]["n"]).apply(
null,
arguments
);
});
var stackSave = (Module["stackSave"] = function () {
return (stackSave = Module["stackSave"] = Module["asm"]["o"]).apply(
null,
arguments
);
});
var stackRestore = (Module["stackRestore"] = function () {
return (stackRestore = Module["stackRestore"] = Module["asm"]["p"]).apply(
null,
arguments
);
});
var stackAlloc = (Module["stackAlloc"] = function () {
return (stackAlloc = Module["stackAlloc"] = Module["asm"]["q"]).apply(
null,
arguments
);
});
Module["ccall"] = ccall;
Module["getValue"] = getValue;
var calledRun;
function ExitStatus(status) {
this.name = "ExitStatus";
this.message = "Program terminated with exit(" + status + ")";
this.status = status;
}
dependenciesFulfilled = function runCaller() {
if (!calledRun) run();
if (!calledRun) dependenciesFulfilled = runCaller;
};
function run(args) {
args = args || arguments_;
if (runDependencies > 0) {
return;
}
preRun();
if (runDependencies > 0) return;
function doRun() {
if (calledRun) return;
calledRun = true;
Module["calledRun"] = true;
if (ABORT) return;
initRuntime();
preMain();
readyPromiseResolve(Module);
if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"]();
postRun();
}
if (Module["setStatus"]) {
Module["setStatus"]("Running...");
setTimeout(function () {
setTimeout(function () {
Module["setStatus"]("");
}, 1);
doRun();
}, 1);
} else {
doRun();
}
}
Module["run"] = run;
if (Module["preInit"]) {
if (typeof Module["preInit"] == "function")
Module["preInit"] = [Module["preInit"]];
while (Module["preInit"].length > 0) {
Module["preInit"].pop()();
}
}
noExitRuntime = true;
run();
return InitWally.ready;
};
})();
if (typeof exports === "object" && typeof module === "object")
module.exports = InitWally;
else if (typeof define === "function" && define["amd"])
define([], function () {
return InitWally;
});
else if (typeof exports === "object") exports["InitWally"] = InitWally;

Binary file not shown.