mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2025-11-05 11:56:37 +00:00
Compare commits
No commits in common. "master" and "2.2.3" have entirely different histories.
95 changed files with 1476 additions and 1563 deletions
4
.github/workflows/package.yaml
vendored
4
.github/workflows/package.yaml
vendored
|
|
@ -12,11 +12,11 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-2022, ubuntu-22.04, ubuntu-22.04-arm, macos-13, macos-14]
|
os: [windows-2022, ubuntu-22.04, ubuntu-22.04-arm, macos-13, macos-14]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Set up JDK 22.0.2
|
- name: Set up JDK 22.0.2
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '22.0.2'
|
java-version: '22.0.2'
|
||||||
|
|
|
||||||
91
build.gradle
91
build.gradle
|
|
@ -1,8 +1,8 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'org-openjfx-javafxplugin'
|
id 'org-openjfx-javafxplugin'
|
||||||
id 'org.beryx.jlink' version '3.1.3'
|
id 'org.beryx.jlink' version '3.1.1'
|
||||||
id 'org.gradlex.extra-java-module-info' version '1.13'
|
id 'org.gradlex.extra-java-module-info' version '1.9'
|
||||||
id 'io.matthewnelson.kmp.tor.resource-filterjar' version '408.16.3'
|
id 'io.matthewnelson.kmp.tor.resource-filterjar' version '408.16.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,16 +19,17 @@ if(System.getProperty("os.arch") == "aarch64") {
|
||||||
}
|
}
|
||||||
def headless = "true".equals(System.getProperty("java.awt.headless"))
|
def headless = "true".equals(System.getProperty("java.awt.headless"))
|
||||||
|
|
||||||
group = 'com.sparrowwallet'
|
group 'com.sparrowwallet'
|
||||||
version = '2.3.1'
|
version '2.2.3'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = uri('https://code.sparrowwallet.com/api/packages/sparrowwallet/maven') }
|
maven { url 'https://code.sparrowwallet.com/api/packages/sparrowwallet/maven' }
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(AbstractArchiveTask).configureEach {
|
tasks.withType(AbstractArchiveTask) {
|
||||||
useFileSystemPermissions()
|
preserveFileTimestamps = false
|
||||||
|
reproducibleFileOrder = true
|
||||||
}
|
}
|
||||||
|
|
||||||
javafx {
|
javafx {
|
||||||
|
|
@ -44,20 +45,20 @@ dependencies {
|
||||||
//Any changes to the dependencies must be reflected in the module definitions below!
|
//Any changes to the dependencies must be reflected in the module definitions below!
|
||||||
implementation(project(':drongo'))
|
implementation(project(':drongo'))
|
||||||
implementation(project(':lark'))
|
implementation(project(':lark'))
|
||||||
implementation('com.google.guava:guava:33.5.0-jre')
|
implementation('com.google.guava:guava:33.0.0-jre')
|
||||||
implementation('com.google.code.gson:gson:2.9.1')
|
implementation('com.google.code.gson:gson:2.9.1')
|
||||||
implementation('com.h2database:h2:2.1.214')
|
implementation('com.h2database:h2:2.1.214')
|
||||||
implementation('com.zaxxer:HikariCP:4.0.3') {
|
implementation('com.zaxxer:HikariCP:4.0.3') {
|
||||||
exclude group: 'org.slf4j'
|
exclude group: 'org.slf4j'
|
||||||
}
|
}
|
||||||
implementation('org.jdbi:jdbi3-core:3.49.5') {
|
implementation('org.jdbi:jdbi3-core:3.20.0') {
|
||||||
exclude group: 'org.slf4j'
|
exclude group: 'org.slf4j'
|
||||||
}
|
}
|
||||||
implementation('org.jdbi:jdbi3-sqlobject:3.49.5') {
|
implementation('org.jdbi:jdbi3-sqlobject:3.20.0') {
|
||||||
exclude group: 'org.slf4j'
|
exclude group: 'org.slf4j'
|
||||||
}
|
}
|
||||||
implementation('org.flywaydb:flyway-core:9.22.3')
|
implementation('org.flywaydb:flyway-core:9.22.3')
|
||||||
implementation('org.fxmisc.richtext:richtextfx:0.11.6')
|
implementation('org.fxmisc.richtext:richtextfx:0.10.4')
|
||||||
implementation('no.tornado:tornadofx-controls:1.0.4')
|
implementation('no.tornado:tornadofx-controls:1.0.4')
|
||||||
implementation('com.google.zxing:javase:3.4.0') {
|
implementation('com.google.zxing:javase:3.4.0') {
|
||||||
exclude group: 'com.beust', module: 'jcommander'
|
exclude group: 'com.beust', module: 'jcommander'
|
||||||
|
|
@ -73,15 +74,13 @@ dependencies {
|
||||||
implementation('com.fasterxml.jackson.core:jackson-databind:2.17.2')
|
implementation('com.fasterxml.jackson.core:jackson-databind:2.17.2')
|
||||||
implementation('com.sparrowwallet:hummingbird:1.7.4')
|
implementation('com.sparrowwallet:hummingbird:1.7.4')
|
||||||
implementation('co.nstant.in:cbor:0.9')
|
implementation('co.nstant.in:cbor:0.9')
|
||||||
implementation('org.openpnp:openpnp-capture-java:0.0.30-1')
|
implementation('org.openpnp:openpnp-capture-java:0.0.28-6')
|
||||||
implementation("io.matthewnelson.kmp-tor:runtime:2.2.1")
|
implementation("io.matthewnelson.kmp-tor:runtime:2.2.1")
|
||||||
implementation("io.matthewnelson.kmp-tor:resource-exec-tor-gpl:408.16.3")
|
implementation("io.matthewnelson.kmp-tor:resource-exec-tor-gpl:408.16.3")
|
||||||
implementation('org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.10.1') {
|
implementation('org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.10.1') {
|
||||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-common'
|
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-common'
|
||||||
}
|
}
|
||||||
implementation('de.jangassen:nsmenufx:3.1.0') {
|
implementation('de.codecentric.centerdevice:centerdevice-nsmenufx:2.1.7')
|
||||||
exclude group: 'net.java.dev.jna', module: 'jna'
|
|
||||||
}
|
|
||||||
implementation('org.controlsfx:controlsfx:11.1.0' ) {
|
implementation('org.controlsfx:controlsfx:11.1.0' ) {
|
||||||
exclude group: 'org.openjfx', module: 'javafx-base'
|
exclude group: 'org.openjfx', module: 'javafx-base'
|
||||||
exclude group: 'org.openjfx', module: 'javafx-graphics'
|
exclude group: 'org.openjfx', module: 'javafx-graphics'
|
||||||
|
|
@ -101,7 +100,7 @@ dependencies {
|
||||||
implementation('com.sparrowwallet:tern:1.0.6')
|
implementation('com.sparrowwallet:tern:1.0.6')
|
||||||
implementation('io.reactivex.rxjava2:rxjava:2.2.15')
|
implementation('io.reactivex.rxjava2:rxjava:2.2.15')
|
||||||
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
|
implementation('io.reactivex.rxjava2:rxjavafx:2.2.2')
|
||||||
implementation('org.apache.commons:commons-lang3:3.19.0')
|
implementation('org.apache.commons:commons-lang3:3.7')
|
||||||
implementation('org.apache.commons:commons-compress:1.27.1')
|
implementation('org.apache.commons:commons-compress:1.27.1')
|
||||||
implementation('net.sourceforge.streamsupport:streamsupport:1.7.0')
|
implementation('net.sourceforge.streamsupport:streamsupport:1.7.0')
|
||||||
implementation('com.github.librepdf:openpdf:1.3.30')
|
implementation('com.github.librepdf:openpdf:1.3.30')
|
||||||
|
|
@ -110,7 +109,6 @@ dependencies {
|
||||||
implementation('com.github.hervegirod:fxsvgimage:1.1')
|
implementation('com.github.hervegirod:fxsvgimage:1.1')
|
||||||
implementation('com.sparrowwallet:toucan:0.9.0')
|
implementation('com.sparrowwallet:toucan:0.9.0')
|
||||||
implementation('com.jcraft:jzlib:1.1.3')
|
implementation('com.jcraft:jzlib:1.1.3')
|
||||||
implementation('io.github.doblon8:jzbar:0.2.1')
|
|
||||||
testImplementation('org.junit.jupiter:junit-jupiter-api:5.10.0')
|
testImplementation('org.junit.jupiter:junit-jupiter-api:5.10.0')
|
||||||
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.10.0')
|
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.10.0')
|
||||||
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
|
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
|
||||||
|
|
@ -143,12 +141,6 @@ application {
|
||||||
mainClass = 'com.sparrowwallet.sparrow.SparrowWallet'
|
mainClass = 'com.sparrowwallet.sparrow.SparrowWallet'
|
||||||
|
|
||||||
applicationDefaultJvmArgs = ["-XX:+HeapDumpOnOutOfMemoryError",
|
applicationDefaultJvmArgs = ["-XX:+HeapDumpOnOutOfMemoryError",
|
||||||
"--enable-native-access=com.sparrowwallet.drongo",
|
|
||||||
"--enable-native-access=com.sun.jna",
|
|
||||||
"--enable-native-access=javafx.graphics",
|
|
||||||
"--enable-native-access=com.fazecast.jSerialComm",
|
|
||||||
"--enable-native-access=org.usb4java",
|
|
||||||
"--enable-native-access=io.github.doblon8.jzbar",
|
|
||||||
"--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls",
|
"--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls",
|
||||||
"--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls",
|
"--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls",
|
||||||
"--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls",
|
"--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls",
|
||||||
|
|
@ -158,6 +150,11 @@ application {
|
||||||
"--add-opens=javafx.controls/javafx.scene.control.cell=com.sparrowwallet.sparrow",
|
"--add-opens=javafx.controls/javafx.scene.control.cell=com.sparrowwallet.sparrow",
|
||||||
"--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=com.sparrowwallet.sparrow",
|
"--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=com.sparrowwallet.sparrow",
|
||||||
"--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=javafx.fxml",
|
"--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=javafx.fxml",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.javafx.tk=centerdevice.nsmenufx",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.javafx.tk.quantum=centerdevice.nsmenufx",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.glass.ui=centerdevice.nsmenufx",
|
||||||
|
"--add-opens=javafx.controls/com.sun.javafx.scene.control=centerdevice.nsmenufx",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.javafx.menu=centerdevice.nsmenufx",
|
||||||
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
|
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
|
||||||
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
|
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
|
||||||
"--add-opens=javafx.graphics/javafx.scene.input=com.sparrowwallet.sparrow",
|
"--add-opens=javafx.graphics/javafx.scene.input=com.sparrowwallet.sparrow",
|
||||||
|
|
@ -168,7 +165,8 @@ application {
|
||||||
"--add-reads=org.flywaydb.core=java.desktop"]
|
"--add-reads=org.flywaydb.core=java.desktop"]
|
||||||
|
|
||||||
if(os.macOsX) {
|
if(os.macOsX) {
|
||||||
applicationDefaultJvmArgs += ["-Dprism.lcdtext=false", "-Xdock:name=Sparrow"]
|
applicationDefaultJvmArgs += ["-Dprism.lcdtext=false", "-Xdock:name=Sparrow",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.glass.ui.mac=centerdevice.nsmenufx"]
|
||||||
}
|
}
|
||||||
if(headless) {
|
if(headless) {
|
||||||
applicationDefaultJvmArgs += ["-Dglass.platform=Monocle", "-Dmonocle.platform=Headless", "-Dprism.order=sw"]
|
applicationDefaultJvmArgs += ["-Dglass.platform=Monocle", "-Dmonocle.platform=Headless", "-Dprism.order=sw"]
|
||||||
|
|
@ -191,14 +189,7 @@ jlink {
|
||||||
options = ['--strip-native-commands', '--strip-java-debug-attributes', '--compress', 'zip-6', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png', '--exclude-resources', 'glob:/com.sparrowwallet.merged.module/META-INF/*']
|
options = ['--strip-native-commands', '--strip-java-debug-attributes', '--compress', 'zip-6', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png', '--exclude-resources', 'glob:/com.sparrowwallet.merged.module/META-INF/*']
|
||||||
launcher {
|
launcher {
|
||||||
name = 'sparrow'
|
name = 'sparrow'
|
||||||
jvmArgs = ["--enable-native-access=com.sparrowwallet.drongo",
|
jvmArgs = ["--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls",
|
||||||
"--enable-native-access=com.sun.jna",
|
|
||||||
"--enable-native-access=javafx.graphics",
|
|
||||||
"--enable-native-access=com.sparrowwallet.merged.module",
|
|
||||||
"--enable-native-access=com.fazecast.jSerialComm",
|
|
||||||
"--enable-native-access=org.usb4java",
|
|
||||||
"--enable-native-access=io.github.doblon8.jzbar",
|
|
||||||
"--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls",
|
|
||||||
"--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls",
|
"--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls",
|
||||||
"--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls",
|
"--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls",
|
||||||
"--add-opens=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls",
|
"--add-opens=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls",
|
||||||
|
|
@ -207,6 +198,11 @@ jlink {
|
||||||
"--add-opens=javafx.controls/javafx.scene.control.cell=com.sparrowwallet.sparrow",
|
"--add-opens=javafx.controls/javafx.scene.control.cell=com.sparrowwallet.sparrow",
|
||||||
"--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=com.sparrowwallet.sparrow",
|
"--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=com.sparrowwallet.sparrow",
|
||||||
"--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=javafx.fxml",
|
"--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=javafx.fxml",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.javafx.tk=centerdevice.nsmenufx",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.javafx.tk.quantum=centerdevice.nsmenufx",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.glass.ui=centerdevice.nsmenufx",
|
||||||
|
"--add-opens=javafx.controls/com.sun.javafx.scene.control=centerdevice.nsmenufx",
|
||||||
|
"--add-opens=javafx.graphics/com.sun.javafx.menu=centerdevice.nsmenufx",
|
||||||
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
|
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
|
||||||
"--add-opens=javafx.graphics/javafx.scene.input=com.sparrowwallet.sparrow",
|
"--add-opens=javafx.graphics/javafx.scene.input=com.sparrowwallet.sparrow",
|
||||||
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
|
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
|
||||||
|
|
@ -225,7 +221,6 @@ jlink {
|
||||||
"--add-reads=com.sparrowwallet.merged.module=org.bouncycastle.pg",
|
"--add-reads=com.sparrowwallet.merged.module=org.bouncycastle.pg",
|
||||||
"--add-reads=com.sparrowwallet.merged.module=org.bouncycastle.provider",
|
"--add-reads=com.sparrowwallet.merged.module=org.bouncycastle.provider",
|
||||||
"--add-reads=com.sparrowwallet.merged.module=kotlin.stdlib",
|
"--add-reads=com.sparrowwallet.merged.module=kotlin.stdlib",
|
||||||
"--add-reads=com.sparrowwallet.merged.module=org.reactfx.reactfx",
|
|
||||||
"--add-reads=kotlin.stdlib=kotlinx.coroutines.core",
|
"--add-reads=kotlin.stdlib=kotlinx.coroutines.core",
|
||||||
"--add-reads=org.flywaydb.core=java.desktop"]
|
"--add-reads=org.flywaydb.core=java.desktop"]
|
||||||
|
|
||||||
|
|
@ -233,7 +228,7 @@ jlink {
|
||||||
jvmArgs += ["-Djavax.accessibility.assistive_technologies", "-Djavax.accessibility.screen_magnifier_present=false"]
|
jvmArgs += ["-Djavax.accessibility.assistive_technologies", "-Djavax.accessibility.screen_magnifier_present=false"]
|
||||||
}
|
}
|
||||||
if(os.macOsX) {
|
if(os.macOsX) {
|
||||||
jvmArgs += ["-Dprism.lcdtext=false", "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module"]
|
jvmArgs += ["-Dprism.lcdtext=false", "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module", "--add-opens=javafx.graphics/com.sun.glass.ui.mac=centerdevice.nsmenufx"]
|
||||||
}
|
}
|
||||||
if(headless) {
|
if(headless) {
|
||||||
jvmArgs += ["-Dglass.platform=Monocle", "-Dmonocle.platform=Headless", "-Dprism.order=sw"]
|
jvmArgs += ["-Dglass.platform=Monocle", "-Dmonocle.platform=Headless", "-Dprism.order=sw"]
|
||||||
|
|
@ -285,8 +280,7 @@ if(os.linux) {
|
||||||
|
|
||||||
tasks.register('addUserWritePermission', Exec) {
|
tasks.register('addUserWritePermission', Exec) {
|
||||||
if(os.windows) {
|
if(os.windows) {
|
||||||
def usersGroup = '*S-1-5-32-545' // Windows "Users" group SID (language-independent)
|
commandLine 'icacls', "$buildDir\\image\\legal", '/grant', 'Users:(OI)(CI)F', '/T'
|
||||||
commandLine 'icacls', "$buildDir\\image\\legal", '/grant', "${usersGroup}:(OI)(CI)F", '/T'
|
|
||||||
} else {
|
} else {
|
||||||
commandLine 'chmod', '-R', 'u+w', "$buildDir/image/legal"
|
commandLine 'chmod', '-R', 'u+w', "$buildDir/image/legal"
|
||||||
}
|
}
|
||||||
|
|
@ -386,12 +380,33 @@ extraJavaModuleInfo {
|
||||||
requires('java.desktop')
|
requires('java.desktop')
|
||||||
requires('com.sun.jna')
|
requires('com.sun.jna')
|
||||||
}
|
}
|
||||||
|
module('de.codecentric.centerdevice:centerdevice-nsmenufx', 'centerdevice.nsmenufx') {
|
||||||
|
exports('de.codecentric.centerdevice')
|
||||||
|
requires('javafx.base')
|
||||||
|
requires('javafx.controls')
|
||||||
|
requires('javafx.graphics')
|
||||||
|
}
|
||||||
module('net.sourceforge.javacsv:javacsv', 'net.sourceforge.javacsv') {
|
module('net.sourceforge.javacsv:javacsv', 'net.sourceforge.javacsv') {
|
||||||
exports('com.csvreader')
|
exports('com.csvreader')
|
||||||
}
|
}
|
||||||
module('com.google.guava:listenablefuture|empty-to-avoid-conflict-with-guava', 'com.google.guava.listenablefuture')
|
module('com.google.guava:listenablefuture|empty-to-avoid-conflict-with-guava', 'com.google.guava.listenablefuture')
|
||||||
module('com.google.code.findbugs:jsr305', 'com.google.code.findbugs.jsr305')
|
module('com.google.code.findbugs:jsr305', 'com.google.code.findbugs.jsr305')
|
||||||
module('j2objc-annotations-2.8.jar', 'com.google.j2objc.j2objc.annotations', '2.8')
|
module('j2objc-annotations-2.8.jar', 'com.google.j2objc.j2objc.annotations', '2.8')
|
||||||
|
module('org.jdbi:jdbi3-core', 'org.jdbi.v3.core') {
|
||||||
|
exports('org.jdbi.v3.core')
|
||||||
|
exports('org.jdbi.v3.core.mapper')
|
||||||
|
exports('org.jdbi.v3.core.statement')
|
||||||
|
exports('org.jdbi.v3.core.result')
|
||||||
|
exports('org.jdbi.v3.core.h2')
|
||||||
|
exports('org.jdbi.v3.core.spi')
|
||||||
|
requires('io.leangen.geantyref')
|
||||||
|
requires('java.sql')
|
||||||
|
requires('org.slf4j')
|
||||||
|
requires('com.github.benmanes.caffeine')
|
||||||
|
}
|
||||||
|
module('io.leangen.geantyref:geantyref', 'io.leangen.geantyref') {
|
||||||
|
exports('io.leangen.geantyref')
|
||||||
|
}
|
||||||
module('org.fxmisc.richtext:richtextfx', 'org.fxmisc.richtext') {
|
module('org.fxmisc.richtext:richtextfx', 'org.fxmisc.richtext') {
|
||||||
exports('org.fxmisc.richtext')
|
exports('org.fxmisc.richtext')
|
||||||
exports('org.fxmisc.richtext.event')
|
exports('org.fxmisc.richtext.event')
|
||||||
|
|
@ -401,10 +416,10 @@ extraJavaModuleInfo {
|
||||||
requires('javafx.graphics')
|
requires('javafx.graphics')
|
||||||
requires('org.fxmisc.flowless')
|
requires('org.fxmisc.flowless')
|
||||||
requires('org.reactfx.reactfx')
|
requires('org.reactfx.reactfx')
|
||||||
requires('org.fxmisc.undo')
|
requires('org.fxmisc.undo.undofx')
|
||||||
requires('org.fxmisc.wellbehaved')
|
requires('org.fxmisc.wellbehaved')
|
||||||
}
|
}
|
||||||
module('org.fxmisc.undo:undofx', 'org.fxmisc.undo') {
|
module('org.fxmisc.undo:undofx', 'org.fxmisc.undo.undofx') {
|
||||||
requires('javafx.base')
|
requires('javafx.base')
|
||||||
requires('javafx.controls')
|
requires('javafx.controls')
|
||||||
requires('javafx.graphics')
|
requires('javafx.graphics')
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ plugins {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.gradle:osdetector-gradle-plugin:1.7.3'
|
implementation 'com.google.gradle:osdetector-gradle-plugin:1.7.3'
|
||||||
|
implementation 'org.javamodularity:moduleplugin:1.8.14'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://plugins.gradle.org/m2/")
|
url "https://plugins.gradle.org/m2/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ package org.openjfx.gradle;
|
||||||
import com.google.gradle.osdetector.OsDetectorPlugin;
|
import com.google.gradle.osdetector.OsDetectorPlugin;
|
||||||
import org.gradle.api.Plugin;
|
import org.gradle.api.Plugin;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
|
import org.javamodularity.moduleplugin.ModuleSystemPlugin;
|
||||||
import org.openjfx.gradle.tasks.ExecTask;
|
import org.openjfx.gradle.tasks.ExecTask;
|
||||||
|
|
||||||
public class JavaFXPlugin implements Plugin<Project> {
|
public class JavaFXPlugin implements Plugin<Project> {
|
||||||
|
|
@ -39,9 +40,10 @@ public class JavaFXPlugin implements Plugin<Project> {
|
||||||
@Override
|
@Override
|
||||||
public void apply(Project project) {
|
public void apply(Project project) {
|
||||||
project.getPlugins().apply(OsDetectorPlugin.class);
|
project.getPlugins().apply(OsDetectorPlugin.class);
|
||||||
|
project.getPlugins().apply(ModuleSystemPlugin.class);
|
||||||
|
|
||||||
project.getExtensions().create("javafx", JavaFXOptions.class, project);
|
project.getExtensions().create("javafx", JavaFXOptions.class, project);
|
||||||
|
|
||||||
project.getTasks().register("configJavafxRun", ExecTask.class, project);
|
project.getTasks().create("configJavafxRun", ExecTask.class, project);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,19 +33,27 @@ import org.gradle.api.DefaultTask;
|
||||||
import org.gradle.api.GradleException;
|
import org.gradle.api.GradleException;
|
||||||
import org.gradle.api.Project;
|
import org.gradle.api.Project;
|
||||||
import org.gradle.api.file.FileCollection;
|
import org.gradle.api.file.FileCollection;
|
||||||
|
import org.gradle.api.logging.Logger;
|
||||||
|
import org.gradle.api.logging.Logging;
|
||||||
import org.gradle.api.plugins.ApplicationPlugin;
|
import org.gradle.api.plugins.ApplicationPlugin;
|
||||||
import org.gradle.api.tasks.JavaExec;
|
import org.gradle.api.tasks.JavaExec;
|
||||||
import org.gradle.api.tasks.TaskAction;
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
import org.javamodularity.moduleplugin.extensions.RunModuleOptions;
|
||||||
import org.openjfx.gradle.JavaFXModule;
|
import org.openjfx.gradle.JavaFXModule;
|
||||||
import org.openjfx.gradle.JavaFXOptions;
|
import org.openjfx.gradle.JavaFXOptions;
|
||||||
import org.openjfx.gradle.JavaFXPlatform;
|
import org.openjfx.gradle.JavaFXPlatform;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
public class ExecTask extends DefaultTask {
|
public class ExecTask extends DefaultTask {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logging.getLogger(ExecTask.class);
|
||||||
|
|
||||||
private final Project project;
|
private final Project project;
|
||||||
private JavaExec execTask;
|
private JavaExec execTask;
|
||||||
|
|
||||||
|
|
@ -70,11 +78,37 @@ public class ExecTask extends DefaultTask {
|
||||||
|
|
||||||
var definedJavaFXModuleNames = new TreeSet<>(javaFXOptions.getModules());
|
var definedJavaFXModuleNames = new TreeSet<>(javaFXOptions.getModules());
|
||||||
if (!definedJavaFXModuleNames.isEmpty()) {
|
if (!definedJavaFXModuleNames.isEmpty()) {
|
||||||
|
RunModuleOptions moduleOptions = execTask.getExtensions().findByType(RunModuleOptions.class);
|
||||||
|
|
||||||
final FileCollection classpathWithoutJavaFXJars = execTask.getClasspath().filter(
|
final FileCollection classpathWithoutJavaFXJars = execTask.getClasspath().filter(
|
||||||
jar -> Arrays.stream(JavaFXModule.values()).noneMatch(javaFXModule -> jar.getName().contains(javaFXModule.getArtifactName()))
|
jar -> Arrays.stream(JavaFXModule.values()).noneMatch(javaFXModule -> jar.getName().contains(javaFXModule.getArtifactName()))
|
||||||
);
|
);
|
||||||
final FileCollection javaFXPlatformJars = execTask.getClasspath().filter(jar -> isJavaFXJar(jar, javaFXOptions.getPlatform()));
|
final FileCollection javaFXPlatformJars = execTask.getClasspath().filter(jar -> isJavaFXJar(jar, javaFXOptions.getPlatform()));
|
||||||
execTask.setClasspath(classpathWithoutJavaFXJars.plus(javaFXPlatformJars));
|
|
||||||
|
if (moduleOptions != null) {
|
||||||
|
LOGGER.info("Modular JavaFX application found");
|
||||||
|
// Remove empty JavaFX jars from classpath
|
||||||
|
execTask.setClasspath(classpathWithoutJavaFXJars.plus(javaFXPlatformJars));
|
||||||
|
definedJavaFXModuleNames.forEach(javaFXModule -> moduleOptions.getAddModules().add(javaFXModule));
|
||||||
|
} else {
|
||||||
|
LOGGER.info("Non-modular JavaFX application found");
|
||||||
|
// Remove all JavaFX jars from classpath
|
||||||
|
execTask.setClasspath(classpathWithoutJavaFXJars);
|
||||||
|
|
||||||
|
var javaFXModuleJvmArgs = List.of("--module-path", javaFXPlatformJars.getAsPath());
|
||||||
|
|
||||||
|
var jvmArgs = new ArrayList<String>();
|
||||||
|
jvmArgs.add("--add-modules");
|
||||||
|
jvmArgs.add(String.join(",", definedJavaFXModuleNames));
|
||||||
|
|
||||||
|
List<String> execJvmArgs = execTask.getJvmArgs();
|
||||||
|
if (execJvmArgs != null) {
|
||||||
|
jvmArgs.addAll(execJvmArgs);
|
||||||
|
}
|
||||||
|
jvmArgs.addAll(javaFXModuleJvmArgs);
|
||||||
|
|
||||||
|
execTask.setJvmArgs(jvmArgs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new GradleException("Run task not found. Please, make sure the Application plugin is applied");
|
throw new GradleException("Run task not found. Please, make sure the Application plugin is applied");
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ sudo apt install -y rpm fakeroot binutils
|
||||||
First, assign a temporary variable in your shell for the specific release you want to build. For the current one specify:
|
First, assign a temporary variable in your shell for the specific release you want to build. For the current one specify:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
GIT_TAG="2.3.0"
|
GIT_TAG="2.2.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
The project can then be initially cloned as follows:
|
The project can then be initially cloned as follows:
|
||||||
|
|
|
||||||
2
drongo
2
drongo
|
|
@ -1 +1 @@
|
||||||
Subproject commit e975cbe6f8d8574785124e6db5780d0541e20024
|
Subproject commit 13e1fafbe8892d7005a043ae561e09ed66f7cea6
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
||||||
15
gradlew
vendored
15
gradlew
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015 the original authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
|
|
@ -57,7 +55,7 @@
|
||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|
@ -86,7 +84,7 @@ done
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|
@ -114,6 +112,7 @@ case "$( uname )" in #(
|
||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
|
|
@ -171,6 +170,7 @@ fi
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
|
@ -203,14 +203,15 @@ fi
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command:
|
# Collect all arguments for the java command:
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# and any embedded shellness will be escaped.
|
# and any embedded shellness will be escaped.
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# treated as '${Hostname}' itself on the command line.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
# Stop when "xargs" is not available.
|
||||||
|
|
|
||||||
25
gradlew.bat
vendored
25
gradlew.bat
vendored
|
|
@ -13,8 +13,6 @@
|
||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
@rem SPDX-License-Identifier: Apache-2.0
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
@ -45,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
@ -59,21 +57,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
echo. 1>&2
|
echo.
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
echo location of your Java installation. 1>&2
|
echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|
|
||||||
2
lark
2
lark
|
|
@ -1 +1 @@
|
||||||
Subproject commit 10e8d9cd4bbe9fde4dd93c059e2a9faeec6be3e0
|
Subproject commit 5facb25ede49c30650a8460dc04982650edb397f
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.3.1</string>
|
<string>2.2.3</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
||||||
|
|
@ -33,8 +33,6 @@
|
||||||
<string>Copyright (C) 2021</string>
|
<string>Copyright (C) 2021</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<string>true</string>
|
<string>true</string>
|
||||||
<key>NSCameraUseContinuityCameraDeviceType</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Sparrow requires access to the camera in order to scan QR codes</string>
|
<string>Sparrow requires access to the camera in order to scan QR codes</string>
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@ package com.sparrowwallet.sparrow;
|
||||||
import com.beust.jcommander.JCommander;
|
import com.beust.jcommander.JCommander;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.sparrowwallet.drongo.*;
|
import com.sparrowwallet.drongo.*;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
|
||||||
import com.sparrowwallet.drongo.crypto.*;
|
import com.sparrowwallet.drongo.crypto.*;
|
||||||
import com.sparrowwallet.drongo.dns.DnsPayment;
|
|
||||||
import com.sparrowwallet.drongo.dns.DnsPaymentCache;
|
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.psbt.*;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
||||||
|
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||||
|
import com.sparrowwallet.drongo.psbt.PSBTSignatureException;
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.hummingbird.UR;
|
import com.sparrowwallet.hummingbird.UR;
|
||||||
import com.sparrowwallet.hummingbird.registry.CryptoPSBT;
|
import com.sparrowwallet.hummingbird.registry.CryptoPSBT;
|
||||||
|
|
@ -31,7 +30,7 @@ import com.sparrowwallet.sparrow.transaction.TransactionView;
|
||||||
import com.sparrowwallet.sparrow.wallet.Entry;
|
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletController;
|
import com.sparrowwallet.sparrow.wallet.WalletController;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
import de.jangassen.MenuToolkit;
|
import de.codecentric.centerdevice.MenuToolkit;
|
||||||
import javafx.animation.*;
|
import javafx.animation.*;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
|
@ -50,14 +49,12 @@ import javafx.geometry.Side;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.control.Menu;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.control.MenuItem;
|
|
||||||
import javafx.scene.input.*;
|
import javafx.scene.input.*;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.stage.*;
|
import javafx.stage.*;
|
||||||
import javafx.stage.Window;
|
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.controlsfx.control.Notifications;
|
import org.controlsfx.control.Notifications;
|
||||||
import org.controlsfx.control.StatusBar;
|
import org.controlsfx.control.StatusBar;
|
||||||
|
|
@ -72,7 +69,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.sparrowwallet.sparrow.AppServices.*;
|
import static com.sparrowwallet.sparrow.AppServices.*;
|
||||||
|
|
@ -826,10 +822,10 @@ public class AppController implements Initializable {
|
||||||
try(FileOutputStream outputStream = new FileOutputStream(file)) {
|
try(FileOutputStream outputStream = new FileOutputStream(file)) {
|
||||||
if(asText) {
|
if(asText) {
|
||||||
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
|
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
|
||||||
writer.print(transactionTabData.getPsbt().getForExport().toBase64String(includeXpubs));
|
writer.print(transactionTabData.getPsbt().toBase64String(includeXpubs));
|
||||||
writer.flush();
|
writer.flush();
|
||||||
} else {
|
} else {
|
||||||
outputStream.write(transactionTabData.getPsbt().getForExport().serialize(includeXpubs, true));
|
outputStream.write(transactionTabData.getPsbt().serialize(includeXpubs, true));
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
log.error("Error saving PSBT", e);
|
log.error("Error saving PSBT", e);
|
||||||
|
|
@ -852,7 +848,7 @@ public class AppController implements Initializable {
|
||||||
TabData tabData = (TabData)selectedTab.getUserData();
|
TabData tabData = (TabData)selectedTab.getUserData();
|
||||||
if(tabData.getType() == TabData.TabType.TRANSACTION) {
|
if(tabData.getType() == TabData.TabType.TRANSACTION) {
|
||||||
TransactionTabData transactionTabData = (TransactionTabData)tabData;
|
TransactionTabData transactionTabData = (TransactionTabData)tabData;
|
||||||
String data = asBase64 ? transactionTabData.getPsbt().getForExport().toBase64String() : transactionTabData.getPsbt().getForExport().toString();
|
String data = asBase64 ? transactionTabData.getPsbt().toBase64String() : transactionTabData.getPsbt().toString();
|
||||||
|
|
||||||
ClipboardContent content = new ClipboardContent();
|
ClipboardContent content = new ClipboardContent();
|
||||||
content.putString(data);
|
content.putString(data);
|
||||||
|
|
@ -866,7 +862,7 @@ public class AppController implements Initializable {
|
||||||
if(tabData.getType() == TabData.TabType.TRANSACTION) {
|
if(tabData.getType() == TabData.TabType.TRANSACTION) {
|
||||||
TransactionTabData transactionTabData = (TransactionTabData)tabData;
|
TransactionTabData transactionTabData = (TransactionTabData)tabData;
|
||||||
|
|
||||||
byte[] psbtBytes = transactionTabData.getPsbt().getForExport().serialize();
|
byte[] psbtBytes = transactionTabData.getPsbt().serialize();
|
||||||
CryptoPSBT cryptoPSBT = new CryptoPSBT(psbtBytes);
|
CryptoPSBT cryptoPSBT = new CryptoPSBT(psbtBytes);
|
||||||
BBQR bbqr = new BBQR(BBQRType.PSBT, psbtBytes);
|
BBQR bbqr = new BBQR(BBQRType.PSBT, psbtBytes);
|
||||||
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(cryptoPSBT.toUR(), bbqr, false, true, false);
|
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(cryptoPSBT.toUR(), bbqr, false, true, false);
|
||||||
|
|
@ -1038,10 +1034,6 @@ public class AppController implements Initializable {
|
||||||
cmd.add(System.getProperty(JPACKAGE_APP_PATH));
|
cmd.add(System.getProperty(JPACKAGE_APP_PATH));
|
||||||
cmd.addAll(args.toParams());
|
cmd.addAll(args.toParams());
|
||||||
final ProcessBuilder builder = new ProcessBuilder(cmd);
|
final ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
if(OsType.getCurrent() == OsType.UNIX) {
|
|
||||||
Map<String, String> env = builder.environment();
|
|
||||||
env.remove("LD_LIBRARY_PATH");
|
|
||||||
}
|
|
||||||
builder.start();
|
builder.start();
|
||||||
quit(event);
|
quit(event);
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
|
|
@ -1901,11 +1893,6 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTransactionTab(String name, File file, PSBT psbt) {
|
private void addTransactionTab(String name, File file, PSBT psbt) {
|
||||||
//Convert to PSBTv0 first
|
|
||||||
if(psbt.getVersion() != null && psbt.getVersion() >= 2) {
|
|
||||||
psbt.convertVersion(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add any missing previous outputs if available in open wallets
|
//Add any missing previous outputs if available in open wallets
|
||||||
for(PSBTInput psbtInput : psbt.getPsbtInputs()) {
|
for(PSBTInput psbtInput : psbt.getPsbtInputs()) {
|
||||||
if(psbtInput.getUtxo() == null) {
|
if(psbtInput.getUtxo() == null) {
|
||||||
|
|
@ -1925,39 +1912,6 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add DNS payment information if not already cached
|
|
||||||
for(PSBTOutput psbtOutput : psbt.getPsbtOutputs()) {
|
|
||||||
if(psbtOutput.getDnssecProof() != null && !psbtOutput.getDnssecProof().isEmpty()) {
|
|
||||||
Address address = psbtOutput.getScript() != null ? psbtOutput.getScript().getToAddress() : null;
|
|
||||||
if(address != null && DnsPaymentCache.getDnsPayment(address) == null) {
|
|
||||||
try {
|
|
||||||
Optional<DnsPayment> optDnsPayment = psbtOutput.getDnsPayment();
|
|
||||||
if(optDnsPayment.isPresent() && address.equals(optDnsPayment.get().bitcoinURI().getAddress())) {
|
|
||||||
DnsPaymentCache.putDnsPayment(address, optDnsPayment.get());
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
log.debug("Error resolving DNS payment", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SilentPaymentAddress silentPaymentAddress = psbtOutput.getSilentPaymentAddress();
|
|
||||||
if(address != null && silentPaymentAddress == null) {
|
|
||||||
silentPaymentAddress = AppServices.get().getOpenWallets().keySet().stream()
|
|
||||||
.map(wallet -> wallet.getSilentPaymentAddress(address)).filter(Objects::nonNull).findFirst().orElse(null);
|
|
||||||
}
|
|
||||||
if(silentPaymentAddress != null && DnsPaymentCache.getDnsPayment(silentPaymentAddress) == null) {
|
|
||||||
try {
|
|
||||||
Optional<DnsPayment> optDnsPayment = psbtOutput.getDnsPayment();
|
|
||||||
if(optDnsPayment.isPresent() && silentPaymentAddress.equals(optDnsPayment.get().bitcoinURI().getSilentPaymentAddress())) {
|
|
||||||
DnsPaymentCache.putDnsPayment(silentPaymentAddress, optDnsPayment.get());
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
log.debug("Error resolving DNS payment", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Window psbtWalletWindow = AppServices.get().getWindowForPSBT(psbt);
|
Window psbtWalletWindow = AppServices.get().getWindowForPSBT(psbt);
|
||||||
if(psbtWalletWindow != null && !tabs.getScene().getWindow().equals(psbtWalletWindow)) {
|
if(psbtWalletWindow != null && !tabs.getScene().getWindow().equals(psbtWalletWindow)) {
|
||||||
EventManager.get().post(new ViewPSBTEvent(psbtWalletWindow, name, file, psbt));
|
EventManager.get().post(new ViewPSBTEvent(psbtWalletWindow, name, file, psbt));
|
||||||
|
|
@ -2096,33 +2050,23 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem moveRight = new MenuItem("Move Right");
|
MenuItem moveRight = new MenuItem("Move Right");
|
||||||
moveRight.setAccelerator(new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN));
|
|
||||||
moveRight.setOnAction(event -> {
|
moveRight.setOnAction(event -> {
|
||||||
int currentIndex = tabs.getSelectionModel().getSelectedIndex();
|
int index = tabs.getTabs().indexOf(tab);
|
||||||
if(currentIndex + 1 >= tabs.getTabs().size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Tab selectedTab = tabs.getSelectionModel().getSelectedItem();
|
|
||||||
tabs.getTabs().removeListener(tabsChangeListener);
|
tabs.getTabs().removeListener(tabsChangeListener);
|
||||||
tabs.getTabs().remove(selectedTab);
|
tabs.getTabs().remove(tab);
|
||||||
tabs.getTabs().add(currentIndex + 1, selectedTab);
|
tabs.getTabs().add(index + 1, tab);
|
||||||
tabs.getTabs().addListener(tabsChangeListener);
|
tabs.getTabs().addListener(tabsChangeListener);
|
||||||
tabs.getSelectionModel().select(selectedTab);
|
tabs.getSelectionModel().select(tab);
|
||||||
EventManager.get().post(new RequestOpenWalletsEvent()); //Rearrange recent files list
|
EventManager.get().post(new RequestOpenWalletsEvent()); //Rearrange recent files list
|
||||||
});
|
});
|
||||||
MenuItem moveLeft = new MenuItem("Move Left");
|
MenuItem moveLeft = new MenuItem("Move Left");
|
||||||
moveLeft.setAccelerator(new KeyCodeCombination(KeyCode.LEFT, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN));
|
|
||||||
moveLeft.setOnAction(event -> {
|
moveLeft.setOnAction(event -> {
|
||||||
int currentIndex = tabs.getSelectionModel().getSelectedIndex();
|
int index = tabs.getTabs().indexOf(tab);
|
||||||
if(currentIndex == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Tab selectedTab = tabs.getSelectionModel().getSelectedItem();
|
|
||||||
tabs.getTabs().removeListener(tabsChangeListener);
|
tabs.getTabs().removeListener(tabsChangeListener);
|
||||||
tabs.getTabs().remove(selectedTab);
|
tabs.getTabs().remove(tab);
|
||||||
tabs.getTabs().add(currentIndex - 1, selectedTab);
|
tabs.getTabs().add(index - 1, tab);
|
||||||
tabs.getTabs().addListener(tabsChangeListener);
|
tabs.getTabs().addListener(tabsChangeListener);
|
||||||
tabs.getSelectionModel().select(selectedTab);
|
tabs.getSelectionModel().select(tab);
|
||||||
EventManager.get().post(new RequestOpenWalletsEvent()); //Rearrange recent files list
|
EventManager.get().post(new RequestOpenWalletsEvent()); //Rearrange recent files list
|
||||||
});
|
});
|
||||||
contextMenu.getItems().addAll(moveRight, moveLeft);
|
contextMenu.getItems().addAll(moveRight, moveLeft);
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,8 @@ public class AppServices {
|
||||||
private static final String TOR_DEFAULT_PROXY_CIRCUIT_ID = "default";
|
private static final String TOR_DEFAULT_PROXY_CIRCUIT_ID = "default";
|
||||||
|
|
||||||
public static final List<Integer> TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50);
|
public static final List<Integer> TARGET_BLOCKS_RANGE = List.of(1, 2, 3, 4, 5, 10, 25, 50);
|
||||||
private static final List<Double> LONG_FEE_RATES_RANGE = List.of(1d, 2d, 4d, 8d, 16d, 32d, 64d, 128d, 256d, 512d, 1024d, 2048d, 4096d, 8192d);
|
public static final List<Long> LONG_FEE_RATES_RANGE = List.of(1L, 2L, 4L, 8L, 16L, 32L, 64L, 128L, 256L, 512L, 1024L, 2048L, 4096L, 8192L);
|
||||||
|
public static final List<Long> FEE_RATES_RANGE = LONG_FEE_RATES_RANGE.subList(0, LONG_FEE_RATES_RANGE.size() - 3);
|
||||||
public static final double FALLBACK_FEE_RATE = 20000d / 1000;
|
public static final double FALLBACK_FEE_RATE = 20000d / 1000;
|
||||||
public static final double TESTNET_FALLBACK_FEE_RATE = 1000d / 1000;
|
public static final double TESTNET_FALLBACK_FEE_RATE = 1000d / 1000;
|
||||||
|
|
||||||
|
|
@ -141,8 +142,6 @@ public class AppServices {
|
||||||
|
|
||||||
private static Double minimumRelayFeeRate;
|
private static Double minimumRelayFeeRate;
|
||||||
|
|
||||||
private static Double serverMinimumRelayFeeRate;
|
|
||||||
|
|
||||||
private static CurrencyRate fiatCurrencyExchangeRate;
|
private static CurrencyRate fiatCurrencyExchangeRate;
|
||||||
|
|
||||||
private static List<Device> devices;
|
private static List<Device> devices;
|
||||||
|
|
@ -212,7 +211,6 @@ public class AppServices {
|
||||||
preventSleepService = createPreventSleepService();
|
preventSleepService = createPreventSleepService();
|
||||||
|
|
||||||
onlineProperty.addListener(onlineServicesListener);
|
onlineProperty.addListener(onlineServicesListener);
|
||||||
minimumRelayFeeRate = getConfiguredMinimumRelayFeeRate(config);
|
|
||||||
|
|
||||||
if(config.getMode() == Mode.ONLINE) {
|
if(config.getMode() == Mode.ONLINE) {
|
||||||
if(config.requiresInternalTor()) {
|
if(config.requiresInternalTor()) {
|
||||||
|
|
@ -752,26 +750,6 @@ public class AppServices {
|
||||||
return Math.max(minRate, Transaction.DUST_RELAY_TX_FEE);
|
return Math.max(minRate, Transaction.DUST_RELAY_TX_FEE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Double> getLongFeeRatesRange() {
|
|
||||||
if(minimumRelayFeeRate == null || minimumRelayFeeRate >= Transaction.DEFAULT_MIN_RELAY_FEE) {
|
|
||||||
return LONG_FEE_RATES_RANGE;
|
|
||||||
} else {
|
|
||||||
List<Double> longFeeRatesRange = new ArrayList<>();
|
|
||||||
longFeeRatesRange.add(minimumRelayFeeRate);
|
|
||||||
longFeeRatesRange.addAll(LONG_FEE_RATES_RANGE);
|
|
||||||
return longFeeRatesRange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Double> getFeeRatesRange() {
|
|
||||||
if(minimumRelayFeeRate == null || minimumRelayFeeRate >= Transaction.DEFAULT_MIN_RELAY_FEE) {
|
|
||||||
return LONG_FEE_RATES_RANGE.subList(0, LONG_FEE_RATES_RANGE.size() - 3);
|
|
||||||
} else {
|
|
||||||
List<Double> longFeeRatesRange = getLongFeeRatesRange();
|
|
||||||
return longFeeRatesRange.subList(0, longFeeRatesRange.size() - 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Double getNextBlockMedianFeeRate() {
|
public static Double getNextBlockMedianFeeRate() {
|
||||||
return nextBlockMedianFeeRate == null ? getDefaultFeeRate() : nextBlockMedianFeeRate;
|
return nextBlockMedianFeeRate == null ? getDefaultFeeRate() : nextBlockMedianFeeRate;
|
||||||
}
|
}
|
||||||
|
|
@ -810,18 +788,10 @@ public class AppServices {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Double getConfiguredMinimumRelayFeeRate(Config config) {
|
|
||||||
return config.getMinRelayFeeRate() >= 0d && config.getMinRelayFeeRate() < Transaction.DEFAULT_MIN_RELAY_FEE ? config.getMinRelayFeeRate() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Double getMinimumRelayFeeRate() {
|
public static Double getMinimumRelayFeeRate() {
|
||||||
return minimumRelayFeeRate == null ? Transaction.DEFAULT_MIN_RELAY_FEE : minimumRelayFeeRate;
|
return minimumRelayFeeRate == null ? Transaction.DEFAULT_MIN_RELAY_FEE : minimumRelayFeeRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Double getServerMinimumRelayFeeRate() {
|
|
||||||
return serverMinimumRelayFeeRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CurrencyRate getFiatCurrencyExchangeRate() {
|
public static CurrencyRate getFiatCurrencyExchangeRate() {
|
||||||
return fiatCurrencyExchangeRate;
|
return fiatCurrencyExchangeRate;
|
||||||
}
|
}
|
||||||
|
|
@ -835,8 +805,8 @@ public class AppServices {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addPayjoinURI(BitcoinURI bitcoinURI) {
|
public static void addPayjoinURI(BitcoinURI bitcoinURI) {
|
||||||
if(bitcoinURI.getPayjoinUrl() == null || bitcoinURI.getAddress() == null) {
|
if(bitcoinURI.getPayjoinUrl() == null) {
|
||||||
throw new IllegalArgumentException("Not a valid payjoin URI");
|
throw new IllegalArgumentException("Not a payjoin URI");
|
||||||
}
|
}
|
||||||
payjoinURIs.put(bitcoinURI.getAddress(), bitcoinURI);
|
payjoinURIs.put(bitcoinURI.getAddress(), bitcoinURI);
|
||||||
}
|
}
|
||||||
|
|
@ -1233,7 +1203,7 @@ public class AppServices {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Font getMonospaceFont() {
|
public static Font getMonospaceFont() {
|
||||||
return Font.font("Fragment Mono Regular", 13);
|
return Font.font("Roboto Mono", 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isOnWayland() {
|
public static boolean isOnWayland() {
|
||||||
|
|
@ -1249,10 +1219,7 @@ public class AppServices {
|
||||||
public void newConnection(ConnectionEvent event) {
|
public void newConnection(ConnectionEvent event) {
|
||||||
currentBlockHeight = event.getBlockHeight();
|
currentBlockHeight = event.getBlockHeight();
|
||||||
System.setProperty(Network.BLOCK_HEIGHT_PROPERTY, Integer.toString(currentBlockHeight));
|
System.setProperty(Network.BLOCK_HEIGHT_PROPERTY, Integer.toString(currentBlockHeight));
|
||||||
if(getConfiguredMinimumRelayFeeRate(Config.get()) == null) {
|
minimumRelayFeeRate = Math.max(event.getMinimumRelayFeeRate(), Transaction.DEFAULT_MIN_RELAY_FEE);
|
||||||
minimumRelayFeeRate = event.getMinimumRelayFeeRate() == null ? Transaction.DEFAULT_MIN_RELAY_FEE : event.getMinimumRelayFeeRate();
|
|
||||||
}
|
|
||||||
serverMinimumRelayFeeRate = event.getMinimumRelayFeeRate();
|
|
||||||
latestBlockHeader = event.getBlockHeader();
|
latestBlockHeader = event.getBlockHeader();
|
||||||
Config.get().addRecentServer();
|
Config.get().addRecentServer();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,8 +113,8 @@ public class SparrowDesktop extends Application {
|
||||||
private void initializeFonts() {
|
private void initializeFonts() {
|
||||||
GlyphFontRegistry.register(new FontAwesome5());
|
GlyphFontRegistry.register(new FontAwesome5());
|
||||||
GlyphFontRegistry.register(new FontAwesome5Brands());
|
GlyphFontRegistry.register(new FontAwesome5Brands());
|
||||||
Font.loadFont(AppServices.class.getResourceAsStream("/font/FragmentMono-Regular.ttf"), 13);
|
Font.loadFont(AppServices.class.getResourceAsStream("/font/RobotoMono-Regular.ttf"), 13);
|
||||||
Font.loadFont(AppServices.class.getResourceAsStream("/font/FragmentMono-Italic.ttf"), 11);
|
Font.loadFont(AppServices.class.getResourceAsStream("/font/RobotoMono-Italic.ttf"), 11);
|
||||||
if(OsType.getCurrent() == OsType.MACOS) {
|
if(OsType.getCurrent() == OsType.MACOS) {
|
||||||
Font.loadFont(AppServices.class.getResourceAsStream("/font/LiberationSans-Regular.ttf"), 13);
|
Font.loadFont(AppServices.class.getResourceAsStream("/font/LiberationSans-Regular.ttf"), 13);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import java.util.*;
|
||||||
public class SparrowWallet {
|
public class SparrowWallet {
|
||||||
public static final String APP_ID = "sparrow";
|
public static final String APP_ID = "sparrow";
|
||||||
public static final String APP_NAME = "Sparrow";
|
public static final String APP_NAME = "Sparrow";
|
||||||
public static final String APP_VERSION = "2.3.1";
|
public static final String APP_VERSION = "2.2.3";
|
||||||
public static final String APP_VERSION_SUFFIX = "";
|
public static final String APP_VERSION_SUFFIX = "";
|
||||||
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
||||||
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,6 @@ class CoinCell extends TreeTableCell<Entry, Number> implements ConfirmationsList
|
||||||
} else if(entry instanceof UtxoEntry) {
|
} else if(entry instanceof UtxoEntry) {
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
} else if(entry instanceof HashIndexEntry) {
|
} else if(entry instanceof HashIndexEntry) {
|
||||||
tooltip.hideConfirmations();
|
|
||||||
|
|
||||||
Region node = new Region();
|
Region node = new Region();
|
||||||
node.setPrefWidth(10);
|
node.setPrefWidth(10);
|
||||||
setGraphic(node);
|
setGraphic(node);
|
||||||
|
|
@ -150,14 +148,6 @@ class CoinCell extends TreeTableCell<Entry, Number> implements ConfirmationsList
|
||||||
setTooltipText();
|
setTooltipText();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideConfirmations() {
|
|
||||||
showConfirmations = false;
|
|
||||||
isCoinbase = false;
|
|
||||||
confirmationsProperty.unbind();
|
|
||||||
|
|
||||||
setTooltipText();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTooltipText() {
|
private void setTooltipText() {
|
||||||
setText(value + (showConfirmations ? " (" + getConfirmationsDescription() + ")" : ""));
|
setText(value + (showConfirmations ? " (" + getConfirmationsDescription() + ")" : ""));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,10 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.ContextMenu;
|
|
||||||
import javafx.scene.control.MenuItem;
|
|
||||||
import javafx.scene.control.SeparatorMenuItem;
|
|
||||||
import javafx.scene.input.Clipboard;
|
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.controlsfx.control.textfield.CustomTextField;
|
import org.controlsfx.control.textfield.CustomTextField;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ComboBoxTextField extends CustomTextField {
|
public class ComboBoxTextField extends CustomTextField {
|
||||||
private final ObjectProperty<ComboBox<?>> comboProperty = new SimpleObjectProperty<>();
|
private final ObjectProperty<ComboBox<?>> comboProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
|
@ -74,53 +68,4 @@ public class ComboBoxTextField extends CustomTextField {
|
||||||
public void setComboProperty(ComboBox<?> comboProperty) {
|
public void setComboProperty(ComboBox<?> comboProperty) {
|
||||||
this.comboProperty.set(comboProperty);
|
this.comboProperty.set(comboProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContextMenu getCustomContextMenu(List<MenuItem> customItems) {
|
|
||||||
return new CustomContextMenu(customItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CustomContextMenu extends ContextMenu {
|
|
||||||
public CustomContextMenu(List<MenuItem> customItems) {
|
|
||||||
super();
|
|
||||||
setFont(null);
|
|
||||||
|
|
||||||
MenuItem undo = new MenuItem("Undo");
|
|
||||||
undo.setOnAction(_ -> undo());
|
|
||||||
|
|
||||||
MenuItem redo = new MenuItem("Redo");
|
|
||||||
redo.setOnAction(_ -> redo());
|
|
||||||
|
|
||||||
MenuItem cut = new MenuItem("Cut");
|
|
||||||
cut.setOnAction(_ -> cut());
|
|
||||||
|
|
||||||
MenuItem copy = new MenuItem("Copy");
|
|
||||||
copy.setOnAction(_ -> copy());
|
|
||||||
|
|
||||||
MenuItem paste = new MenuItem("Paste");
|
|
||||||
paste.setOnAction(_ -> paste());
|
|
||||||
|
|
||||||
MenuItem delete = new MenuItem("Delete");
|
|
||||||
delete.setOnAction(_ -> deleteText(getSelection()));
|
|
||||||
|
|
||||||
MenuItem selectAll = new MenuItem("Select All");
|
|
||||||
selectAll.setOnAction(_ -> selectAll());
|
|
||||||
|
|
||||||
getItems().addAll(undo, redo, new SeparatorMenuItem(), cut, copy, paste, delete, new SeparatorMenuItem(), selectAll);
|
|
||||||
getItems().addAll(customItems);
|
|
||||||
|
|
||||||
setOnShowing(_ -> {
|
|
||||||
boolean hasSelection = getSelection().getLength() > 0;
|
|
||||||
boolean hasText = getText() != null && !getText().isEmpty();
|
|
||||||
boolean clipboardHasContent = Clipboard.getSystemClipboard().hasString();
|
|
||||||
|
|
||||||
undo.setDisable(!isUndoable());
|
|
||||||
redo.setDisable(!isRedoable());
|
|
||||||
cut.setDisable(!isEditable() || !hasSelection);
|
|
||||||
copy.setDisable(!hasSelection);
|
|
||||||
paste.setDisable(!isEditable() || !clipboardHasContent);
|
|
||||||
delete.setDisable(!hasSelection);
|
|
||||||
selectAll.setDisable(!hasText);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.input.Clipboard;
|
import javafx.scene.input.Clipboard;
|
||||||
import javafx.scene.input.ClipboardContent;
|
import javafx.scene.input.ClipboardContent;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
|
|
||||||
public class CopyableCoinLabel extends CopyableLabel {
|
public class CopyableCoinLabel extends CopyableLabel {
|
||||||
|
|
@ -30,10 +29,6 @@ public class CopyableCoinLabel extends CopyableLabel {
|
||||||
valueProperty().addListener((observable, oldValue, newValue) -> setValueAsText((Long)newValue, Config.get().getUnitFormat(), Config.get().getBitcoinUnit()));
|
valueProperty().addListener((observable, oldValue, newValue) -> setValueAsText((Long)newValue, Config.get().getUnitFormat(), Config.get().getBitcoinUnit()));
|
||||||
|
|
||||||
setOnMouseClicked(event -> {
|
setOnMouseClicked(event -> {
|
||||||
if(!event.getButton().equals(MouseButton.PRIMARY)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(bitcoinUnit == null) {
|
if(bitcoinUnit == null) {
|
||||||
bitcoinUnit = Config.get().getBitcoinUnit();
|
bitcoinUnit = Config.get().getBitcoinUnit();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -453,26 +453,20 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
});
|
});
|
||||||
vBox.getChildren().addAll(pinField, enterPinButton);
|
vBox.getChildren().addAll(pinField, enterPinButton);
|
||||||
|
|
||||||
GridPane gridPane = new GridPane();
|
TilePane tilePane = new TilePane();
|
||||||
gridPane.setHgap(10);
|
tilePane.setPrefColumns(3);
|
||||||
gridPane.setVgap(10);
|
tilePane.setHgap(10);
|
||||||
gridPane.setMaxWidth(150);
|
tilePane.setVgap(10);
|
||||||
gridPane.setMaxHeight(device.getModel().hasZeroInPin() ? 160 : 120);
|
tilePane.setMaxWidth(150);
|
||||||
|
tilePane.setMaxHeight(120);
|
||||||
|
|
||||||
int[] digits = device.getModel().hasZeroInPin() ? new int[] {7, 8, 9, 4, 5, 6, 1, 2, 3, 0} : new int[] {7, 8, 9, 4, 5, 6, 1, 2, 3};
|
int[] digits = new int[] {7, 8, 9, 4, 5, 6, 1, 2, 3};
|
||||||
for(int i = 0; i < digits.length; i++) {
|
for(int i = 0; i < digits.length; i++) {
|
||||||
Button pinButton = new Button();
|
Button pinButton = new Button();
|
||||||
Glyph circle = new Glyph(FontAwesome5.FONT_NAME, "CIRCLE");
|
Glyph circle = new Glyph(FontAwesome5.FONT_NAME, "CIRCLE");
|
||||||
pinButton.setGraphic(circle);
|
pinButton.setGraphic(circle);
|
||||||
pinButton.setUserData(digits[i]);
|
pinButton.setUserData(digits[i]);
|
||||||
GridPane.setRowIndex(pinButton, i / 3);
|
tilePane.getChildren().add(pinButton);
|
||||||
GridPane.setColumnIndex(pinButton, i % 3);
|
|
||||||
if((i / 3) == 3) {
|
|
||||||
GridPane.setHgrow(pinButton, Priority.ALWAYS);
|
|
||||||
GridPane.setColumnSpan(pinButton, 3);
|
|
||||||
pinButton.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
}
|
|
||||||
gridPane.getChildren().add(pinButton);
|
|
||||||
pinButton.setOnAction(event -> {
|
pinButton.setOnAction(event -> {
|
||||||
pinField.setText(pinField.getText() + pinButton.getUserData());
|
pinField.setText(pinField.getText() + pinButton.getUserData());
|
||||||
});
|
});
|
||||||
|
|
@ -480,7 +474,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
|
|
||||||
HBox contentBox = new HBox();
|
HBox contentBox = new HBox();
|
||||||
contentBox.setSpacing(50);
|
contentBox.setSpacing(50);
|
||||||
contentBox.getChildren().add(gridPane);
|
contentBox.getChildren().add(tilePane);
|
||||||
contentBox.getChildren().add(vBox);
|
contentBox.getChildren().add(vBox);
|
||||||
contentBox.setPadding(new Insets(10, 0, 10, 0));
|
contentBox.setPadding(new Insets(10, 0, 10, 0));
|
||||||
contentBox.setAlignment(Pos.TOP_CENTER);
|
contentBox.setAlignment(Pos.TOP_CENTER);
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import com.sparrowwallet.drongo.OsType;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
|
@ -57,7 +55,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
super.updateItem(entry, empty);
|
super.updateItem(entry, empty);
|
||||||
|
|
||||||
//Return immediately to avoid CPU usage when updating the same invisible cell to determine tableview size (see https://bugs.openjdk.org/browse/JDK-8280442)
|
//Return immediately to avoid CPU usage when updating the same invisible cell to determine tableview size (see https://bugs.openjdk.org/browse/JDK-8280442)
|
||||||
if(this == lastCell && !getTableRow().isVisible() && isTableSizeRecalculation()) {
|
if(this == lastCell && !getTableRow().isVisible()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastCell = this;
|
lastCell = this;
|
||||||
|
|
@ -68,7 +66,8 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
setText(null);
|
setText(null);
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
} else {
|
} else {
|
||||||
if(entry instanceof TransactionEntry transactionEntry) {
|
if(entry instanceof TransactionEntry) {
|
||||||
|
TransactionEntry transactionEntry = (TransactionEntry)entry;
|
||||||
if(transactionEntry.getBlockTransaction().getHeight() == -1) {
|
if(transactionEntry.getBlockTransaction().getHeight() == -1) {
|
||||||
setText("Unconfirmed Parent");
|
setText("Unconfirmed Parent");
|
||||||
setContextMenu(new UnconfirmedTransactionContextMenu(transactionEntry));
|
setContextMenu(new UnconfirmedTransactionContextMenu(transactionEntry));
|
||||||
|
|
@ -102,7 +101,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
actionBox.getChildren().add(viewTransactionButton);
|
actionBox.getChildren().add(viewTransactionButton);
|
||||||
|
|
||||||
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
||||||
if(blockTransaction.getHeight() <= 0 && canRBF(blockTransaction, transactionEntry.getWallet()) &&
|
if(blockTransaction.getHeight() <= 0 && canRBF(blockTransaction) &&
|
||||||
Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
||||||
Button increaseFeeButton = new Button("");
|
Button increaseFeeButton = new Button("");
|
||||||
increaseFeeButton.setGraphic(getIncreaseFeeRBFGlyph());
|
increaseFeeButton.setGraphic(getIncreaseFeeRBFGlyph());
|
||||||
|
|
@ -122,7 +121,8 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
}
|
}
|
||||||
|
|
||||||
setGraphic(actionBox);
|
setGraphic(actionBox);
|
||||||
} else if(entry instanceof NodeEntry nodeEntry) {
|
} else if(entry instanceof NodeEntry) {
|
||||||
|
NodeEntry nodeEntry = (NodeEntry)entry;
|
||||||
Address address = nodeEntry.getAddress();
|
Address address = nodeEntry.getAddress();
|
||||||
setText(address.toString());
|
setText(address.toString());
|
||||||
setContextMenu(new AddressContextMenu(address, nodeEntry.getOutputDescriptor(), nodeEntry, true, getTreeTableView()));
|
setContextMenu(new AddressContextMenu(address, nodeEntry.getOutputDescriptor(), nodeEntry, true, getTreeTableView()));
|
||||||
|
|
@ -163,7 +163,8 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
setContextMenu(null);
|
setContextMenu(null);
|
||||||
setGraphic(new HBox());
|
setGraphic(new HBox());
|
||||||
}
|
}
|
||||||
} else if(entry instanceof HashIndexEntry hashIndexEntry) {
|
} else if(entry instanceof HashIndexEntry) {
|
||||||
|
HashIndexEntry hashIndexEntry = (HashIndexEntry)entry;
|
||||||
setText(hashIndexEntry.getDescription());
|
setText(hashIndexEntry.getDescription());
|
||||||
setContextMenu(getTreeTableView().getStyleClass().contains("bip47") ? null : new HashIndexEntryContextMenu(getTreeTableView(), hashIndexEntry));
|
setContextMenu(getTreeTableView().getStyleClass().contains("bip47") ? null : new HashIndexEntryContextMenu(getTreeTableView(), hashIndexEntry));
|
||||||
Tooltip tooltip = new Tooltip();
|
Tooltip tooltip = new Tooltip();
|
||||||
|
|
@ -211,14 +212,13 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
|
|
||||||
private static void increaseFee(TransactionEntry transactionEntry, boolean cancelTransaction) {
|
private static void increaseFee(TransactionEntry transactionEntry, boolean cancelTransaction) {
|
||||||
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
||||||
boolean silentPaymentTransaction = transactionEntry.getWallet().isSilentPaymentsTransaction(blockTransaction);
|
|
||||||
Map<BlockTransactionHashIndex, WalletNode> walletTxos = transactionEntry.getWallet().getWalletTxos();
|
Map<BlockTransactionHashIndex, WalletNode> walletTxos = transactionEntry.getWallet().getWalletTxos();
|
||||||
List<BlockTransactionHashIndex> utxos = transactionEntry.getChildren().stream()
|
List<BlockTransactionHashIndex> utxos = transactionEntry.getChildren().stream()
|
||||||
.filter(e -> e instanceof HashIndexEntry)
|
.filter(e -> e instanceof HashIndexEntry)
|
||||||
.map(e -> (HashIndexEntry)e)
|
.map(e -> (HashIndexEntry)e)
|
||||||
.filter(e -> e.getType().equals(HashIndexEntry.Type.INPUT) && e.isSpendable())
|
.filter(e -> e.getType().equals(HashIndexEntry.Type.INPUT) && e.isSpendable())
|
||||||
.map(e -> blockTransaction.getTransaction().getInputs().get((int)e.getHashIndex().getIndex()))
|
.map(e -> blockTransaction.getTransaction().getInputs().get((int)e.getHashIndex().getIndex()))
|
||||||
.filter(i -> Config.get().isMempoolFullRbf() || i.isReplaceByFeeEnabled() || silentPaymentTransaction)
|
.filter(i -> Config.get().isMempoolFullRbf() || i.isReplaceByFeeEnabled())
|
||||||
.map(txInput -> walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txInput.getOutpoint().getHash()) && txo.getIndex() == txInput.getOutpoint().getIndex()).findFirst().get())
|
.map(txInput -> walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txInput.getOutpoint().getHash()) && txo.getIndex() == txInput.getOutpoint().getIndex()).findFirst().get())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
|
@ -243,7 +243,6 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
boolean consolidationTransaction = consolidationOutputs.size() == blockTransaction.getTransaction().getOutputs().size() && consolidationOutputs.size() == 1;
|
boolean consolidationTransaction = consolidationOutputs.size() == blockTransaction.getTransaction().getOutputs().size() && consolidationOutputs.size() == 1;
|
||||||
boolean safeToAddInputsOrOutputs = transactionEntry.getWallet().isSafeToAddInputsOrOutputs(blockTransaction);
|
|
||||||
long changeTotal = ourOutputs.stream().mapToLong(TransactionOutput::getValue).sum() - consolidationOutputs.stream().mapToLong(TransactionOutput::getValue).sum();
|
long changeTotal = ourOutputs.stream().mapToLong(TransactionOutput::getValue).sum() - consolidationOutputs.stream().mapToLong(TransactionOutput::getValue).sum();
|
||||||
Transaction tx = blockTransaction.getTransaction();
|
Transaction tx = blockTransaction.getTransaction();
|
||||||
double vSize = tx.getVirtualSize();
|
double vSize = tx.getVirtualSize();
|
||||||
|
|
@ -258,7 +257,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
List<OutputGroup> outputGroups = transactionEntry.getWallet().getGroupedUtxos(txoFilters, feeRate, AppServices.getMinimumRelayFeeRate(), Config.get().isGroupByAddress())
|
List<OutputGroup> outputGroups = transactionEntry.getWallet().getGroupedUtxos(txoFilters, feeRate, AppServices.getMinimumRelayFeeRate(), Config.get().isGroupByAddress())
|
||||||
.stream().filter(outputGroup -> outputGroup.getEffectiveValue() >= 0).collect(Collectors.toList());
|
.stream().filter(outputGroup -> outputGroup.getEffectiveValue() >= 0).collect(Collectors.toList());
|
||||||
Collections.shuffle(outputGroups);
|
Collections.shuffle(outputGroups);
|
||||||
while((double)changeTotal / vSize < getMaxFeeRate() && !outputGroups.isEmpty() && !cancelTransaction && !consolidationTransaction && safeToAddInputsOrOutputs) {
|
while((double)changeTotal / vSize < getMaxFeeRate() && !outputGroups.isEmpty() && !cancelTransaction && !consolidationTransaction) {
|
||||||
//If there is insufficient change output, include another random output group so the fee can be increased
|
//If there is insufficient change output, include another random output group so the fee can be increased
|
||||||
OutputGroup outputGroup = outputGroups.remove(0);
|
OutputGroup outputGroup = outputGroups.remove(0);
|
||||||
for(BlockTransactionHashIndex utxo : outputGroup.getUtxos()) {
|
for(BlockTransactionHashIndex utxo : outputGroup.getUtxos()) {
|
||||||
|
|
@ -299,13 +298,9 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
label += " (Replaced By Fee)";
|
label += " (Replaced By Fee)";
|
||||||
}
|
}
|
||||||
|
|
||||||
Address address = txOutput.getScript().getToAddress();
|
if(txOutput.getScript().getToAddress() != null) {
|
||||||
if(address != null) {
|
|
||||||
long value = txOutput.getValue();
|
|
||||||
//Disable change creation by enabling max payment when there is only one output and no additional UTXOs included
|
//Disable change creation by enabling max payment when there is only one output and no additional UTXOs included
|
||||||
boolean sendMax = blockTransaction.getTransaction().getOutputs().size() == 1 && rbfChange == 0;
|
return new Payment(txOutput.getScript().getToAddress(), label, txOutput.getValue(), blockTransaction.getTransaction().getOutputs().size() == 1 && rbfChange == 0);
|
||||||
SilentPaymentAddress silentPaymentAddress = transactionEntry.getWallet().getSilentPaymentAddress(address);
|
|
||||||
return silentPaymentAddress == null ? new Payment(address, label, value, sendMax) : new SilentPayment(silentPaymentAddress, label, value, sendMax);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -342,7 +337,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
}
|
}
|
||||||
|
|
||||||
EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), utxos));
|
EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), utxos));
|
||||||
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, payments, opReturns.isEmpty() ? null : opReturns, rbfFee, true, blockTransaction, safeToAddInputsOrOutputs)));
|
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, payments, opReturns.isEmpty() ? null : opReturns, rbfFee, true, blockTransaction)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Double getMaxFeeRate() {
|
private static Double getMaxFeeRate() {
|
||||||
|
|
@ -399,11 +394,11 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
Payment payment = new Payment(freshAddress, label, inputTotal, true);
|
Payment payment = new Payment(freshAddress, label, inputTotal, true);
|
||||||
|
|
||||||
EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), utxos));
|
EventManager.get().post(new SendActionEvent(transactionEntry.getWallet(), utxos));
|
||||||
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, List.of(payment), null, blockTransaction.getFee(), true, null, true)));
|
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, List.of(payment), null, blockTransaction.getFee(), true, null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canRBF(BlockTransaction blockTransaction, Wallet wallet) {
|
private static boolean canRBF(BlockTransaction blockTransaction) {
|
||||||
return Config.get().isMempoolFullRbf() || blockTransaction.getTransaction().isReplaceByFee() || wallet.isSilentPaymentsTransaction(blockTransaction);
|
return Config.get().isMempoolFullRbf() || blockTransaction.getTransaction().isReplaceByFee();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canSignMessage(WalletNode walletNode) {
|
private static boolean canSignMessage(WalletNode walletNode) {
|
||||||
|
|
@ -481,7 +476,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
tooltip += "\nFee rate: " + String.format("%.2f", feeRate) + " sats/vB";
|
tooltip += "\nFee rate: " + String.format("%.2f", feeRate) + " sats/vB";
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltip += "\nRBF: " + (canRBF(transactionEntry.getBlockTransaction(), transactionEntry.getWallet()) ? "Enabled" : "Disabled");
|
tooltip += "\nRBF: " + (canRBF(transactionEntry.getBlockTransaction()) ? "Enabled" : "Disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
return tooltip;
|
return tooltip;
|
||||||
|
|
@ -549,7 +544,6 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
|
|
||||||
private static class UnconfirmedTransactionContextMenu extends ContextMenu {
|
private static class UnconfirmedTransactionContextMenu extends ContextMenu {
|
||||||
public UnconfirmedTransactionContextMenu(TransactionEntry transactionEntry) {
|
public UnconfirmedTransactionContextMenu(TransactionEntry transactionEntry) {
|
||||||
Wallet wallet = transactionEntry.getWallet();
|
|
||||||
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
||||||
MenuItem viewTransaction = new MenuItem("View Transaction");
|
MenuItem viewTransaction = new MenuItem("View Transaction");
|
||||||
viewTransaction.setGraphic(getViewTransactionGlyph());
|
viewTransaction.setGraphic(getViewTransactionGlyph());
|
||||||
|
|
@ -559,7 +553,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
});
|
});
|
||||||
getItems().add(viewTransaction);
|
getItems().add(viewTransaction);
|
||||||
|
|
||||||
if(canRBF(blockTransaction, wallet) && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
if(canRBF(blockTransaction) && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
||||||
MenuItem increaseFee = new MenuItem("Increase Fee (RBF)");
|
MenuItem increaseFee = new MenuItem("Increase Fee (RBF)");
|
||||||
increaseFee.setGraphic(getIncreaseFeeRBFGlyph());
|
increaseFee.setGraphic(getIncreaseFeeRBFGlyph());
|
||||||
increaseFee.setOnAction(AE -> {
|
increaseFee.setOnAction(AE -> {
|
||||||
|
|
@ -570,7 +564,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
getItems().add(increaseFee);
|
getItems().add(increaseFee);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(canRBF(blockTransaction, wallet) && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
if(canRBF(blockTransaction) && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
||||||
MenuItem cancelTx = new MenuItem("Cancel Transaction (RBF)");
|
MenuItem cancelTx = new MenuItem("Cancel Transaction (RBF)");
|
||||||
cancelTx.setGraphic(getCancelTransactionRBFGlyph());
|
cancelTx.setGraphic(getCancelTransactionRBFGlyph());
|
||||||
cancelTx.setOnAction(AE -> {
|
cancelTx.setOnAction(AE -> {
|
||||||
|
|
@ -856,11 +850,4 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTableSizeRecalculation() {
|
|
||||||
//As per https://bugs.openjdk.org/browse/JDK-8265669 we check for cell visibility to avoid unnecessary recalculation, but this can result in false positives
|
|
||||||
//The method releaseCell in VirtualFlow is responsible for setting accumCell visibility to false after use, so check this method is calling updateItem
|
|
||||||
return StackWalker.getInstance().walk(frames -> frames.anyMatch(frame -> frame.getClassName().equals("javafx.scene.control.skin.VirtualFlow")
|
|
||||||
&& frame.getMethodName().equals("releaseCell")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.net.FeeRatesSource;
|
import com.sparrowwallet.sparrow.net.FeeRatesSource;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|
@ -8,7 +7,6 @@ import javafx.scene.Node;
|
||||||
import javafx.scene.control.Slider;
|
import javafx.scene.control.Slider;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -16,11 +14,9 @@ import static com.sparrowwallet.sparrow.AppServices.*;
|
||||||
|
|
||||||
public class FeeRangeSlider extends Slider {
|
public class FeeRangeSlider extends Slider {
|
||||||
private static final double FEE_RATE_SCROLL_INCREMENT = 0.01;
|
private static final double FEE_RATE_SCROLL_INCREMENT = 0.01;
|
||||||
private static final DecimalFormat INTEGER_FEE_RATE_FORMAT = new DecimalFormat("0");
|
|
||||||
private static final DecimalFormat FRACTIONAL_FEE_RATE_FORMAT = new DecimalFormat("0.###");
|
|
||||||
|
|
||||||
public FeeRangeSlider() {
|
public FeeRangeSlider() {
|
||||||
super(0, AppServices.getFeeRatesRange().size() - 1, 0);
|
super(0, FEE_RATES_RANGE.size() - 1, 0);
|
||||||
setMajorTickUnit(1);
|
setMajorTickUnit(1);
|
||||||
setMinorTickCount(0);
|
setMinorTickCount(0);
|
||||||
setSnapToTicks(false);
|
setSnapToTicks(false);
|
||||||
|
|
@ -31,11 +27,11 @@ public class FeeRangeSlider extends Slider {
|
||||||
setLabelFormatter(new StringConverter<>() {
|
setLabelFormatter(new StringConverter<>() {
|
||||||
@Override
|
@Override
|
||||||
public String toString(Double object) {
|
public String toString(Double object) {
|
||||||
Double feeRate = AppServices.getLongFeeRatesRange().get(object.intValue());
|
Long feeRate = LONG_FEE_RATES_RANGE.get(object.intValue());
|
||||||
if(isLongFeeRange() && feeRate >= 1000) {
|
if(isLongFeeRange() && feeRate >= 1000) {
|
||||||
return INTEGER_FEE_RATE_FORMAT.format(feeRate / 1000) + "k";
|
return feeRate / 1000 + "k";
|
||||||
}
|
}
|
||||||
return feeRate > 0d && feeRate < Transaction.DEFAULT_MIN_RELAY_FEE ? FRACTIONAL_FEE_RATE_FORMAT.format(feeRate) : INTEGER_FEE_RATE_FORMAT.format(feeRate);
|
return Long.toString(feeRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -55,10 +51,10 @@ public class FeeRangeSlider extends Slider {
|
||||||
setOnScroll(event -> {
|
setOnScroll(event -> {
|
||||||
if(event.getDeltaY() != 0) {
|
if(event.getDeltaY() != 0) {
|
||||||
double newFeeRate = getFeeRate() + (event.getDeltaY() > 0 ? FEE_RATE_SCROLL_INCREMENT : -FEE_RATE_SCROLL_INCREMENT);
|
double newFeeRate = getFeeRate() + (event.getDeltaY() > 0 ? FEE_RATE_SCROLL_INCREMENT : -FEE_RATE_SCROLL_INCREMENT);
|
||||||
if(newFeeRate < AppServices.getLongFeeRatesRange().getFirst()) {
|
if(newFeeRate < LONG_FEE_RATES_RANGE.get(0)) {
|
||||||
newFeeRate = AppServices.getLongFeeRatesRange().getFirst();
|
newFeeRate = LONG_FEE_RATES_RANGE.get(0);
|
||||||
} else if(newFeeRate > AppServices.getLongFeeRatesRange().getLast()) {
|
} else if(newFeeRate > LONG_FEE_RATES_RANGE.get(LONG_FEE_RATES_RANGE.size() - 1)) {
|
||||||
newFeeRate = AppServices.getLongFeeRatesRange().getLast();
|
newFeeRate = LONG_FEE_RATES_RANGE.get(LONG_FEE_RATES_RANGE.size() - 1);
|
||||||
}
|
}
|
||||||
setFeeRate(newFeeRate);
|
setFeeRate(newFeeRate);
|
||||||
}
|
}
|
||||||
|
|
@ -66,79 +62,27 @@ public class FeeRangeSlider extends Slider {
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getFeeRate() {
|
public double getFeeRate() {
|
||||||
return getFeeRate(AppServices.getMinimumRelayFeeRate());
|
return Math.pow(2.0, getValue());
|
||||||
}
|
|
||||||
|
|
||||||
public double getFeeRate(Double minRelayFeeRate) {
|
|
||||||
if(minRelayFeeRate >= Transaction.DEFAULT_MIN_RELAY_FEE) {
|
|
||||||
return Math.pow(2.0, getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(getValue() < 1.0d) {
|
|
||||||
if(minRelayFeeRate == 0.0d) {
|
|
||||||
return getValue();
|
|
||||||
}
|
|
||||||
return Math.pow(minRelayFeeRate, 1.0d - getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.pow(2.0, getValue() - 1.0d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFeeRate(double feeRate) {
|
public void setFeeRate(double feeRate) {
|
||||||
setFeeRate(feeRate, AppServices.getMinimumRelayFeeRate());
|
double value = Math.log(feeRate) / Math.log(2);
|
||||||
}
|
|
||||||
|
|
||||||
public void setFeeRate(double feeRate, Double minRelayFeeRate) {
|
|
||||||
double value = getValue(feeRate, minRelayFeeRate);
|
|
||||||
updateMaxFeeRange(value);
|
updateMaxFeeRange(value);
|
||||||
setValue(value);
|
setValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double getValue(double feeRate, Double minRelayFeeRate) {
|
|
||||||
double value;
|
|
||||||
|
|
||||||
if(minRelayFeeRate >= Transaction.DEFAULT_MIN_RELAY_FEE) {
|
|
||||||
value = Math.log(feeRate) / Math.log(2);
|
|
||||||
} else {
|
|
||||||
if(feeRate < Transaction.DEFAULT_MIN_RELAY_FEE) {
|
|
||||||
if(minRelayFeeRate == 0.0d) {
|
|
||||||
return feeRate;
|
|
||||||
}
|
|
||||||
value = 1.0d - (Math.log(feeRate) / Math.log(minRelayFeeRate));
|
|
||||||
} else {
|
|
||||||
value = (Math.log(feeRate) / Math.log(2.0)) + 1.0d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateFeeRange(Double minRelayFeeRate, Double previousMinRelayFeeRate) {
|
|
||||||
if(minRelayFeeRate != null && previousMinRelayFeeRate != null) {
|
|
||||||
setFeeRate(getFeeRate(previousMinRelayFeeRate), minRelayFeeRate);
|
|
||||||
}
|
|
||||||
setMinorTickCount(1);
|
|
||||||
setMinorTickCount(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateMaxFeeRange(double value) {
|
private void updateMaxFeeRange(double value) {
|
||||||
if(value >= getMax() && !isLongFeeRange()) {
|
if(value >= getMax() && !isLongFeeRange()) {
|
||||||
if(AppServices.getMinimumRelayFeeRate() < Transaction.DEFAULT_MIN_RELAY_FEE) {
|
setMax(LONG_FEE_RATES_RANGE.size() - 1);
|
||||||
setMin(1.0d);
|
|
||||||
}
|
|
||||||
setMax(AppServices.getLongFeeRatesRange().size() - 1);
|
|
||||||
updateTrackHighlight();
|
updateTrackHighlight();
|
||||||
} else if(value == getMin() && isLongFeeRange()) {
|
} else if(value == getMin() && isLongFeeRange()) {
|
||||||
if(AppServices.getMinimumRelayFeeRate() < Transaction.DEFAULT_MIN_RELAY_FEE) {
|
setMax(FEE_RATES_RANGE.size() - 1);
|
||||||
setMin(0.0d);
|
|
||||||
}
|
|
||||||
setMax(AppServices.getFeeRatesRange().size() - 1);
|
|
||||||
updateTrackHighlight();
|
updateTrackHighlight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLongFeeRange() {
|
private boolean isLongFeeRange() {
|
||||||
return getMax() > AppServices.getFeeRatesRange().size() - 1;
|
return getMax() > FEE_RATES_RANGE.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateTrackHighlight() {
|
public void updateTrackHighlight() {
|
||||||
|
|
@ -193,9 +137,9 @@ public class FeeRangeSlider extends Slider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPercentageOfFeeRange(Double feeRate) {
|
private int getPercentageOfFeeRange(Double feeRate) {
|
||||||
double index = getValue(feeRate, AppServices.getMinimumRelayFeeRate());
|
double index = Math.log(feeRate) / Math.log(2);
|
||||||
if(isLongFeeRange()) {
|
if(isLongFeeRange()) {
|
||||||
index *= ((double)AppServices.getFeeRatesRange().size() / (AppServices.getLongFeeRatesRange().size())) * 0.99;
|
index *= ((double)FEE_RATES_RANGE.size() / (LONG_FEE_RATES_RANGE.size())) * 0.99;
|
||||||
}
|
}
|
||||||
return (int)Math.round(index * 10.0);
|
return (int)Math.round(index * 10.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -398,14 +398,14 @@ public class PrivateKeySweepDialog extends Dialog<Transaction> {
|
||||||
|
|
||||||
double feeRate = feeRange.getFeeRate();
|
double feeRate = feeRange.getFeeRate();
|
||||||
long fee = (long)Math.ceil(noFeeTransaction.getVirtualSize() * feeRate);
|
long fee = (long)Math.ceil(noFeeTransaction.getVirtualSize() * feeRate);
|
||||||
if(feeRate == AppServices.getMinimumRelayFeeRate() && feeRate > 0d) {
|
if(feeRate == Transaction.DEFAULT_MIN_RELAY_FEE) {
|
||||||
fee++;
|
fee++;
|
||||||
}
|
}
|
||||||
|
|
||||||
long dustThreshold = destAddress.getScriptType().getDustThreshold(sweepOutput, Transaction.DUST_RELAY_TX_FEE);
|
long dustThreshold = destAddress.getScriptType().getDustThreshold(sweepOutput, Transaction.DUST_RELAY_TX_FEE);
|
||||||
if(total - fee <= dustThreshold) {
|
if(total - fee <= dustThreshold) {
|
||||||
feeRate = AppServices.getMinimumRelayFeeRate();
|
feeRate = Transaction.DEFAULT_MIN_RELAY_FEE;
|
||||||
fee = (long)Math.ceil(noFeeTransaction.getVirtualSize() * feeRate) + (feeRate > 0d ? 1 : 0);
|
fee = (long)Math.ceil(noFeeTransaction.getVirtualSize() * feeRate) + 1;
|
||||||
|
|
||||||
if(total - fee <= dustThreshold) {
|
if(total - fee <= dustThreshold) {
|
||||||
AppServices.showErrorDialog("Insufficient funds", "The unspent outputs for this private key contain insufficient funds to spend (" + total + " sats).");
|
AppServices.showErrorDialog("Insufficient funds", "The unspent outputs for this private key contain insufficient funds to spend (" + total + " sats).");
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,6 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
});
|
});
|
||||||
webcamDeviceProperty.addListener((_, _, newValue) -> {
|
webcamDeviceProperty.addListener((_, _, newValue) -> {
|
||||||
Config.get().setWebcamDevice(newValue.getName());
|
Config.get().setWebcamDevice(newValue.getName());
|
||||||
Config.get().setWebcamDeviceId(newValue.getUniqueId());
|
|
||||||
if(!Objects.equals(webcamService.getDevice(), newValue)) {
|
if(!Objects.equals(webcamService.getDevice(), newValue)) {
|
||||||
webcamService.cancel();
|
webcamService.cancel();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.protocol.Script;
|
import com.sparrowwallet.drongo.protocol.Script;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptChunk;
|
import com.sparrowwallet.drongo.protocol.ScriptChunk;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptOpCodes;
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import org.controlsfx.control.decoration.Decorator;
|
import org.controlsfx.control.decoration.Decorator;
|
||||||
import org.controlsfx.control.decoration.GraphicDecoration;
|
import org.controlsfx.control.decoration.GraphicDecoration;
|
||||||
|
|
@ -54,11 +53,7 @@ public class ScriptArea extends CodeArea {
|
||||||
for (int i = 0; i < script.getChunks().size(); i++) {
|
for (int i = 0; i < script.getChunks().size(); i++) {
|
||||||
ScriptChunk chunk = script.getChunks().get(i);
|
ScriptChunk chunk = script.getChunks().get(i);
|
||||||
if(chunk.isOpCode()) {
|
if(chunk.isOpCode()) {
|
||||||
if(chunk.getOpcode() == ScriptOpCodes.OP_0 && witnessScript != null) {
|
append(chunk.toString(), "script-opcode");
|
||||||
append("<empty>", "script-other");
|
|
||||||
} else {
|
|
||||||
append(chunk.toString(), "script-opcode");
|
|
||||||
}
|
|
||||||
} else if(chunk.isPubKey()) {
|
} else if(chunk.isPubKey()) {
|
||||||
append("<pubkey" + pubKeyCount++ + ">", "script-pubkey");
|
append("<pubkey" + pubKeyCount++ + ">", "script-pubkey");
|
||||||
} else if(chunk.isSignature()) {
|
} else if(chunk.isSignature()) {
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,12 @@ import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
import com.sparrowwallet.drongo.OsType;
|
import com.sparrowwallet.drongo.OsType;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
||||||
import com.sparrowwallet.drongo.dns.DnsPayment;
|
|
||||||
import com.sparrowwallet.drongo.dns.DnsPaymentCache;
|
|
||||||
import com.sparrowwallet.drongo.dns.DnsPaymentResolver;
|
|
||||||
import com.sparrowwallet.drongo.dns.DnsPaymentValidationException;
|
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.drongo.uri.BitcoinURIParseException;
|
|
||||||
import com.sparrowwallet.drongo.wallet.Payment;
|
import com.sparrowwallet.drongo.wallet.Payment;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.event.RequestConnectEvent;
|
|
||||||
import com.sparrowwallet.sparrow.glyphfont.GlyphUtils;
|
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.concurrent.Service;
|
|
||||||
import javafx.concurrent.Task;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.input.Clipboard;
|
import javafx.scene.input.Clipboard;
|
||||||
|
|
@ -32,20 +18,19 @@ import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.controlsfx.control.spreadsheet.*;
|
import org.controlsfx.control.spreadsheet.*;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class SendToManyDialog extends Dialog<List<Payment>> {
|
public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
private final BitcoinUnit bitcoinUnit;
|
private final BitcoinUnit bitcoinUnit;
|
||||||
private final SpreadsheetView spreadsheetView;
|
private final SpreadsheetView spreadsheetView;
|
||||||
public static final SendToAddressCellType SEND_TO_ADDRESS = new SendToAddressCellType();
|
public static final AddressCellType ADDRESS = new AddressCellType();
|
||||||
|
|
||||||
public SendToManyDialog(BitcoinUnit bitcoinUnit, List<Payment> payments) {
|
public SendToManyDialog(BitcoinUnit bitcoinUnit, List<Payment> payments) {
|
||||||
this.bitcoinUnit = bitcoinUnit;
|
this.bitcoinUnit = bitcoinUnit;
|
||||||
|
|
@ -83,16 +68,14 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
dialogPane.setContent(stackPane);
|
dialogPane.setContent(stackPane);
|
||||||
|
|
||||||
dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
|
dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
|
||||||
Button okButton = (Button) dialogPane.lookupButton(ButtonType.OK);
|
|
||||||
okButton.addEventFilter(ActionEvent.ACTION, event -> {
|
|
||||||
getPayments();
|
|
||||||
event.consume();
|
|
||||||
});
|
|
||||||
|
|
||||||
final ButtonType loadCsvButtonType = new javafx.scene.control.ButtonType("Load CSV", ButtonBar.ButtonData.LEFT);
|
final ButtonType loadCsvButtonType = new javafx.scene.control.ButtonType("Load CSV", ButtonBar.ButtonData.LEFT);
|
||||||
dialogPane.getButtonTypes().add(loadCsvButtonType);
|
dialogPane.getButtonTypes().add(loadCsvButtonType);
|
||||||
|
|
||||||
setResultConverter((_) -> null);
|
setResultConverter((dialogButton) -> {
|
||||||
|
ButtonBar.ButtonData data = dialogButton == null ? null : dialogButton.getButtonData();
|
||||||
|
return data == ButtonBar.ButtonData.OK_DONE ? getPayments() : null;
|
||||||
|
});
|
||||||
|
|
||||||
dialogPane.setPrefWidth(850);
|
dialogPane.setPrefWidth(850);
|
||||||
dialogPane.setPrefHeight(500);
|
dialogPane.setPrefHeight(500);
|
||||||
|
|
@ -102,24 +85,18 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid getGrid(List<Payment> payments) {
|
private Grid getGrid(List<Payment> payments) {
|
||||||
return createGrid(payments.stream().map(payment -> new SendToPayment(payment, SendToAddress.fromPayment(payment))).collect(Collectors.toList()));
|
int rowCount = payments.size();
|
||||||
}
|
|
||||||
|
|
||||||
private Grid createGrid(List<SendToPayment> sendToPayments) {
|
|
||||||
int rowCount = sendToPayments.size();
|
|
||||||
int columnCount = 3;
|
int columnCount = 3;
|
||||||
GridBase grid = new GridBase(rowCount, columnCount);
|
GridBase grid = new GridBase(rowCount, columnCount);
|
||||||
ObservableList<ObservableList<SpreadsheetCell>> rows = FXCollections.observableArrayList();
|
ObservableList<ObservableList<SpreadsheetCell>> rows = FXCollections.observableArrayList();
|
||||||
for(int row = 0; row < grid.getRowCount(); ++row) {
|
for(int row = 0; row < grid.getRowCount(); ++row) {
|
||||||
SendToPayment sendToPayment = sendToPayments.get(row);
|
|
||||||
final ObservableList<SpreadsheetCell> list = FXCollections.observableArrayList();
|
final ObservableList<SpreadsheetCell> list = FXCollections.observableArrayList();
|
||||||
|
|
||||||
SendToAddress sendToAddress = sendToPayment.sendToAddress();
|
SpreadsheetCell addressCell = ADDRESS.createCell(row, 0, 1, 1, payments.get(row).getAddress());
|
||||||
SpreadsheetCell addressCell = SEND_TO_ADDRESS.createCell(row, 0, 1, 1, sendToAddress);
|
|
||||||
addressCell.getStyleClass().add("fixed-width");
|
addressCell.getStyleClass().add("fixed-width");
|
||||||
list.add(addressCell);
|
list.add(addressCell);
|
||||||
|
|
||||||
double amount = (double)sendToPayment.payment().getAmount();
|
double amount = (double)payments.get(row).getAmount();
|
||||||
if(bitcoinUnit == BitcoinUnit.BTC) {
|
if(bitcoinUnit == BitcoinUnit.BTC) {
|
||||||
amount = amount / Transaction.SATOSHIS_PER_BITCOIN;
|
amount = amount / Transaction.SATOSHIS_PER_BITCOIN;
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +108,7 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
}
|
}
|
||||||
list.add(amountCell);
|
list.add(amountCell);
|
||||||
|
|
||||||
list.add(SpreadsheetCellType.STRING.createCell(row, 2, 1, 1, sendToPayment.payment().getLabel()));
|
list.add(SpreadsheetCellType.STRING.createCell(row, 2, 1, 1, payments.get(row).getLabel()));
|
||||||
rows.add(list);
|
rows.add(list);
|
||||||
}
|
}
|
||||||
grid.setRows(rows);
|
grid.setRows(rows);
|
||||||
|
|
@ -140,49 +117,32 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getPayments() {
|
private List<Payment> getPayments() {
|
||||||
if(needsResolution() && Config.get().hasServer() && !AppServices.isConnected() && !AppServices.isConnecting()) {
|
List<Payment> payments = new ArrayList<>();
|
||||||
if(Config.get().getConnectToResolve() == null || Config.get().getConnectToResolve() == Boolean.FALSE) {
|
Grid grid = spreadsheetView.getGrid();
|
||||||
Platform.runLater(() -> {
|
String firstLabel = null;
|
||||||
ConfirmationAlert confirmationAlert = new ConfirmationAlert("Connect to resolve?", "You are currently offline. Connect to resolve the addresses?", ButtonType.NO, ButtonType.YES);
|
for(int row = 0; row < grid.getRowCount(); row++) {
|
||||||
Optional<ButtonType> optType = confirmationAlert.showAndWait();
|
|
||||||
if(confirmationAlert.isDontAskAgain() && optType.isPresent()) {
|
|
||||||
Config.get().setConnectToResolve(optType.get() == ButtonType.YES);
|
|
||||||
}
|
|
||||||
if(optType.isPresent() && optType.get() == ButtonType.YES) {
|
|
||||||
EventManager.get().post(new RequestConnectEvent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Platform.runLater(() -> EventManager.get().post(new RequestConnectEvent()));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatePaymentsService createPaymentsService = new CreatePaymentsService();
|
|
||||||
createPaymentsService.setOnSucceeded(_ -> {
|
|
||||||
List<Payment> payments = createPaymentsService.getValue();
|
|
||||||
if(payments != null) {
|
|
||||||
setResult(payments);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
createPaymentsService.setOnFailed(event -> {
|
|
||||||
Throwable ex = event.getSource().getException();
|
|
||||||
AppServices.showErrorDialog("Error creating payments", ex.getMessage());
|
|
||||||
});
|
|
||||||
createPaymentsService.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean needsResolution() {
|
|
||||||
for(int row = 0; row < spreadsheetView.getGrid().getRowCount(); row++) {
|
|
||||||
ObservableList<SpreadsheetCell> rowCells = spreadsheetView.getItems().get(row);
|
ObservableList<SpreadsheetCell> rowCells = spreadsheetView.getItems().get(row);
|
||||||
SendToAddress sendToAddress = (SendToAddress)rowCells.getFirst().getItem();
|
Address address = (Address)rowCells.get(0).getItem();
|
||||||
if(sendToAddress.hrn != null && DnsPaymentCache.getDnsPayment(sendToAddress.hrn) == null) {
|
Double value = (Double)rowCells.get(1).getItem();
|
||||||
return true;
|
String label = (String)rowCells.get(2).getItem();
|
||||||
|
if(firstLabel == null) {
|
||||||
|
firstLabel = label;
|
||||||
|
}
|
||||||
|
if(label == null || label.isEmpty()) {
|
||||||
|
label = firstLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(address != null && value != null) {
|
||||||
|
if(bitcoinUnit == BitcoinUnit.BTC) {
|
||||||
|
value = value * Transaction.SATOSHIS_PER_BITCOIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
payments.add(new Payment(address, label, value.longValue(), false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return payments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SendToManyDialogPane extends DialogPane {
|
private class SendToManyDialogPane extends DialogPane {
|
||||||
|
|
@ -192,7 +152,7 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
if(buttonType.getButtonData() == ButtonBar.ButtonData.LEFT) {
|
if(buttonType.getButtonData() == ButtonBar.ButtonData.LEFT) {
|
||||||
Button loadButton = new Button(buttonType.getText());
|
Button loadButton = new Button(buttonType.getText());
|
||||||
loadButton.setGraphicTextGap(5);
|
loadButton.setGraphicTextGap(5);
|
||||||
loadButton.setGraphic(GlyphUtils.getUpArrowGlyph());
|
loadButton.setGraphic(getGlyph(FontAwesome5.Glyph.ARROW_UP));
|
||||||
final ButtonBar.ButtonData buttonData = buttonType.getButtonData();
|
final ButtonBar.ButtonData buttonData = buttonType.getButtonData();
|
||||||
ButtonBar.setButtonData(loadButton, buttonData);
|
ButtonBar.setButtonData(loadButton, buttonData);
|
||||||
loadButton.setOnAction(event -> {
|
loadButton.setOnAction(event -> {
|
||||||
|
|
@ -207,7 +167,7 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
File file = fileChooser.showOpenDialog(this.getScene().getWindow());
|
File file = fileChooser.showOpenDialog(this.getScene().getWindow());
|
||||||
if(file != null) {
|
if(file != null) {
|
||||||
try {
|
try {
|
||||||
List<SendToPayment> csvPayments = new ArrayList<>();
|
List<Payment> csvPayments = new ArrayList<>();
|
||||||
try(Reader reader = new FileReader(file, StandardCharsets.UTF_8)) {
|
try(Reader reader = new FileReader(file, StandardCharsets.UTF_8)) {
|
||||||
CsvReader csvReader = new CsvReader(reader);
|
CsvReader csvReader = new CsvReader(reader);
|
||||||
while(csvReader.readRecord()) {
|
while(csvReader.readRecord()) {
|
||||||
|
|
@ -223,22 +183,9 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
} else {
|
} else {
|
||||||
amount = Long.parseLong(csvReader.get(1).replace(",", ""));
|
amount = Long.parseLong(csvReader.get(1).replace(",", ""));
|
||||||
}
|
}
|
||||||
|
Address address = Address.fromString(csvReader.get(0));
|
||||||
String label = csvReader.get(2);
|
String label = csvReader.get(2);
|
||||||
Optional<String> optDnsPaymentHrn = DnsPayment.getHrn(csvReader.get(0));
|
csvPayments.add(new Payment(address, label, amount, false));
|
||||||
if(optDnsPaymentHrn.isPresent()) {
|
|
||||||
Payment payment = new Payment(null, label, amount, false);
|
|
||||||
csvPayments.add(new SendToPayment(payment, new SendToAddress(optDnsPaymentHrn.get())));
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from(csvReader.get(0));
|
|
||||||
Payment payment = new SilentPayment(silentPaymentAddress, label, amount, false);
|
|
||||||
csvPayments.add(new SendToPayment(payment, SendToAddress.fromPayment(payment)));
|
|
||||||
} catch(Exception e) {
|
|
||||||
Address address = Address.fromString(csvReader.get(0));
|
|
||||||
Payment payment = new Payment(address, label, amount, false);
|
|
||||||
csvPayments.add(new SendToPayment(payment, SendToAddress.fromPayment(payment)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(NumberFormatException e) {
|
} catch(NumberFormatException e) {
|
||||||
//ignore and continue - probably a header line
|
//ignore and continue - probably a header line
|
||||||
} catch(InvalidAddressException e) {
|
} catch(InvalidAddressException e) {
|
||||||
|
|
@ -251,7 +198,7 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
spreadsheetView.setGrid(createGrid(csvPayments));
|
spreadsheetView.setGrid(getGrid(csvPayments));
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
AppServices.showErrorDialog("Cannot load CSV", e.getMessage());
|
AppServices.showErrorDialog("Cannot load CSV", e.getMessage());
|
||||||
|
|
@ -266,18 +213,24 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Glyph getGlyph(FontAwesome5.Glyph glyphName) {
|
||||||
|
Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, glyphName);
|
||||||
|
glyph.setFontSize(11);
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SendToAddressCellType extends SpreadsheetCellType<SendToAddress> {
|
public static class AddressCellType extends SpreadsheetCellType<Address> {
|
||||||
public SendToAddressCellType() {
|
public AddressCellType() {
|
||||||
this(new StringConverterWithFormat<>(new SendToAddressStringConverter()) {
|
this(new StringConverterWithFormat<>(new AddressStringConverter()) {
|
||||||
@Override
|
@Override
|
||||||
public String toString(SendToAddress item) {
|
public String toString(Address item) {
|
||||||
return toStringFormat(item, ""); //$NON-NLS-1$
|
return toStringFormat(item, ""); //$NON-NLS-1$
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SendToAddress fromString(String str) {
|
public Address fromString(String str) {
|
||||||
if(str == null || str.isEmpty()) { //$NON-NLS-1$
|
if(str == null || str.isEmpty()) { //$NON-NLS-1$
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -286,7 +239,7 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toStringFormat(SendToAddress item, String format) {
|
public String toStringFormat(Address item, String format) {
|
||||||
try {
|
try {
|
||||||
if(item == null) {
|
if(item == null) {
|
||||||
return ""; //$NON-NLS-1$
|
return ""; //$NON-NLS-1$
|
||||||
|
|
@ -300,7 +253,7 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendToAddressCellType(StringConverter<SendToAddress> converter) {
|
public AddressCellType(StringConverter<Address> converter) {
|
||||||
super(converter);
|
super(converter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,7 +263,7 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpreadsheetCell createCell(final int row, final int column, final int rowSpan, final int columnSpan,
|
public SpreadsheetCell createCell(final int row, final int column, final int rowSpan, final int columnSpan,
|
||||||
final SendToAddress value) {
|
final Address value) {
|
||||||
SpreadsheetCell cell = new SpreadsheetCellBase(row, column, rowSpan, columnSpan, this);
|
SpreadsheetCell cell = new SpreadsheetCellBase(row, column, rowSpan, columnSpan, this);
|
||||||
cell.setItem(value);
|
cell.setItem(value);
|
||||||
return cell;
|
return cell;
|
||||||
|
|
@ -323,7 +276,7 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean match(Object value, Object... options) {
|
public boolean match(Object value, Object... options) {
|
||||||
if(value instanceof SendToAddress)
|
if(value instanceof Address)
|
||||||
return true;
|
return true;
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
|
|
@ -336,9 +289,9 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SendToAddress convertValue(Object value) {
|
public Address convertValue(Object value) {
|
||||||
if(value instanceof SendToAddress)
|
if(value instanceof Address)
|
||||||
return (SendToAddress)value;
|
return (Address)value;
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
return converter.fromString(value == null ? null : value.toString());
|
return converter.fromString(value == null ? null : value.toString());
|
||||||
|
|
@ -349,155 +302,13 @@ public class SendToManyDialog extends Dialog<List<Payment>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(SendToAddress item) {
|
public String toString(Address item) {
|
||||||
return converter.toString(item);
|
return converter.toString(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(SendToAddress item, String format) {
|
public String toString(Address item, String format) {
|
||||||
return ((StringConverterWithFormat<SendToAddress>)converter).toStringFormat(item, format);
|
return ((StringConverterWithFormat<Address>)converter).toStringFormat(item, format);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static class SendToAddress {
|
|
||||||
private final String hrn;
|
|
||||||
private final Address address;
|
|
||||||
private final SilentPaymentAddress silentPaymentAddress;
|
|
||||||
|
|
||||||
public SendToAddress(String hrn) {
|
|
||||||
this.hrn = hrn;
|
|
||||||
this.address = null;
|
|
||||||
this.silentPaymentAddress = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SendToAddress(Address address) {
|
|
||||||
this.hrn = null;
|
|
||||||
this.address = address;
|
|
||||||
this.silentPaymentAddress = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SendToAddress(SilentPaymentAddress silentPaymentAddress) {
|
|
||||||
this.hrn = null;
|
|
||||||
this.address = null;
|
|
||||||
this.silentPaymentAddress = silentPaymentAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return hrn == null ? silentPaymentAddress == null ? (address == null ? null : address.toString()) : silentPaymentAddress.toString() : hrn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SendToAddress fromPayment(Payment payment) {
|
|
||||||
DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(payment);
|
|
||||||
if(dnsPayment != null) {
|
|
||||||
return new SendToAddress(dnsPayment.hrn());
|
|
||||||
}
|
|
||||||
return payment instanceof SilentPayment ? new SendToAddress(((SilentPayment)payment).getSilentPaymentAddress()) : new SendToAddress(payment.getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Payment toPayment(String label, long value, boolean sendMax) throws DnsPaymentValidationException, IOException, ExecutionException, InterruptedException, BitcoinURIParseException {
|
|
||||||
if(hrn != null) {
|
|
||||||
DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(hrn);
|
|
||||||
if(dnsPayment == null) {
|
|
||||||
DnsPaymentResolver resolver = new DnsPaymentResolver(hrn);
|
|
||||||
Optional<DnsPayment> optDnsPayment = resolver.resolve();
|
|
||||||
if(optDnsPayment.isPresent()) {
|
|
||||||
dnsPayment = optDnsPayment.get();
|
|
||||||
if(dnsPayment.hasAddress()) {
|
|
||||||
DnsPaymentCache.putDnsPayment(dnsPayment.bitcoinURI().getAddress(), dnsPayment);
|
|
||||||
} else if(dnsPayment.hasSilentPaymentAddress()) {
|
|
||||||
DnsPaymentCache.putDnsPayment(dnsPayment.bitcoinURI().getSilentPaymentAddress(), dnsPayment);
|
|
||||||
}
|
|
||||||
return getPayment(optDnsPayment.get(), label, value, sendMax);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Payment to " + hrn + " could not be resolved.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return getPayment(dnsPayment, label, value, sendMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(silentPaymentAddress != null) {
|
|
||||||
return new SilentPayment(silentPaymentAddress, label, value, sendMax);
|
|
||||||
} else {
|
|
||||||
return new Payment(address, label, value, sendMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Payment getPayment(DnsPayment dnsPayment, String label, long value, boolean sendMax) {
|
|
||||||
if(dnsPayment.hasAddress()) {
|
|
||||||
return new Payment(dnsPayment.bitcoinURI().getAddress(), label, value, sendMax);
|
|
||||||
} else if(dnsPayment.hasSilentPaymentAddress()) {
|
|
||||||
return new SilentPayment(dnsPayment.bitcoinURI().getSilentPaymentAddress(), label, value, sendMax);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Payment to " + dnsPayment + " has no associated address.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SendToAddressStringConverter extends StringConverter<SendToAddress> {
|
|
||||||
private final AddressStringConverter addressStringConverter = new AddressStringConverter();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SendToAddress fromString(String value) {
|
|
||||||
Optional<String> optDnsPaymentHrn = DnsPayment.getHrn(value);
|
|
||||||
if(optDnsPaymentHrn.isPresent()) {
|
|
||||||
return new SendToAddress(optDnsPaymentHrn.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from(value);
|
|
||||||
return new SendToAddress(silentPaymentAddress);
|
|
||||||
} catch(Exception e) {
|
|
||||||
Address address = addressStringConverter.fromString(value);
|
|
||||||
return address == null ? null : new SendToAddress(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString(SendToAddress value) {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CreatePaymentsService extends Service<List<Payment>> {
|
|
||||||
@Override
|
|
||||||
protected Task<List<Payment>> createTask() {
|
|
||||||
return new Task<>() {
|
|
||||||
@Override
|
|
||||||
protected List<Payment> call() throws Exception {
|
|
||||||
return getPayments();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Payment> getPayments() throws DnsPaymentValidationException, IOException, ExecutionException, InterruptedException, BitcoinURIParseException {
|
|
||||||
List<Payment> payments = new ArrayList<>();
|
|
||||||
Grid grid = spreadsheetView.getGrid();
|
|
||||||
String firstLabel = null;
|
|
||||||
for(int row = 0; row < grid.getRowCount(); row++) {
|
|
||||||
ObservableList<SpreadsheetCell> rowCells = spreadsheetView.getItems().get(row);
|
|
||||||
SendToAddress sendToAddress = (SendToAddress)rowCells.get(0).getItem();
|
|
||||||
Double value = (Double)rowCells.get(1).getItem();
|
|
||||||
String label = (String)rowCells.get(2).getItem();
|
|
||||||
if(firstLabel == null) {
|
|
||||||
firstLabel = label;
|
|
||||||
}
|
|
||||||
if(label == null || label.isEmpty()) {
|
|
||||||
label = firstLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sendToAddress != null && value != null) {
|
|
||||||
if(bitcoinUnit == BitcoinUnit.BTC) {
|
|
||||||
value = value * Transaction.SATOSHIS_PER_BITCOIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
payments.add(sendToAddress.toPayment(label, value.longValue(), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return payments;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private record SendToPayment(Payment payment, SendToAddress sendToAddress) {}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,8 @@ import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.OsType;
|
import com.sparrowwallet.drongo.OsType;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||||
import com.sparrowwallet.drongo.dns.DnsPayment;
|
|
||||||
import com.sparrowwallet.drongo.dns.DnsPaymentCache;
|
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.*;
|
import com.sparrowwallet.sparrow.*;
|
||||||
|
|
@ -26,7 +22,6 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.embed.swing.SwingFXUtils;
|
import javafx.embed.swing.SwingFXUtils;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.HPos;
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Group;
|
import javafx.scene.Group;
|
||||||
|
|
@ -108,7 +103,6 @@ public class TransactionDiagram extends GridPane {
|
||||||
expandedDiagram.setId("transactionDiagram");
|
expandedDiagram.setId("transactionDiagram");
|
||||||
expandedDiagram.setExpanded(true);
|
expandedDiagram.setExpanded(true);
|
||||||
expandedDiagram.setFinal(isFinal());
|
expandedDiagram.setFinal(isFinal());
|
||||||
expandedDiagram.setMaxWidth(AppServices.getActiveWindow().getWidth() - 200);
|
|
||||||
updateDerivedDiagram(expandedDiagram);
|
updateDerivedDiagram(expandedDiagram);
|
||||||
|
|
||||||
HBox buttonBox = new HBox();
|
HBox buttonBox = new HBox();
|
||||||
|
|
@ -126,7 +120,7 @@ public class TransactionDiagram extends GridPane {
|
||||||
AppServices.setStageIcon(stage);
|
AppServices.setStageIcon(stage);
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
stage.setOnShowing(e -> {
|
stage.setOnShowing(e -> {
|
||||||
AppServices.moveToActiveWindowScreen(stage, expandedDiagram.getMaxWidth(), 460);
|
AppServices.moveToActiveWindowScreen(stage, 600, 460);
|
||||||
});
|
});
|
||||||
stage.setOnHidden(e -> {
|
stage.setOnHidden(e -> {
|
||||||
expandedDiagram = null;
|
expandedDiagram = null;
|
||||||
|
|
@ -143,39 +137,6 @@ public class TransactionDiagram extends GridPane {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public TransactionDiagram() {
|
|
||||||
ColumnConstraints col1 = new ColumnConstraints();
|
|
||||||
col1.setPrefWidth(22);
|
|
||||||
col1.setHgrow(Priority.NEVER);
|
|
||||||
|
|
||||||
ColumnConstraints col2 = new ColumnConstraints();
|
|
||||||
col2.setHgrow(Priority.ALWAYS);
|
|
||||||
col2.setPercentWidth(25);
|
|
||||||
col2.setFillWidth(true);
|
|
||||||
|
|
||||||
ColumnConstraints col3 = new ColumnConstraints();
|
|
||||||
col3.setPrefWidth(140);
|
|
||||||
col3.setHgrow(Priority.NEVER);
|
|
||||||
|
|
||||||
ColumnConstraints col4 = new ColumnConstraints();
|
|
||||||
Label label = new Label();
|
|
||||||
col4.setMinWidth(TextUtils.computeTextWidth(label.getFont(), "Transaction", 0) + 20);
|
|
||||||
col4.setHgrow(Priority.NEVER);
|
|
||||||
col4.setHalignment(HPos.CENTER);
|
|
||||||
|
|
||||||
ColumnConstraints col5 = new ColumnConstraints();
|
|
||||||
col5.setPrefWidth(140);
|
|
||||||
col5.setHgrow(Priority.NEVER);
|
|
||||||
|
|
||||||
ColumnConstraints col6 = new ColumnConstraints();
|
|
||||||
col6.setHgrow(Priority.ALWAYS);
|
|
||||||
col6.setPercentWidth(25);
|
|
||||||
col6.setFillWidth(true);
|
|
||||||
|
|
||||||
getColumnConstraints().addAll(col1, col2, col3, col4, col5, col6);
|
|
||||||
setPadding(new Insets(0, 0, 0, 40));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(WalletTransaction walletTx) {
|
public void update(WalletTransaction walletTx) {
|
||||||
setMinHeight(getDiagramHeight());
|
setMinHeight(getDiagramHeight());
|
||||||
setMaxHeight(getDiagramHeight());
|
setMaxHeight(getDiagramHeight());
|
||||||
|
|
@ -204,7 +165,7 @@ public class TransactionDiagram extends GridPane {
|
||||||
|
|
||||||
VBox messagePane = new VBox();
|
VBox messagePane = new VBox();
|
||||||
messagePane.setPrefHeight(getDiagramHeight());
|
messagePane.setPrefHeight(getDiagramHeight());
|
||||||
messagePane.setPadding(new Insets(0, 10, 0, 10));
|
messagePane.setPadding(new Insets(0, 10, 0, 280));
|
||||||
messagePane.setAlignment(Pos.CENTER);
|
messagePane.setAlignment(Pos.CENTER);
|
||||||
messagePane.getChildren().add(createSpacer());
|
messagePane.getChildren().add(createSpacer());
|
||||||
|
|
||||||
|
|
@ -264,6 +225,7 @@ public class TransactionDiagram extends GridPane {
|
||||||
GridPane.setConstraints(outputsPane, 5, 0);
|
GridPane.setConstraints(outputsPane, 5, 0);
|
||||||
|
|
||||||
getChildren().clear();
|
getChildren().clear();
|
||||||
|
getChildren().addAll(inputsTypePane, inputsPane, inputsLinesPane, txPane, outputsLinesPane, outputsPane);
|
||||||
|
|
||||||
List<Payment> userPayments = getUserPayments();
|
List<Payment> userPayments = getUserPayments();
|
||||||
if(!isFinal() && userPayments.size() > 1) {
|
if(!isFinal() && userPayments.size() > 1) {
|
||||||
|
|
@ -272,8 +234,6 @@ public class TransactionDiagram extends GridPane {
|
||||||
getChildren().add(totalsPane);
|
getChildren().add(totalsPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildren().addAll(inputsTypePane, inputsPane, inputsLinesPane, txPane, outputsLinesPane, outputsPane);
|
|
||||||
|
|
||||||
if(contextMenu == null) {
|
if(contextMenu == null) {
|
||||||
contextMenu = new ContextMenu();
|
contextMenu = new ContextMenu();
|
||||||
MenuItem menuItem = new MenuItem("Save as Image...");
|
MenuItem menuItem = new MenuItem("Save as Image...");
|
||||||
|
|
@ -447,6 +407,8 @@ public class TransactionDiagram extends GridPane {
|
||||||
|
|
||||||
private Pane getInputsLabels(List<Map<BlockTransactionHashIndex, WalletNode>> displayedUtxoSets) {
|
private Pane getInputsLabels(List<Map<BlockTransactionHashIndex, WalletNode>> displayedUtxoSets) {
|
||||||
VBox inputsBox = new VBox();
|
VBox inputsBox = new VBox();
|
||||||
|
inputsBox.setMaxWidth(isExpanded() ? 300 : 150);
|
||||||
|
inputsBox.setPrefWidth(isExpanded() ? 230 : 150);
|
||||||
inputsBox.setPadding(new Insets(0, 10, 0, 10));
|
inputsBox.setPadding(new Insets(0, 10, 0, 10));
|
||||||
inputsBox.minHeightProperty().bind(minHeightProperty());
|
inputsBox.minHeightProperty().bind(minHeightProperty());
|
||||||
inputsBox.setAlignment(Pos.BASELINE_RIGHT);
|
inputsBox.setAlignment(Pos.BASELINE_RIGHT);
|
||||||
|
|
@ -678,8 +640,7 @@ public class TransactionDiagram extends GridPane {
|
||||||
|
|
||||||
double width = 140.0;
|
double width = 140.0;
|
||||||
long sum = walletTx.getTotal();
|
long sum = walletTx.getTotal();
|
||||||
List<Long> values = walletTx.getOutputs().stream().filter(output -> !(output instanceof WalletTransaction.NonAddressOutput))
|
List<Long> values = walletTx.getTransaction().getOutputs().stream().filter(txo -> txo.getScript().getToAddress() != null).map(TransactionOutput::getValue).collect(Collectors.toList());
|
||||||
.map(output -> output.getTransactionOutput().getValue()).collect(Collectors.toList());
|
|
||||||
values.add(walletTx.getFee());
|
values.add(walletTx.getFee());
|
||||||
int numOutputs = displayedPayments.size() + walletTx.getChangeMap().size() + 1;
|
int numOutputs = displayedPayments.size() + walletTx.getChangeMap().size() + 1;
|
||||||
for(int i = 1; i <= numOutputs; i++) {
|
for(int i = 1; i <= numOutputs; i++) {
|
||||||
|
|
@ -715,6 +676,8 @@ public class TransactionDiagram extends GridPane {
|
||||||
|
|
||||||
private Pane getOutputsLabels(List<Payment> displayedPayments) {
|
private Pane getOutputsLabels(List<Payment> displayedPayments) {
|
||||||
VBox outputsBox = new VBox();
|
VBox outputsBox = new VBox();
|
||||||
|
outputsBox.setMaxWidth(isExpanded() ? 350 : 150);
|
||||||
|
outputsBox.setPrefWidth(isExpanded() ? 230 : 150);
|
||||||
outputsBox.setPadding(new Insets(0, 20, 0, 10));
|
outputsBox.setPadding(new Insets(0, 20, 0, 10));
|
||||||
outputsBox.setAlignment(Pos.BASELINE_LEFT);
|
outputsBox.setAlignment(Pos.BASELINE_LEFT);
|
||||||
outputsBox.getChildren().add(createSpacer());
|
outputsBox.getChildren().add(createSpacer());
|
||||||
|
|
@ -723,16 +686,15 @@ public class TransactionDiagram extends GridPane {
|
||||||
for(Payment payment : displayedPayments) {
|
for(Payment payment : displayedPayments) {
|
||||||
Glyph outputGlyph = GlyphUtils.getOutputGlyph(walletTx, payment);
|
Glyph outputGlyph = GlyphUtils.getOutputGlyph(walletTx, payment);
|
||||||
boolean labelledPayment = outputGlyph.getStyleClass().stream().anyMatch(style -> List.of("premix-icon", "badbank-icon", "whirlpoolfee-icon", "anchor-icon").contains(style)) || payment instanceof AdditionalPayment || payment.getLabel() != null;
|
boolean labelledPayment = outputGlyph.getStyleClass().stream().anyMatch(style -> List.of("premix-icon", "badbank-icon", "whirlpoolfee-icon", "anchor-icon").contains(style)) || payment instanceof AdditionalPayment || payment.getLabel() != null;
|
||||||
Label recipientLabel = new Label(payment.getLabel() == null || payment.getType() == Payment.Type.FAKE_MIX || payment.getType() == Payment.Type.MIX ? payment.toString().substring(0, 8) + "..." : payment.getLabel(), outputGlyph);
|
Label recipientLabel = new Label(payment.getLabel() == null || payment.getType() == Payment.Type.FAKE_MIX || payment.getType() == Payment.Type.MIX ? payment.getAddress().toString().substring(0, 8) + "..." : payment.getLabel(), outputGlyph);
|
||||||
recipientLabel.getStyleClass().add("output-label");
|
recipientLabel.getStyleClass().add("output-label");
|
||||||
recipientLabel.getStyleClass().add(labelledPayment ? "payment-label" : "recipient-label");
|
recipientLabel.getStyleClass().add(labelledPayment ? "payment-label" : "recipient-label");
|
||||||
Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
|
Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
|
||||||
WalletNode toNode = payment instanceof WalletNodePayment walletNodePayment ? walletNodePayment.getWalletNode() : null;
|
WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null;
|
||||||
Wallet toBip47Wallet = getBip47SendWallet(payment);
|
Wallet toBip47Wallet = getBip47SendWallet(payment);
|
||||||
DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(payment);
|
|
||||||
Tooltip recipientTooltip = new Tooltip((toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ")
|
Tooltip recipientTooltip = new Tooltip((toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ")
|
||||||
+ getSatsValue(payment.getAmount()) + " sats to "
|
+ getSatsValue(payment.getAmount()) + " sats to "
|
||||||
+ (payment instanceof AdditionalPayment ? (isExpanded() ? "\n" : "(click to expand)\n") + payment : (toWallet == null ? (dnsPayment == null ? (payment.getLabel() == null ? (toNode != null ? toNode : (toBip47Wallet == null ? "external address" : toBip47Wallet.getDisplayName())) : payment.getLabel()) : dnsPayment.toString()) : toWallet.getFullDisplayName()) + "\n" + payment.getDisplayAddress())
|
+ (payment instanceof AdditionalPayment ? (isExpanded() ? "\n" : "(click to expand)\n") + payment : (toWallet == null ? (payment.getLabel() == null ? (toNode != null ? toNode : (toBip47Wallet == null ? "external address" : toBip47Wallet.getDisplayName())) : payment.getLabel()) : toWallet.getFullDisplayName()) + "\n" + payment.getAddress().toString())
|
||||||
+ (walletTx.isDuplicateAddress(payment) ? " (Duplicate)" : ""));
|
+ (walletTx.isDuplicateAddress(payment) ? " (Duplicate)" : ""));
|
||||||
recipientTooltip.getStyleClass().add("recipient-label");
|
recipientTooltip.getStyleClass().add("recipient-label");
|
||||||
recipientTooltip.setShowDelay(new Duration(TOOLTIP_SHOW_DELAY));
|
recipientTooltip.setShowDelay(new Duration(TOOLTIP_SHOW_DELAY));
|
||||||
|
|
@ -757,13 +719,9 @@ public class TransactionDiagram extends GridPane {
|
||||||
paymentBox.getChildren().addAll(region, amountLabel);
|
paymentBox.getChildren().addAll(region, amountLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(payment instanceof SilentPayment silentPayment) {
|
Wallet bip47Wallet = toWallet != null && toWallet.isBip47() ? toWallet : (toBip47Wallet != null && toBip47Wallet.isBip47() ? toBip47Wallet : null);
|
||||||
outputNodes.add(new OutputNode(paymentBox, silentPayment.isAddressComputed() ? silentPayment.getAddress() : null, payment.getAmount(), null, silentPayment.getSilentPaymentAddress()));
|
PaymentCode paymentCode = bip47Wallet == null ? null : bip47Wallet.getKeystores().getFirst().getExternalPaymentCode();
|
||||||
} else {
|
outputNodes.add(new OutputNode(paymentBox, payment.getAddress(), payment.getAmount(), paymentCode));
|
||||||
Wallet bip47Wallet = toWallet != null && toWallet.isBip47() ? toWallet : (toBip47Wallet != null && toBip47Wallet.isBip47() ? toBip47Wallet : null);
|
|
||||||
PaymentCode paymentCode = bip47Wallet == null ? null : bip47Wallet.getKeystores().getFirst().getExternalPaymentCode();
|
|
||||||
outputNodes.add(new OutputNode(paymentBox, payment.getAddress(), payment.getAmount(), paymentCode, null));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Integer> seenIndexes = new HashSet<>();
|
Set<Integer> seenIndexes = new HashSet<>();
|
||||||
|
|
@ -827,7 +785,7 @@ public class TransactionDiagram extends GridPane {
|
||||||
outputsBox.getChildren().add(outputNode.outputLabel);
|
outputsBox.getChildren().add(outputNode.outputLabel);
|
||||||
outputsBox.getChildren().add(createSpacer());
|
outputsBox.getChildren().add(createSpacer());
|
||||||
|
|
||||||
ContextMenu contextMenu = new LabelContextMenu(outputNode.address, outputNode.amount, outputNode.paymentCode, outputNode.silentPaymentAddress);
|
ContextMenu contextMenu = new LabelContextMenu(outputNode.address, outputNode.amount, outputNode.paymentCode);
|
||||||
if(!outputNode.outputLabel.getChildren().isEmpty() && outputNode.outputLabel.getChildren().get(0) instanceof Label outputLabelControl) {
|
if(!outputNode.outputLabel.getChildren().isEmpty() && outputNode.outputLabel.getChildren().get(0) instanceof Label outputLabelControl) {
|
||||||
outputLabelControl.setContextMenu(contextMenu);
|
outputLabelControl.setContextMenu(contextMenu);
|
||||||
}
|
}
|
||||||
|
|
@ -1002,11 +960,8 @@ public class TransactionDiagram extends GridPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getOutputIndex(Address address, long amount, Collection<Integer> seenIndexes) {
|
private int getOutputIndex(Address address, long amount, Collection<Integer> seenIndexes) {
|
||||||
List<TransactionOutput> addressOutputs = walletTx.getOutputs().stream().filter(output -> !(output instanceof WalletTransaction.NonAddressOutput))
|
List<TransactionOutput> addressOutputs = walletTx.getTransaction().getOutputs().stream().filter(txOutput -> txOutput.getScript().getToAddress() != null).collect(Collectors.toList());
|
||||||
.map(WalletTransaction.Output::getTransactionOutput).collect(Collectors.toList());
|
TransactionOutput output = addressOutputs.stream().filter(txOutput -> address.equals(txOutput.getScript().getToAddress()) && txOutput.getValue() == amount && !seenIndexes.contains(txOutput.getIndex())).findFirst().orElseThrow();
|
||||||
TransactionOutput output = addressOutputs.stream()
|
|
||||||
.filter(txOutput -> address.equals(txOutput.getScript().getToAddress()) && txOutput.getValue() == amount && !seenIndexes.contains(txOutput.getIndex()))
|
|
||||||
.findFirst().orElseThrow();
|
|
||||||
return addressOutputs.indexOf(output);
|
return addressOutputs.indexOf(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1156,7 +1111,7 @@ public class TransactionDiagram extends GridPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return additionalPayments.stream().map(Payment::toString).collect(Collectors.joining("\n"));
|
return additionalPayments.stream().map(payment -> payment.getAddress().toString()).collect(Collectors.joining("\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1165,27 +1120,25 @@ public class TransactionDiagram extends GridPane {
|
||||||
public Address address;
|
public Address address;
|
||||||
public long amount;
|
public long amount;
|
||||||
public PaymentCode paymentCode;
|
public PaymentCode paymentCode;
|
||||||
public SilentPaymentAddress silentPaymentAddress;
|
|
||||||
|
|
||||||
public OutputNode(Pane outputLabel, Address address, long amount) {
|
public OutputNode(Pane outputLabel, Address address, long amount) {
|
||||||
this(outputLabel, address, amount, null, null);
|
this(outputLabel, address, amount, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputNode(Pane outputLabel, Address address, long amount, PaymentCode paymentCode, SilentPaymentAddress silentPaymentAddress) {
|
public OutputNode(Pane outputLabel, Address address, long amount, PaymentCode paymentCode) {
|
||||||
this.outputLabel = outputLabel;
|
this.outputLabel = outputLabel;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.paymentCode = paymentCode;
|
this.paymentCode = paymentCode;
|
||||||
this.silentPaymentAddress = silentPaymentAddress;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LabelContextMenu extends ContextMenu {
|
private class LabelContextMenu extends ContextMenu {
|
||||||
public LabelContextMenu(Address address, long value) {
|
public LabelContextMenu(Address address, long value) {
|
||||||
this(address, value, null, null);
|
this(address, value, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LabelContextMenu(Address address, long value, PaymentCode paymentCode, SilentPaymentAddress silentPaymentAddress) {
|
public LabelContextMenu(Address address, long value, PaymentCode paymentCode) {
|
||||||
if(address != null) {
|
if(address != null) {
|
||||||
MenuItem copyAddress = new MenuItem("Copy Address");
|
MenuItem copyAddress = new MenuItem("Copy Address");
|
||||||
copyAddress.setOnAction(event -> {
|
copyAddress.setOnAction(event -> {
|
||||||
|
|
@ -1233,17 +1186,6 @@ public class TransactionDiagram extends GridPane {
|
||||||
});
|
});
|
||||||
getItems().add(copyPaymentCode);
|
getItems().add(copyPaymentCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(silentPaymentAddress != null) {
|
|
||||||
MenuItem copySilentPaymentAddress = new MenuItem("Copy Silent Payment Address");
|
|
||||||
copySilentPaymentAddress.setOnAction(AE -> {
|
|
||||||
hide();
|
|
||||||
ClipboardContent content = new ClipboardContent();
|
|
||||||
content.putString(silentPaymentAddress.toString());
|
|
||||||
Clipboard.getSystemClipboard().setContent(content);
|
|
||||||
});
|
|
||||||
getItems().add(copySilentPaymentAddress);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,20 +90,20 @@ public class TransactionDiagramLabel extends HBox {
|
||||||
outputLabels.add(mixOutputLabel);
|
outputLabels.add(mixOutputLabel);
|
||||||
}
|
}
|
||||||
} else if(walletTx.getPayments().size() >= 5 && walletTx.getPayments().stream().mapToLong(Payment::getAmount).distinct().count() <= 1 && walletTx.getWallet() != null
|
} else if(walletTx.getPayments().size() >= 5 && walletTx.getPayments().stream().mapToLong(Payment::getAmount).distinct().count() <= 1 && walletTx.getWallet() != null
|
||||||
&& walletTx.getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX && !walletTx.getWalletNodePayments().isEmpty()) {
|
&& walletTx.getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX && walletTx.getPayments().stream().anyMatch(walletTx::isConsolidationSend)) {
|
||||||
OutputLabel remixOutputLabel = getRemixOutputLabel(transactionDiagram, walletTx.getPayments());
|
OutputLabel remixOutputLabel = getRemixOutputLabel(transactionDiagram, walletTx.getPayments());
|
||||||
if(remixOutputLabel != null) {
|
if(remixOutputLabel != null) {
|
||||||
outputLabels.add(remixOutputLabel);
|
outputLabels.add(remixOutputLabel);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<Payment> payments = walletTx.getExternalPayments().stream().filter(payment -> payment.getType() == Payment.Type.DEFAULT).collect(Collectors.toList());
|
List<Payment> payments = walletTx.getPayments().stream().filter(payment -> payment.getType() == Payment.Type.DEFAULT && !walletTx.isConsolidationSend(payment)).collect(Collectors.toList());
|
||||||
List<OutputLabel> paymentLabels = payments.stream().map(payment -> getOutputLabel(transactionDiagram, payment)).collect(Collectors.toList());
|
List<OutputLabel> paymentLabels = payments.stream().map(payment -> getOutputLabel(transactionDiagram, payment)).collect(Collectors.toList());
|
||||||
if(walletTx.getSelectedUtxos().values().stream().allMatch(Objects::isNull)) {
|
if(walletTx.getSelectedUtxos().values().stream().allMatch(Objects::isNull)) {
|
||||||
paymentLabels.sort(Comparator.comparingInt(paymentLabel -> (paymentLabel.text.startsWith("Receive") ? 0 : 1)));
|
paymentLabels.sort(Comparator.comparingInt(paymentLabel -> (paymentLabel.text.startsWith("Receive") ? 0 : 1)));
|
||||||
}
|
}
|
||||||
outputLabels.addAll(paymentLabels);
|
outputLabels.addAll(paymentLabels);
|
||||||
|
|
||||||
List<Payment> consolidations = walletTx.getWalletNodePayments().stream().filter(payment -> payment.getType() == Payment.Type.DEFAULT).collect(Collectors.toList());
|
List<Payment> consolidations = walletTx.getPayments().stream().filter(payment -> payment.getType() == Payment.Type.DEFAULT && walletTx.isConsolidationSend(payment)).collect(Collectors.toList());
|
||||||
outputLabels.addAll(consolidations.stream().map(consolidation -> getOutputLabel(transactionDiagram, consolidation)).collect(Collectors.toList()));
|
outputLabels.addAll(consolidations.stream().map(consolidation -> getOutputLabel(transactionDiagram, consolidation)).collect(Collectors.toList()));
|
||||||
|
|
||||||
List<Payment> mixes = walletTx.getPayments().stream().filter(payment -> payment.getType() == Payment.Type.MIX || payment.getType() == Payment.Type.FAKE_MIX).collect(Collectors.toList());
|
List<Payment> mixes = walletTx.getPayments().stream().filter(payment -> payment.getType() == Payment.Type.MIX || payment.getType() == Payment.Type.FAKE_MIX).collect(Collectors.toList());
|
||||||
|
|
@ -203,10 +203,10 @@ public class TransactionDiagramLabel extends HBox {
|
||||||
private OutputLabel getOutputLabel(TransactionDiagram transactionDiagram, Payment payment) {
|
private OutputLabel getOutputLabel(TransactionDiagram transactionDiagram, Payment payment) {
|
||||||
WalletTransaction walletTx = transactionDiagram.getWalletTransaction();
|
WalletTransaction walletTx = transactionDiagram.getWalletTransaction();
|
||||||
Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
|
Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
|
||||||
WalletNode toNode = payment instanceof WalletNodePayment walletNodePayment ? walletNodePayment.getWalletNode() : null;
|
WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null;
|
||||||
|
|
||||||
Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), payment);
|
Glyph glyph = GlyphUtils.getOutputGlyph(transactionDiagram.getWalletTransaction(), payment);
|
||||||
String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment;
|
String text = (toWallet == null ? (toNode != null ? "Consolidate " : "Pay ") : "Receive ") + transactionDiagram.getSatsValue(payment.getAmount()) + " sats to " + payment.getAddress().toString();
|
||||||
|
|
||||||
return getOutputLabel(glyph, text);
|
return getOutputLabel(glyph, text);
|
||||||
}
|
}
|
||||||
|
|
@ -240,7 +240,7 @@ public class TransactionDiagramLabel extends HBox {
|
||||||
icon.setGraphic(glyph);
|
icon.setGraphic(glyph);
|
||||||
|
|
||||||
CopyableLabel label = new CopyableLabel();
|
CopyableLabel label = new CopyableLabel();
|
||||||
label.setFont(Font.font("Fragment Mono Italic", 13));
|
label.setFont(Font.font("Roboto Mono Italic", 13));
|
||||||
label.setText(text);
|
label.setText(text);
|
||||||
|
|
||||||
HBox output = new HBox(5);
|
HBox output = new HBox(5);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ public enum WebcamPixelFormat {
|
||||||
PIX_FMT_RGB24("RGB3", true),
|
PIX_FMT_RGB24("RGB3", true),
|
||||||
PIX_FMT_YUYV("YUYV", true),
|
PIX_FMT_YUYV("YUYV", true),
|
||||||
PIX_FMT_NV12("NV12", true),
|
PIX_FMT_NV12("NV12", true),
|
||||||
PIX_FMT_YU12("YU12", true),
|
|
||||||
PIX_FMT_MJPG("MJPG", true);
|
PIX_FMT_MJPG("MJPG", true);
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import com.google.zxing.qrcode.QRCodeReader;
|
||||||
import com.sparrowwallet.bokmakierie.Bokmakierie;
|
import com.sparrowwallet.bokmakierie.Bokmakierie;
|
||||||
import com.sparrowwallet.drongo.OsType;
|
import com.sparrowwallet.drongo.OsType;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.io.ZBar;
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
|
@ -16,6 +15,7 @@ import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.embed.swing.SwingFXUtils;
|
import javafx.embed.swing.SwingFXUtils;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
import net.sourceforge.zbar.ZBar;
|
||||||
import org.openpnp.capture.*;
|
import org.openpnp.capture.*;
|
||||||
import org.openpnp.capture.library.OpenpnpCaptureLibrary;
|
import org.openpnp.capture.library.OpenpnpCaptureLibrary;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
@ -137,20 +137,14 @@ public class WebcamService extends ScheduledService<Image> {
|
||||||
|
|
||||||
if(device != null) {
|
if(device != null) {
|
||||||
for(CaptureDevice webcam : availableDevices) {
|
for(CaptureDevice webcam : availableDevices) {
|
||||||
if(webcam.equals(device)) {
|
if(webcam.getName().equals(device.getName())) {
|
||||||
selectedDevice = webcam;
|
selectedDevice = webcam;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(Config.get().getWebcamDevice() != null) {
|
} else if(Config.get().getWebcamDevice() != null) {
|
||||||
for(CaptureDevice webcam : availableDevices) {
|
for(CaptureDevice webcam : availableDevices) {
|
||||||
if(webcam.getUniqueId().equals(Config.get().getWebcamDeviceId())) {
|
|
||||||
selectedDevice = webcam;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(webcam.getName().equals(Config.get().getWebcamDevice())) {
|
if(webcam.getName().equals(Config.get().getWebcamDevice())) {
|
||||||
selectedDevice = webcam;
|
selectedDevice = webcam;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -319,6 +313,9 @@ public class WebcamService extends ScheduledService<Image> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result readQR(BufferedImage bufferedImage) {
|
private Result readQR(BufferedImage bufferedImage) {
|
||||||
|
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
|
||||||
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
com.sparrowwallet.bokmakierie.Result result = bokmakierie.scan(bufferedImage);
|
com.sparrowwallet.bokmakierie.Result result = bokmakierie.scan(bufferedImage);
|
||||||
if(result != null) {
|
if(result != null) {
|
||||||
|
|
@ -336,8 +333,6 @@ public class WebcamService extends ScheduledService<Image> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
|
|
||||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
|
||||||
return qrReader.decode(bitmap, Map.of(DecodeHintType.TRY_HARDER, Boolean.TRUE));
|
return qrReader.decode(bitmap, Map.of(DecodeHintType.TRY_HARDER, Boolean.TRUE));
|
||||||
} catch(ReaderException e) {
|
} catch(ReaderException e) {
|
||||||
// fall thru, it means there is no QR code in image
|
// fall thru, it means there is no QR code in image
|
||||||
|
|
@ -449,27 +444,10 @@ public class WebcamService extends ScheduledService<Image> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends Enum<T>> T getNearestEnum(T target, T[] values) {
|
public static <T extends Enum<T>> T getNearestEnum(T target, T[] values) {
|
||||||
if(values == null || values.length == 0) {
|
int ordinal = target.ordinal();
|
||||||
return null;
|
return Stream.concat(ordinal > 0 ? Stream.of(values[ordinal - 1]) : Stream.empty(), ordinal < values.length - 1 ? Stream.of(values[ordinal + 1]) : Stream.empty())
|
||||||
}
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
int targetOrdinal = target.ordinal();
|
|
||||||
if(values.length == 1) {
|
|
||||||
return values[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < values.length; i++) {
|
|
||||||
if(targetOrdinal < values[i].ordinal()) {
|
|
||||||
if(i == 0) {
|
|
||||||
return values[0];
|
|
||||||
}
|
|
||||||
int diffToPrev = Math.abs(targetOrdinal - values[i - 1].ordinal());
|
|
||||||
int diffToNext = Math.abs(targetOrdinal - values[i].ordinal());
|
|
||||||
return diffToPrev <= diffToNext ? values[i - 1] : values[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return values[values.length - 1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CroppedDimension {
|
private static class CroppedDimension {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.event;
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.protocol.BlockHeader;
|
import com.sparrowwallet.drongo.protocol.BlockHeader;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
|
||||||
import com.sparrowwallet.sparrow.net.MempoolRateSize;
|
import com.sparrowwallet.sparrow.net.MempoolRateSize;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -14,7 +13,6 @@ public class ConnectionEvent extends FeeRatesUpdatedEvent {
|
||||||
private final int blockHeight;
|
private final int blockHeight;
|
||||||
private final BlockHeader blockHeader;
|
private final BlockHeader blockHeader;
|
||||||
private final Double minimumRelayFeeRate;
|
private final Double minimumRelayFeeRate;
|
||||||
private final Double previousMinimumRelayFeeRate;
|
|
||||||
|
|
||||||
public ConnectionEvent(List<String> serverVersion, String serverBanner, int blockHeight, BlockHeader blockHeader, Map<Integer, Double> targetBlockFeeRates, Set<MempoolRateSize> mempoolRateSizes, Double minimumRelayFeeRate) {
|
public ConnectionEvent(List<String> serverVersion, String serverBanner, int blockHeight, BlockHeader blockHeader, Map<Integer, Double> targetBlockFeeRates, Set<MempoolRateSize> mempoolRateSizes, Double minimumRelayFeeRate) {
|
||||||
super(targetBlockFeeRates, mempoolRateSizes);
|
super(targetBlockFeeRates, mempoolRateSizes);
|
||||||
|
|
@ -23,7 +21,6 @@ public class ConnectionEvent extends FeeRatesUpdatedEvent {
|
||||||
this.blockHeight = blockHeight;
|
this.blockHeight = blockHeight;
|
||||||
this.blockHeader = blockHeader;
|
this.blockHeader = blockHeader;
|
||||||
this.minimumRelayFeeRate = minimumRelayFeeRate;
|
this.minimumRelayFeeRate = minimumRelayFeeRate;
|
||||||
this.previousMinimumRelayFeeRate = AppServices.getMinimumRelayFeeRate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getServerVersion() {
|
public List<String> getServerVersion() {
|
||||||
|
|
@ -45,8 +42,4 @@ public class ConnectionEvent extends FeeRatesUpdatedEvent {
|
||||||
public Double getMinimumRelayFeeRate() {
|
public Double getMinimumRelayFeeRate() {
|
||||||
return minimumRelayFeeRate;
|
return minimumRelayFeeRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Double getPreviousMinimumRelayFeeRate() {
|
|
||||||
return previousMinimumRelayFeeRate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,12 @@ public class SpendUtxoEvent {
|
||||||
private final boolean requireAllUtxos;
|
private final boolean requireAllUtxos;
|
||||||
private final BlockTransaction replacedTransaction;
|
private final BlockTransaction replacedTransaction;
|
||||||
private final PaymentCode paymentCode;
|
private final PaymentCode paymentCode;
|
||||||
private final boolean allowPaymentChanges;
|
|
||||||
|
|
||||||
public SpendUtxoEvent(Wallet wallet, List<BlockTransactionHashIndex> utxos) {
|
public SpendUtxoEvent(Wallet wallet, List<BlockTransactionHashIndex> utxos) {
|
||||||
this(wallet, utxos, null, null, null, false, null, true);
|
this(wallet, utxos, null, null, null, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpendUtxoEvent(Wallet wallet, List<BlockTransactionHashIndex> utxos, List<Payment> payments, List<byte[]> opReturns, Long fee, boolean requireAllUtxos, BlockTransaction replacedTransaction, boolean allowPaymentChanges) {
|
public SpendUtxoEvent(Wallet wallet, List<BlockTransactionHashIndex> utxos, List<Payment> payments, List<byte[]> opReturns, Long fee, boolean requireAllUtxos, BlockTransaction replacedTransaction) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.utxos = utxos;
|
this.utxos = utxos;
|
||||||
this.payments = payments;
|
this.payments = payments;
|
||||||
|
|
@ -32,7 +31,6 @@ public class SpendUtxoEvent {
|
||||||
this.requireAllUtxos = requireAllUtxos;
|
this.requireAllUtxos = requireAllUtxos;
|
||||||
this.replacedTransaction = replacedTransaction;
|
this.replacedTransaction = replacedTransaction;
|
||||||
this.paymentCode = null;
|
this.paymentCode = null;
|
||||||
this.allowPaymentChanges = allowPaymentChanges;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpendUtxoEvent(Wallet wallet, List<Payment> payments, List<byte[]> opReturns, PaymentCode paymentCode) {
|
public SpendUtxoEvent(Wallet wallet, List<Payment> payments, List<byte[]> opReturns, PaymentCode paymentCode) {
|
||||||
|
|
@ -44,7 +42,6 @@ public class SpendUtxoEvent {
|
||||||
this.requireAllUtxos = false;
|
this.requireAllUtxos = false;
|
||||||
this.replacedTransaction = null;
|
this.replacedTransaction = null;
|
||||||
this.paymentCode = paymentCode;
|
this.paymentCode = paymentCode;
|
||||||
this.allowPaymentChanges = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wallet getWallet() {
|
public Wallet getWallet() {
|
||||||
|
|
@ -78,8 +75,4 @@ public class SpendUtxoEvent {
|
||||||
public PaymentCode getPaymentCode() {
|
public PaymentCode getPaymentCode() {
|
||||||
return paymentCode;
|
return paymentCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean allowPaymentChanges() {
|
|
||||||
return allowPaymentChanges;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package com.sparrowwallet.sparrow.event;
|
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
|
||||||
|
|
||||||
public class TransactionOutputsChangedEvent extends TransactionChangedEvent {
|
|
||||||
public TransactionOutputsChangedEvent(Transaction transaction) {
|
|
||||||
super(transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.glyphfont;
|
package com.sparrowwallet.sparrow.glyphfont;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.wallet.Payment;
|
import com.sparrowwallet.drongo.wallet.Payment;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletNodePayment;
|
|
||||||
import com.sparrowwallet.drongo.wallet.WalletTransaction;
|
import com.sparrowwallet.drongo.wallet.WalletTransaction;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.control.TransactionDiagram;
|
import com.sparrowwallet.sparrow.control.TransactionDiagram;
|
||||||
|
|
@ -16,7 +15,7 @@ public class GlyphUtils {
|
||||||
return getFakeMixGlyph();
|
return getFakeMixGlyph();
|
||||||
} else if(payment.getType().equals(Payment.Type.ANCHOR)) {
|
} else if(payment.getType().equals(Payment.Type.ANCHOR)) {
|
||||||
return getAnchorGlyph();
|
return getAnchorGlyph();
|
||||||
} else if(payment instanceof WalletNodePayment) {
|
} else if(walletTx.isConsolidationSend(payment)) {
|
||||||
return getConsolidationGlyph();
|
return getConsolidationGlyph();
|
||||||
} else if(walletTx.isPremixSend(payment)) {
|
} else if(walletTx.isPremixSend(payment)) {
|
||||||
return getPremixGlyph();
|
return getPremixGlyph();
|
||||||
|
|
@ -214,13 +213,6 @@ public class GlyphUtils {
|
||||||
return busyGlyph;
|
return busyGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Glyph getUpArrowGlyph() {
|
|
||||||
Glyph upGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ARROW_UP);
|
|
||||||
upGlyph.getStyleClass().add("arrow-up");
|
|
||||||
upGlyph.setFontSize(12);
|
|
||||||
return upGlyph;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Glyph getDownArrowGlyph() {
|
public static Glyph getDownArrowGlyph() {
|
||||||
Glyph downGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ARROW_DOWN);
|
Glyph downGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ARROW_DOWN);
|
||||||
downGlyph.getStyleClass().add("arrow-down");
|
downGlyph.getStyleClass().add("arrow-down");
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ public class Bip129 implements KeystoreFileExport, KeystoreFileImport, WalletExp
|
||||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||||
try {
|
try {
|
||||||
String record = "BSMS 1.0\n" +
|
String record = "BSMS 1.0\n" +
|
||||||
OutputDescriptor.getOutputDescriptor(wallet, KeyPurpose.DEFAULT_PURPOSES, null) +
|
OutputDescriptor.getOutputDescriptor(wallet) +
|
||||||
"\n/0/*,/1/*\n" +
|
"\n/0/*,/1/*\n" +
|
||||||
wallet.getNode(KeyPurpose.RECEIVE).getChildren().iterator().next().getAddress();
|
wallet.getNode(KeyPurpose.RECEIVE).getChildren().iterator().next().getAddress();
|
||||||
outputStream.write(record.getBytes(StandardCharsets.UTF_8));
|
outputStream.write(record.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import java.io.InputStream;
|
||||||
public class BlueWalletMultisig extends ColdcardMultisig {
|
public class BlueWalletMultisig extends ColdcardMultisig {
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "BlueWallet Vault Multisig";
|
return "Blue Wallet Vault Multisig";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -21,7 +21,7 @@ public class BlueWalletMultisig extends ColdcardMultisig {
|
||||||
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
|
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
|
||||||
Wallet wallet = super.importWallet(inputStream, password);
|
Wallet wallet = super.importWallet(inputStream, password);
|
||||||
for(Keystore keystore : wallet.getKeystores()) {
|
for(Keystore keystore : wallet.getKeystores()) {
|
||||||
keystore.setLabel(keystore.getLabel().replace("Coldcard", "BlueWallet"));
|
keystore.setLabel(keystore.getLabel().replace("Coldcard", "Blue Wallet"));
|
||||||
keystore.setWalletModel(WalletModel.BLUE_WALLET);
|
keystore.setWalletModel(WalletModel.BLUE_WALLET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,12 +30,12 @@ public class BlueWalletMultisig extends ColdcardMultisig {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWalletImportDescription() {
|
public String getWalletImportDescription() {
|
||||||
return "Import file or QR created by using the Wallet > Export Coordination Setup feature on your BlueWallet Vault wallet.";
|
return "Import file or QR created by using the Wallet > Export Coordination Setup feature on your Blue Wallet Vault wallet.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWalletExportDescription() {
|
public String getWalletExportDescription() {
|
||||||
return "Export file that can be read by BlueWallet using the Add Wallet > Vault > Import wallet feature.";
|
return "Export file that can be read by Blue Wallet using the Add Wallet > Vault > Import wallet feature.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import com.sparrowwallet.drongo.BitcoinUnit;
|
import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
|
||||||
import com.sparrowwallet.sparrow.UnitFormat;
|
import com.sparrowwallet.sparrow.UnitFormat;
|
||||||
import com.sparrowwallet.sparrow.Mode;
|
import com.sparrowwallet.sparrow.Mode;
|
||||||
import com.sparrowwallet.sparrow.Theme;
|
import com.sparrowwallet.sparrow.Theme;
|
||||||
|
|
@ -54,7 +53,6 @@ public class Config {
|
||||||
private boolean signBsmsExports = false;
|
private boolean signBsmsExports = false;
|
||||||
private boolean preventSleep = false;
|
private boolean preventSleep = false;
|
||||||
private Boolean connectToBroadcast;
|
private Boolean connectToBroadcast;
|
||||||
private Boolean connectToResolve;
|
|
||||||
private Boolean suggestSendToMany;
|
private Boolean suggestSendToMany;
|
||||||
private List<File> recentWalletFiles;
|
private List<File> recentWalletFiles;
|
||||||
private Integer keyDerivationPeriod;
|
private Integer keyDerivationPeriod;
|
||||||
|
|
@ -65,7 +63,6 @@ public class Config {
|
||||||
private boolean mirrorCapture = true;
|
private boolean mirrorCapture = true;
|
||||||
private boolean useZbar = true;
|
private boolean useZbar = true;
|
||||||
private String webcamDevice;
|
private String webcamDevice;
|
||||||
private String webcamDeviceId;
|
|
||||||
private ServerType serverType;
|
private ServerType serverType;
|
||||||
private Server publicElectrumServer;
|
private Server publicElectrumServer;
|
||||||
private Server coreServer;
|
private Server coreServer;
|
||||||
|
|
@ -85,7 +82,6 @@ public class Config {
|
||||||
private int maxPageSize = DEFAULT_PAGE_SIZE;
|
private int maxPageSize = DEFAULT_PAGE_SIZE;
|
||||||
private boolean usePayNym;
|
private boolean usePayNym;
|
||||||
private boolean mempoolFullRbf;
|
private boolean mempoolFullRbf;
|
||||||
private double minRelayFeeRate = Transaction.DEFAULT_MIN_RELAY_FEE;
|
|
||||||
private Double appWidth;
|
private Double appWidth;
|
||||||
private Double appHeight;
|
private Double appHeight;
|
||||||
|
|
||||||
|
|
@ -366,15 +362,6 @@ public class Config {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getConnectToResolve() {
|
|
||||||
return connectToResolve;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConnectToResolve(Boolean connectToResolve) {
|
|
||||||
this.connectToResolve = connectToResolve;
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getSuggestSendToMany() {
|
public Boolean getSuggestSendToMany() {
|
||||||
return suggestSendToMany;
|
return suggestSendToMany;
|
||||||
}
|
}
|
||||||
|
|
@ -450,15 +437,6 @@ public class Config {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getWebcamDeviceId() {
|
|
||||||
return webcamDeviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWebcamDeviceId(String webcamDeviceId) {
|
|
||||||
this.webcamDeviceId = webcamDeviceId;
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerType getServerType() {
|
public ServerType getServerType() {
|
||||||
return serverType;
|
return serverType;
|
||||||
}
|
}
|
||||||
|
|
@ -720,14 +698,6 @@ public class Config {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getMinRelayFeeRate() {
|
|
||||||
return minRelayFeeRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMinRelayFeeRate(double minRelayFeeRate) {
|
|
||||||
this.minRelayFeeRate = minRelayFeeRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getAppWidth() {
|
public Double getAppWidth() {
|
||||||
return appWidth;
|
return appWidth;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -684,7 +684,7 @@ public class Storage {
|
||||||
|
|
||||||
public static Executor getSingleThreadedExecutor() {
|
public static Executor getSingleThreadedExecutor() {
|
||||||
if(singleThreadedExecutor == null) {
|
if(singleThreadedExecutor == null) {
|
||||||
BasicThreadFactory factory = BasicThreadFactory.builder().namingPattern("LoadWalletService-single").daemon(true).priority(Thread.MIN_PRIORITY).build();
|
BasicThreadFactory factory = new BasicThreadFactory.Builder().namingPattern("LoadWalletService-single").daemon(true).priority(Thread.MIN_PRIORITY).build();
|
||||||
singleThreadedExecutor = Executors.newSingleThreadScheduledExecutor(factory);
|
singleThreadedExecutor = Executors.newSingleThreadScheduledExecutor(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ public class DbPersistence implements Persistence {
|
||||||
|
|
||||||
private synchronized void createUpdateExecutor(Wallet masterWallet) {
|
private synchronized void createUpdateExecutor(Wallet masterWallet) {
|
||||||
if(updateExecutor == null) {
|
if(updateExecutor == null) {
|
||||||
BasicThreadFactory factory = BasicThreadFactory.builder().namingPattern(masterWallet.getFullName() + "-dbupdater").daemon(true).priority(Thread.NORM_PRIORITY).build();
|
BasicThreadFactory factory = new BasicThreadFactory.Builder().namingPattern(masterWallet.getFullName() + "-dbupdater").daemon(true).priority(Thread.NORM_PRIORITY).build();
|
||||||
updateExecutor = Executors.newSingleThreadExecutor(factory);
|
updateExecutor = Executors.newSingleThreadExecutor(factory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ public enum ExchangeSource {
|
||||||
return historicalRates;
|
return historicalRates;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
COINGECKO("Coingecko", "Historical rates for the last 365 days") {
|
COINGECKO("Coingecko", "No historical rates") {
|
||||||
@Override
|
@Override
|
||||||
public List<Currency> getSupportedCurrencies() {
|
public List<Currency> getSupportedCurrencies() {
|
||||||
return getRates().rates.entrySet().stream().filter(rate -> "fiat".equals(rate.getValue().type) && isValidISO4217Code(rate.getKey().toUpperCase(Locale.ROOT)))
|
return getRates().rates.entrySet().stream().filter(rate -> "fiat".equals(rate.getValue().type) && isValidISO4217Code(rate.getKey().toUpperCase(Locale.ROOT)))
|
||||||
|
|
@ -167,11 +167,6 @@ public enum ExchangeSource {
|
||||||
long startDate = start.getTime() / 1000;
|
long startDate = start.getTime() / 1000;
|
||||||
long endDate = end.getTime() / 1000;
|
long endDate = end.getTime() / 1000;
|
||||||
|
|
||||||
Calendar cal = Calendar.getInstance();
|
|
||||||
cal.add(Calendar.YEAR, -1);
|
|
||||||
startDate = Math.max(cal.getTimeInMillis() / 1000, startDate);
|
|
||||||
endDate = Math.max(cal.getTimeInMillis() / 1000, endDate);
|
|
||||||
|
|
||||||
String url = "https://api.coingecko.com/api/v3/coins/bitcoin/market_chart/range?vs_currency=" + currency.getCurrencyCode() + "&from=" + startDate + "&to=" + endDate;
|
String url = "https://api.coingecko.com/api/v3/coins/bitcoin/market_chart/range?vs_currency=" + currency.getCurrencyCode() + "&from=" + startDate + "&to=" + endDate;
|
||||||
|
|
||||||
if(log.isInfoEnabled()) {
|
if(log.isInfoEnabled()) {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
package com.sparrowwallet.sparrow.net;
|
package com.sparrowwallet.sparrow.net;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.OsType;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.FileSystemNotFoundException;
|
import java.nio.file.FileSystemNotFoundException;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.ProviderNotFoundException;
|
import java.nio.file.ProviderNotFoundException;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
|
||||||
import java.nio.file.attribute.PosixFilePermissions;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple library class which helps with loading dynamic libraries stored in the
|
* A simple library class which helps with loading dynamic libraries stored in the
|
||||||
|
|
@ -117,33 +111,9 @@ public class NativeUtils {
|
||||||
String tempDir = System.getProperty("java.io.tmpdir");
|
String tempDir = System.getProperty("java.io.tmpdir");
|
||||||
File generatedDir = new File(tempDir, prefix + System.nanoTime());
|
File generatedDir = new File(tempDir, prefix + System.nanoTime());
|
||||||
|
|
||||||
if(!createOwnerOnlyDirectory(generatedDir)) {
|
if (!generatedDir.mkdir())
|
||||||
throw new IOException("Failed to create temp directory " + generatedDir.getName());
|
throw new IOException("Failed to create temp directory " + generatedDir.getName());
|
||||||
}
|
|
||||||
|
|
||||||
return generatedDir;
|
return generatedDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean createOwnerOnlyDirectory(File directory) throws IOException {
|
|
||||||
try {
|
|
||||||
if(OsType.getCurrent() == OsType.WINDOWS) {
|
|
||||||
Files.createDirectories(directory.toPath());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Files.createDirectories(directory.toPath(), PosixFilePermissions.asFileAttribute(getDirectoryOwnerOnlyPosixFilePermissions()));
|
|
||||||
return true;
|
|
||||||
} catch(UnsupportedOperationException e) {
|
|
||||||
return directory.mkdirs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<PosixFilePermission> getDirectoryOwnerOnlyPosixFilePermissions() {
|
|
||||||
Set<PosixFilePermission> ownerOnly = EnumSet.noneOf(PosixFilePermission.class);
|
|
||||||
ownerOnly.add(PosixFilePermission.OWNER_READ);
|
|
||||||
ownerOnly.add(PosixFilePermission.OWNER_WRITE);
|
|
||||||
ownerOnly.add(PosixFilePermission.OWNER_EXECUTE);
|
|
||||||
|
|
||||||
return ownerOnly;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import java.security.cert.Certificate;
|
||||||
|
|
||||||
public class TcpOverTlsTransport extends TcpTransport {
|
public class TcpOverTlsTransport extends TcpTransport {
|
||||||
private static final Logger log = LoggerFactory.getLogger(TcpOverTlsTransport.class);
|
private static final Logger log = LoggerFactory.getLogger(TcpOverTlsTransport.class);
|
||||||
public static final int PAD_TO_MULTIPLE_OF_BYTES = 96;
|
|
||||||
|
|
||||||
protected final SSLSocketFactory sslSocketFactory;
|
protected final SSLSocketFactory sslSocketFactory;
|
||||||
|
|
||||||
|
|
@ -42,24 +41,6 @@ public class TcpOverTlsTransport extends TcpTransport {
|
||||||
sslSocketFactory = sslContext.getSocketFactory();
|
sslSocketFactory = sslContext.getSocketFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void writeRequest(String request) throws IOException {
|
|
||||||
int currentLength = request.length();
|
|
||||||
int targetLength;
|
|
||||||
if(currentLength % PAD_TO_MULTIPLE_OF_BYTES == 0) {
|
|
||||||
targetLength = currentLength;
|
|
||||||
} else {
|
|
||||||
targetLength = ((currentLength / PAD_TO_MULTIPLE_OF_BYTES) + 1) * PAD_TO_MULTIPLE_OF_BYTES;
|
|
||||||
}
|
|
||||||
|
|
||||||
int paddingNeeded = targetLength - currentLength;
|
|
||||||
if(paddingNeeded > 0) {
|
|
||||||
super.writeRequest(request + " ".repeat(paddingNeeded));
|
|
||||||
} else {
|
|
||||||
super.writeRequest(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TrustManager[] getTrustManagers(File crtFile) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
|
private TrustManager[] getTrustManagers(File crtFile) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
|
||||||
if(crtFile == null) {
|
if(crtFile == null) {
|
||||||
return new TrustManager[] {
|
return new TrustManager[] {
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public class TcpTransport implements CloseableTransport, TimeoutCounter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeRequest(String request) throws IOException {
|
private void writeRequest(String request) throws IOException {
|
||||||
if(log.isTraceEnabled()) {
|
if(log.isTraceEnabled()) {
|
||||||
log.trace("Sending to electrum server at " + server + ": " + request);
|
log.trace("Sending to electrum server at " + server + ": " + request);
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +106,7 @@ public class TcpTransport implements CloseableTransport, TimeoutCounter {
|
||||||
throw new IllegalStateException("Socket connection has not been established.");
|
throw new IllegalStateException("Socket connection has not been established.");
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)));
|
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
|
||||||
out.println(request);
|
out.println(request);
|
||||||
out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,9 +149,6 @@ public class BitcoindClient {
|
||||||
List<String> loadedWallets;
|
List<String> loadedWallets;
|
||||||
try {
|
try {
|
||||||
loadedWallets = getBitcoindService().listWallets();
|
loadedWallets = getBitcoindService().listWallets();
|
||||||
if(loadedWallets == null) {
|
|
||||||
throw new BitcoinRPCException("Wallet support must be enabled in Bitcoin Core");
|
|
||||||
}
|
|
||||||
legacyWalletExists = loadedWallets.contains(Bwt.DEFAULT_CORE_WALLET);
|
legacyWalletExists = loadedWallets.contains(Bwt.DEFAULT_CORE_WALLET);
|
||||||
} catch(JsonRpcException e) {
|
} catch(JsonRpcException e) {
|
||||||
if(e.getErrorMessage().getCode() == RPC_METHOD_NOT_FOUND) {
|
if(e.getErrorMessage().getCode() == RPC_METHOD_NOT_FOUND) {
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,6 @@ public class Payjoin {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.psbt = psbt;
|
this.psbt = psbt;
|
||||||
|
|
||||||
if(payjoinURI.getAddress() == null) {
|
|
||||||
throw new IllegalArgumentException("Payjoin URI must have an address");
|
|
||||||
}
|
|
||||||
|
|
||||||
for(PSBTInput psbtInput : psbt.getPsbtInputs()) {
|
for(PSBTInput psbtInput : psbt.getPsbtInputs()) {
|
||||||
if(psbtInput.getUtxo() == null) {
|
if(psbtInput.getUtxo() == null) {
|
||||||
throw new IllegalArgumentException("Original PSBT for payjoin transaction must have non_witness_utxo or witness_utxo fields for all inputs");
|
throw new IllegalArgumentException("Original PSBT for payjoin transaction must have non_witness_utxo or witness_utxo fields for all inputs");
|
||||||
|
|
@ -108,9 +104,6 @@ public class Payjoin {
|
||||||
} catch(PSBTParseException e) {
|
} catch(PSBTParseException e) {
|
||||||
log.error("Error parsing received PSBT", e);
|
log.error("Error parsing received PSBT", e);
|
||||||
throw new PayjoinReceiverException("Payjoin receiver returned invalid PSBT", e);
|
throw new PayjoinReceiverException("Payjoin receiver returned invalid PSBT", e);
|
||||||
} catch(PayjoinReceiverException e) {
|
|
||||||
log.error("Payjoin receiver error", e);
|
|
||||||
throw e;
|
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
log.error("Payjoin error", e);
|
log.error("Payjoin error", e);
|
||||||
throw new PayjoinReceiverException("Payjoin error", e);
|
throw new PayjoinReceiverException("Payjoin error", e);
|
||||||
|
|
|
||||||
|
|
@ -616,7 +616,6 @@ public class PayNymController {
|
||||||
List<byte[]> opReturns = List.of(blindedPaymentCode);
|
List<byte[]> opReturns = List.of(blindedPaymentCode);
|
||||||
Double feeRate = AppServices.getDefaultFeeRate();
|
Double feeRate = AppServices.getDefaultFeeRate();
|
||||||
Double minimumFeeRate = AppServices.getMinimumFeeRate();
|
Double minimumFeeRate = AppServices.getMinimumFeeRate();
|
||||||
Double minRelayFeeRate = AppServices.getMinimumRelayFeeRate();
|
|
||||||
boolean groupByAddress = Config.get().isGroupByAddress();
|
boolean groupByAddress = Config.get().isGroupByAddress();
|
||||||
boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs();
|
boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs();
|
||||||
|
|
||||||
|
|
@ -624,9 +623,7 @@ public class PayNymController {
|
||||||
List<UtxoSelector> utxoSelectors = List.of(utxos == null ? new KnapsackUtxoSelector(noInputsFee) : new PresetUtxoSelector(utxos, true, false));
|
List<UtxoSelector> utxoSelectors = List.of(utxos == null ? new KnapsackUtxoSelector(noInputsFee) : new PresetUtxoSelector(utxos, true, false));
|
||||||
List<TxoFilter> txoFilters = List.of(new SpentTxoFilter(), new FrozenTxoFilter(), new CoinbaseTxoFilter(wallet));
|
List<TxoFilter> txoFilters = List.of(new SpentTxoFilter(), new FrozenTxoFilter(), new CoinbaseTxoFilter(wallet));
|
||||||
|
|
||||||
TransactionParameters params = new TransactionParameters(utxoSelectors, txoFilters, payments, opReturns, Collections.emptySet(),
|
return wallet.createWalletTransaction(utxoSelectors, txoFilters, payments, opReturns, Collections.emptySet(), feeRate, minimumFeeRate, null, AppServices.getCurrentBlockHeight(), groupByAddress, includeMempoolOutputs);
|
||||||
feeRate, minimumFeeRate, minRelayFeeRate, null, AppServices.getCurrentBlockHeight(), groupByAddress, includeMempoolOutputs, true);
|
|
||||||
return wallet.createWalletTransaction(params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<BlockTransaction, WalletNode> getNotificationTransaction(PaymentCode externalPaymentCode) {
|
private Map<BlockTransaction, WalletNode> getNotificationTransaction(PaymentCode externalPaymentCode) {
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,9 @@ import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.SecureString;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.hummingbird.UR;
|
import com.sparrowwallet.hummingbird.UR;
|
||||||
|
|
@ -60,6 +57,7 @@ import tornadofx.control.Fieldset;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import tornadofx.control.Form;
|
import tornadofx.control.Form;
|
||||||
|
|
||||||
|
import javax.swing.text.html.Option;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
@ -182,15 +180,6 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
@FXML
|
@FXML
|
||||||
private CopyableLabel blockTimestamp;
|
private CopyableLabel blockTimestamp;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Field signedByField;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private CopyableLabel signedBy;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Form blockchainSpacerForm;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Form signingWalletForm;
|
private Form signingWalletForm;
|
||||||
|
|
||||||
|
|
@ -462,7 +451,6 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
headersForm.setWalletTransaction(getWalletTransaction(headersForm.getInputTransactions()));
|
headersForm.setWalletTransaction(getWalletTransaction(headersForm.getInputTransactions()));
|
||||||
|
|
||||||
blockchainForm.managedProperty().bind(blockchainForm.visibleProperty());
|
blockchainForm.managedProperty().bind(blockchainForm.visibleProperty());
|
||||||
blockchainSpacerForm.managedProperty().bind(blockchainForm.managedProperty());
|
|
||||||
|
|
||||||
signingWalletForm.managedProperty().bind(signingWalletForm.visibleProperty());
|
signingWalletForm.managedProperty().bind(signingWalletForm.visibleProperty());
|
||||||
sigHashForm.managedProperty().bind(sigHashForm.visibleProperty());
|
sigHashForm.managedProperty().bind(sigHashForm.visibleProperty());
|
||||||
|
|
@ -648,27 +636,24 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Payment> payments = new ArrayList<>();
|
List<Payment> payments = new ArrayList<>();
|
||||||
List<WalletTransaction.Output> outputs = new ArrayList<>();
|
|
||||||
Map<WalletNode, Long> changeMap = new LinkedHashMap<>();
|
Map<WalletNode, Long> changeMap = new LinkedHashMap<>();
|
||||||
Map<Script, WalletNode> receiveOutputScripts = wallet.getWalletOutputScripts(KeyPurpose.RECEIVE);
|
|
||||||
Map<Script, WalletNode> changeOutputScripts = wallet.getWalletOutputScripts(wallet.getChangeKeyPurpose());
|
Map<Script, WalletNode> changeOutputScripts = wallet.getWalletOutputScripts(wallet.getChangeKeyPurpose());
|
||||||
for(TransactionOutput txOutput : headersForm.getTransaction().getOutputs()) {
|
for(TransactionOutput txOutput : headersForm.getTransaction().getOutputs()) {
|
||||||
WalletNode changeNode = changeOutputScripts.get(txOutput.getScript());
|
WalletNode changeNode = changeOutputScripts.get(txOutput.getScript());
|
||||||
if(changeNode != null) {
|
if(changeNode != null) {
|
||||||
if(headersForm.getTransaction().getOutputs().size() == 4 && headersForm.getTransaction().getOutputs().stream().anyMatch(txo -> txo != txOutput && txo.getValue() == txOutput.getValue())) {
|
if(headersForm.getTransaction().getOutputs().size() == 4 && headersForm.getTransaction().getOutputs().stream().anyMatch(txo -> txo != txOutput && txo.getValue() == txOutput.getValue())) {
|
||||||
if(selectedTxos.values().stream().allMatch(Objects::nonNull)) {
|
if(selectedTxos.values().stream().allMatch(Objects::nonNull)) {
|
||||||
payments.add(new WalletNodePayment(changeNode, ".." + changeNode + " (Fake Mix)", txOutput.getValue(), false, Payment.Type.FAKE_MIX));
|
payments.add(new Payment(txOutput.getScript().getToAddress(), ".." + changeNode + " (Fake Mix)", txOutput.getValue(), false, Payment.Type.FAKE_MIX));
|
||||||
} else {
|
} else {
|
||||||
payments.add(new WalletNodePayment(changeNode, ".." + changeNode + " (Mix)", txOutput.getValue(), false, Payment.Type.MIX));
|
payments.add(new Payment(txOutput.getScript().getToAddress(), ".." + changeNode + " (Mix)", txOutput.getValue(), false, Payment.Type.MIX));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(changeMap.containsKey(changeNode)) {
|
if(changeMap.containsKey(changeNode)) {
|
||||||
payments.add(new WalletNodePayment(changeNode, headersForm.getName(), txOutput.getValue(), false, Payment.Type.DEFAULT));
|
payments.add(new Payment(txOutput.getScript().getToAddress(), headersForm.getName(), txOutput.getValue(), false, Payment.Type.DEFAULT));
|
||||||
} else {
|
} else {
|
||||||
changeMap.put(changeNode, txOutput.getValue());
|
changeMap.put(changeNode, txOutput.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outputs.add(new WalletTransaction.ChangeOutput(txOutput, changeNode, txOutput.getValue()));
|
|
||||||
} else {
|
} else {
|
||||||
Payment.Type paymentType = Payment.Type.DEFAULT;
|
Payment.Type paymentType = Payment.Type.DEFAULT;
|
||||||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||||
|
|
@ -679,44 +664,24 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
|
|
||||||
BlockTransactionHashIndex receivedTxo = walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txOutput.getHash()) && txo.getIndex() == txOutput.getIndex()).findFirst().orElse(null);
|
BlockTransactionHashIndex receivedTxo = walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txOutput.getHash()) && txo.getIndex() == txOutput.getIndex()).findFirst().orElse(null);
|
||||||
String label = headersForm.getName() == null || (headersForm.getName().startsWith("[") && headersForm.getName().endsWith("]") && headersForm.getName().length() == 8) ? null : headersForm.getName();
|
String label = headersForm.getName() == null || (headersForm.getName().startsWith("[") && headersForm.getName().endsWith("]") && headersForm.getName().length() == 8) ? null : headersForm.getName();
|
||||||
Address address = txOutput.getScript().getToAddress();
|
try {
|
||||||
WalletNode receiveNode = receiveOutputScripts.get(txOutput.getScript());
|
Payment payment = new Payment(txOutput.getScript().getToAddresses()[0], receivedTxo != null ? receivedTxo.getLabel() : label, txOutput.getValue(), false, paymentType);
|
||||||
SilentPaymentAddress silentPaymentAddress = headersForm.getSilentPaymentAddress(txOutput);
|
|
||||||
label = receivedTxo != null ? receivedTxo.getLabel() : label;
|
|
||||||
if(address != null || silentPaymentAddress != null) {
|
|
||||||
Payment payment;
|
|
||||||
if(silentPaymentAddress != null) {
|
|
||||||
payment = new SilentPayment(silentPaymentAddress, address, label, txOutput.getValue(), false);
|
|
||||||
} else if(receiveNode != null) {
|
|
||||||
payment = new WalletNodePayment(receiveNode, label, txOutput.getValue(), false, paymentType);
|
|
||||||
} else {
|
|
||||||
payment = new Payment(address, label, txOutput.getValue(), false, paymentType);
|
|
||||||
}
|
|
||||||
WalletTransaction createdTx = AppServices.get().getCreatedTransaction(selectedTxos.keySet());
|
WalletTransaction createdTx = AppServices.get().getCreatedTransaction(selectedTxos.keySet());
|
||||||
if(createdTx != null) {
|
if(createdTx != null) {
|
||||||
Optional<String> optLabel = createdTx.getPayments().stream()
|
Optional<String> optLabel = createdTx.getPayments().stream().filter(pymt -> pymt.getAddress().equals(payment.getAddress()) && pymt.getAmount() == payment.getAmount()).map(Payment::getLabel).findFirst();
|
||||||
.filter(pymt -> (pymt instanceof SilentPayment silentPayment ? silentPayment.getSilentPaymentAddress().equals(silentPaymentAddress) :
|
|
||||||
pymt.getAddress().equals(payment.getAddress())) && pymt.getAmount() == payment.getAmount()).map(Payment::getLabel).findFirst();
|
|
||||||
if(optLabel.isPresent()) {
|
if(optLabel.isPresent()) {
|
||||||
payment.setLabel(optLabel.get());
|
payment.setLabel(optLabel.get());
|
||||||
outputIndexLabels.put(txOutput.getIndex(), optLabel.get());
|
outputIndexLabels.put(txOutput.getIndex(), optLabel.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
payments.add(payment);
|
payments.add(payment);
|
||||||
if(payment instanceof SilentPayment silentPayment) {
|
} catch(Exception e) {
|
||||||
outputs.add(new WalletTransaction.SilentPaymentOutput(txOutput, silentPayment));
|
//ignore
|
||||||
} else if(payment instanceof WalletNodePayment walletNodePayment) {
|
|
||||||
outputs.add(new WalletTransaction.ConsolidationOutput(txOutput, walletNodePayment, walletNodePayment.getAmount()));
|
|
||||||
} else {
|
|
||||||
outputs.add(new WalletTransaction.PaymentOutput(txOutput, payment));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
outputs.add(new WalletTransaction.NonAddressOutput(txOutput));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WalletTransaction(wallet, headersForm.getTransaction(), Collections.emptyList(), List.of(selectedTxos), payments, outputs, changeMap, fee.getValue(), walletInputTransactions);
|
return new WalletTransaction(wallet, headersForm.getTransaction(), Collections.emptyList(), List.of(selectedTxos), payments, changeMap, fee.getValue(), walletInputTransactions);
|
||||||
} else {
|
} else {
|
||||||
Map<BlockTransactionHashIndex, WalletNode> selectedTxos = headersForm.getTransaction().getInputs().stream()
|
Map<BlockTransactionHashIndex, WalletNode> selectedTxos = headersForm.getTransaction().getInputs().stream()
|
||||||
.collect(Collectors.toMap(txInput -> getBlockTransactionInput(inputTransactions, txInput),
|
.collect(Collectors.toMap(txInput -> getBlockTransactionInput(inputTransactions, txInput),
|
||||||
|
|
@ -726,25 +691,16 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
selectedTxos.entrySet().forEach(entry -> entry.setValue(null));
|
selectedTxos.entrySet().forEach(entry -> entry.setValue(null));
|
||||||
|
|
||||||
List<Payment> payments = new ArrayList<>();
|
List<Payment> payments = new ArrayList<>();
|
||||||
List<WalletTransaction.Output> outputs = new ArrayList<>();
|
|
||||||
for(TransactionOutput txOutput : headersForm.getTransaction().getOutputs()) {
|
for(TransactionOutput txOutput : headersForm.getTransaction().getOutputs()) {
|
||||||
Address address = txOutput.getScript().getToAddress();
|
try {
|
||||||
SilentPaymentAddress silentPaymentAddress = headersForm.getSilentPaymentAddress(txOutput);
|
BlockTransactionHashIndex receivedTxo = getBlockTransactionOutput(txOutput);
|
||||||
BlockTransactionHashIndex receivedTxo = getBlockTransactionOutput(txOutput);
|
payments.add(new Payment(txOutput.getScript().getToAddresses()[0], receivedTxo != null ? receivedTxo.getLabel() : null, txOutput.getValue(), false));
|
||||||
String label = receivedTxo != null ? receivedTxo.getLabel() : null;
|
} catch(Exception e) {
|
||||||
if(address != null || silentPaymentAddress != null) {
|
//ignore
|
||||||
Payment payment = (silentPaymentAddress == null ?
|
|
||||||
new Payment(address, label, txOutput.getValue(), false) :
|
|
||||||
new SilentPayment(silentPaymentAddress, address, label, txOutput.getValue(), false));
|
|
||||||
payments.add(payment);
|
|
||||||
outputs.add(payment instanceof SilentPayment silentPayment ? new WalletTransaction.SilentPaymentOutput(txOutput, silentPayment) :
|
|
||||||
new WalletTransaction.PaymentOutput(txOutput, payment));
|
|
||||||
} else {
|
|
||||||
outputs.add(new WalletTransaction.NonAddressOutput(txOutput));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WalletTransaction(null, headersForm.getTransaction(), Collections.emptyList(), List.of(selectedTxos), payments, outputs, Collections.emptyMap(), fee.getValue(), inputTransactions);
|
return new WalletTransaction(null, headersForm.getTransaction(), Collections.emptyList(), List.of(selectedTxos), payments, Collections.emptyMap(), fee.getValue(), inputTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -818,7 +774,6 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
|
|
||||||
blockHeightField.managedProperty().bind(blockHeightField.visibleProperty());
|
blockHeightField.managedProperty().bind(blockHeightField.visibleProperty());
|
||||||
blockTimestampField.managedProperty().bind(blockTimestampField.visibleProperty());
|
blockTimestampField.managedProperty().bind(blockTimestampField.visibleProperty());
|
||||||
signedByField.managedProperty().bind(signedByField.visibleProperty());
|
|
||||||
|
|
||||||
if(blockTransaction.getHeight() > 0) {
|
if(blockTransaction.getHeight() > 0) {
|
||||||
blockHeightField.setVisible(true);
|
blockHeightField.setVisible(true);
|
||||||
|
|
@ -836,19 +791,6 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
} else {
|
} else {
|
||||||
blockTimestampField.setVisible(false);
|
blockTimestampField.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(headersForm.getWalletTransaction() != null && headersForm.getWalletTransaction().getWallet() != null
|
|
||||||
&& headersForm.getWalletTransaction().getWallet().getPolicyType() == PolicyType.MULTI
|
|
||||||
&& headersForm.getWalletTransaction().getWallet().getDefaultPolicy().getNumSignaturesRequired() < headersForm.getWalletTransaction().getWallet().getKeystores().size()) {
|
|
||||||
signedByField.setVisible(true);
|
|
||||||
Wallet wallet = headersForm.getWalletTransaction().getWallet();
|
|
||||||
Map<TransactionInput, Map<TransactionSignature, Keystore>> signedKeystores = wallet.getSignedKeystores(blockTransaction.getTransaction());
|
|
||||||
StringJoiner joiner = new StringJoiner(", ");
|
|
||||||
signedKeystores.values().stream().flatMap(map -> map.values().stream()).distinct().forEach(keystore -> joiner.add(keystore.getLabel()));
|
|
||||||
signedBy.setText(joiner.toString());
|
|
||||||
} else {
|
|
||||||
signedByField.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeSignButton(Wallet signingWallet) {
|
private void initializeSignButton(Wallet signingWallet) {
|
||||||
|
|
@ -985,7 +927,7 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
|
|
||||||
//Don't include non witness utxo fields for segwit wallets when displaying the PSBT as a QR - it can add greatly to the time required for scanning
|
//Don't include non witness utxo fields for segwit wallets when displaying the PSBT as a QR - it can add greatly to the time required for scanning
|
||||||
boolean includeNonWitnessUtxos = !Arrays.asList(ScriptType.WITNESS_TYPES).contains(headersForm.getSigningWallet().getScriptType());
|
boolean includeNonWitnessUtxos = !Arrays.asList(ScriptType.WITNESS_TYPES).contains(headersForm.getSigningWallet().getScriptType());
|
||||||
byte[] psbtBytes = headersForm.getPsbt().getForExport().serialize(true, includeNonWitnessUtxos);
|
byte[] psbtBytes = headersForm.getPsbt().serialize(true, includeNonWitnessUtxos);
|
||||||
|
|
||||||
CryptoPSBT cryptoPSBT = new CryptoPSBT(psbtBytes);
|
CryptoPSBT cryptoPSBT = new CryptoPSBT(psbtBytes);
|
||||||
BBQR bbqr = addBbqrOption ? new BBQR(BBQRType.PSBT, psbtBytes) : null;
|
BBQR bbqr = addBbqrOption ? new BBQR(BBQRType.PSBT, psbtBytes) : null;
|
||||||
|
|
@ -1068,7 +1010,7 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
}
|
}
|
||||||
|
|
||||||
try(FileOutputStream outputStream = new FileOutputStream(file)) {
|
try(FileOutputStream outputStream = new FileOutputStream(file)) {
|
||||||
outputStream.write(headersForm.getPsbt().getForExport().serialize());
|
outputStream.write(headersForm.getPsbt().serialize());
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
log.error("Error saving PSBT", e);
|
log.error("Error saving PSBT", e);
|
||||||
AppServices.showErrorDialog("Error saving PSBT", "Cannot write to " + file.getAbsolutePath());
|
AppServices.showErrorDialog("Error saving PSBT", "Cannot write to " + file.getAbsolutePath());
|
||||||
|
|
@ -1125,12 +1067,7 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
|
|
||||||
private void signUnencryptedKeystores(Wallet unencryptedWallet) {
|
private void signUnencryptedKeystores(Wallet unencryptedWallet) {
|
||||||
try {
|
try {
|
||||||
Map<PSBTInput, WalletNode> signingNodes = unencryptedWallet.getSigningNodes(headersForm.getPsbt());
|
unencryptedWallet.sign(headersForm.getPsbt());
|
||||||
List<SilentPayment> silentPayments = unencryptedWallet.computeSilentPaymentOutputs(headersForm.getPsbt(), signingNodes);
|
|
||||||
if(!silentPayments.isEmpty()) {
|
|
||||||
EventManager.get().post(new TransactionOutputsChangedEvent(headersForm.getTransaction()));
|
|
||||||
}
|
|
||||||
unencryptedWallet.sign(signingNodes);
|
|
||||||
updateSignedKeystores(headersForm.getSigningWallet());
|
updateSignedKeystores(headersForm.getSigningWallet());
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
log.warn("Failed to Sign", e);
|
log.warn("Failed to Sign", e);
|
||||||
|
|
@ -1202,7 +1139,7 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
|
|
||||||
if(fee.getValue() > 0) {
|
if(fee.getValue() > 0) {
|
||||||
double feeRateAmt = fee.getValue() / headersForm.getTransaction().getVirtualSize();
|
double feeRateAmt = fee.getValue() / headersForm.getTransaction().getVirtualSize();
|
||||||
if(feeRateAmt > AppServices.getLongFeeRatesRange().getLast()) {
|
if(feeRateAmt > AppServices.LONG_FEE_RATES_RANGE.get(AppServices.LONG_FEE_RATES_RANGE.size() - 1)) {
|
||||||
Optional<ButtonType> optType = AppServices.showWarningDialog("Very high fee rate!",
|
Optional<ButtonType> optType = AppServices.showWarningDialog("Very high fee rate!",
|
||||||
"This transaction pays a very high fee rate of " + String.format("%.0f", feeRateAmt) + " sats/vB.\n\nBroadcast this transaction?", ButtonType.YES, ButtonType.NO);
|
"This transaction pays a very high fee rate of " + String.format("%.0f", feeRateAmt) + " sats/vB.\n\nBroadcast this transaction?", ButtonType.YES, ButtonType.NO);
|
||||||
if(optType.isPresent() && optType.get() == ButtonType.NO) {
|
if(optType.isPresent() && optType.get() == ButtonType.NO) {
|
||||||
|
|
@ -1288,17 +1225,9 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
|
|
||||||
UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat();
|
UnitFormat format = Config.get().getUnitFormat() == null ? UnitFormat.DOT : Config.get().getUnitFormat();
|
||||||
if(failMessage.startsWith("min relay fee not met")) {
|
if(failMessage.startsWith("min relay fee not met")) {
|
||||||
if(AppServices.getServerMinimumRelayFeeRate() != null && !AppServices.getServerMinimumRelayFeeRate().equals(AppServices.getMinimumRelayFeeRate())) {
|
AppServices.showErrorDialog("Error broadcasting transaction", "The fee rate for the signed transaction is below the minimum " + format.getCurrencyFormat().format(AppServices.getMinimumRelayFeeRate()) + " sats/vB. " +
|
||||||
AppServices.showErrorDialog("Error broadcasting transaction", "The fee rate for the signed transaction is below the minimum configured relay fee rate for the server of " +
|
"This usually happens because a keystore has created a signature that is larger than necessary.\n\n" +
|
||||||
format.getCurrencyFormat().format(AppServices.getServerMinimumRelayFeeRate()) + " sats/vB.");
|
"You can solve this by recreating the transaction with a slightly increased fee rate.");
|
||||||
} else {
|
|
||||||
Double minRelayFeeRate = AppServices.getServerMinimumRelayFeeRate() != null ? AppServices.getServerMinimumRelayFeeRate() : AppServices.getMinimumRelayFeeRate();
|
|
||||||
AppServices.showErrorDialog("Error broadcasting transaction", "The fee rate for the signed transaction is below the minimum " + format.getCurrencyFormat().format(minRelayFeeRate) + " sats/vB. " +
|
|
||||||
"This usually happens because a keystore has created a signature that is larger than necessary.\n\n" +
|
|
||||||
"You can solve this by recreating the transaction with a slightly increased fee rate.");
|
|
||||||
}
|
|
||||||
} else if(failMessage.startsWith("dust")) {
|
|
||||||
AppServices.showErrorDialog("Error broadcasting transaction", "The server will not accept this transaction for broadcast due to its configured dust limit policy.");
|
|
||||||
} else if(failMessage.startsWith("bad-txns-inputs-missingorspent")) {
|
} else if(failMessage.startsWith("bad-txns-inputs-missingorspent")) {
|
||||||
AppServices.showErrorDialog("Error broadcasting transaction", "The server returned an error indicating some or all of the UTXOs this transaction is spending are missing or have already been spent.");
|
AppServices.showErrorDialog("Error broadcasting transaction", "The server returned an error indicating some or all of the UTXOs this transaction is spending are missing or have already been spent.");
|
||||||
} else if(failMessage.contains("mempool min fee not met")) {
|
} else if(failMessage.contains("mempool min fee not met")) {
|
||||||
|
|
@ -1499,7 +1428,6 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
errorGlyph.getStyleClass().add("failure");
|
errorGlyph.getStyleClass().add("failure");
|
||||||
blockHeightField.setVisible(false);
|
blockHeightField.setVisible(false);
|
||||||
blockTimestampField.setVisible(false);
|
blockTimestampField.setVisible(false);
|
||||||
signedByField.setVisible(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1659,13 +1587,6 @@ public class HeadersController extends TransactionFormController implements Init
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void transactionOutputsChanged(TransactionOutputsChangedEvent event) {
|
|
||||||
if(event.getTransaction().equals(headersForm.getTransaction())) {
|
|
||||||
headersForm.setWalletTransaction(getWalletTransaction(headersForm.getInputTransactions()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void transactionExtracted(TransactionExtractedEvent event) {
|
public void transactionExtracted(TransactionExtractedEvent event) {
|
||||||
if(event.getPsbt().equals(headersForm.getPsbt())) {
|
if(event.getPsbt().equals(headersForm.getPsbt())) {
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ public class InputController extends TransactionFormController implements Initia
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(txInput.isAbsoluteTimeLocked()) {
|
if(txInput.isAbsoluteTimeLocked()) {
|
||||||
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_DISABLED);
|
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
|
||||||
if(oldValue != null) {
|
if(oldValue != null) {
|
||||||
EventManager.get().post(new TransactionChangedEvent(transaction));
|
EventManager.get().post(new TransactionChangedEvent(transaction));
|
||||||
}
|
}
|
||||||
|
|
@ -389,7 +389,7 @@ public class InputController extends TransactionFormController implements Initia
|
||||||
if(rbf.selectedProperty().getValue()) {
|
if(rbf.selectedProperty().getValue()) {
|
||||||
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
|
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
|
||||||
} else {
|
} else {
|
||||||
txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_DISABLED);
|
txInput.setSequenceNumber(TransactionInput.SEQUENCE_LOCKTIME_DISABLED - 1);
|
||||||
}
|
}
|
||||||
if(old_toggle != null) {
|
if(old_toggle != null) {
|
||||||
EventManager.get().post(new TransactionChangedEvent(transaction));
|
EventManager.get().post(new TransactionChangedEvent(transaction));
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.protocol.NonStandardScriptException;
|
import com.sparrowwallet.drongo.protocol.NonStandardScriptException;
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionInput;
|
import com.sparrowwallet.drongo.protocol.TransactionInput;
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.*;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.PSBTReorderedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.UnitFormatChangedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.BlockTransactionOutputsFetchedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.ViewTransactionEvent;
|
||||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
|
@ -68,7 +70,20 @@ public class OutputController extends TransactionFormController implements Initi
|
||||||
updateOutputLegendFromWallet(txOutput, walletTransaction != null ? walletTransaction.getWallet() : null);
|
updateOutputLegendFromWallet(txOutput, walletTransaction != null ? walletTransaction.getWallet() : null);
|
||||||
});
|
});
|
||||||
updateOutputLegendFromWallet(txOutput, outputForm.getWallet());
|
updateOutputLegendFromWallet(txOutput, outputForm.getWallet());
|
||||||
updateSends(txOutput);
|
|
||||||
|
value.setValue(txOutput.getValue());
|
||||||
|
to.setVisible(false);
|
||||||
|
try {
|
||||||
|
Address[] addresses = txOutput.getScript().getToAddresses();
|
||||||
|
to.setVisible(true);
|
||||||
|
if(addresses.length == 1) {
|
||||||
|
address.setAddress(addresses[0]);
|
||||||
|
} else {
|
||||||
|
address.setText("multiple addresses");
|
||||||
|
}
|
||||||
|
} catch(NonStandardScriptException e) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
|
||||||
spentField.managedProperty().bind(spentField.visibleProperty());
|
spentField.managedProperty().bind(spentField.visibleProperty());
|
||||||
spentByField.managedProperty().bind(spentByField.visibleProperty());
|
spentByField.managedProperty().bind(spentByField.visibleProperty());
|
||||||
|
|
@ -83,32 +98,6 @@ public class OutputController extends TransactionFormController implements Initi
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeScriptField(scriptPubKeyArea);
|
initializeScriptField(scriptPubKeyArea);
|
||||||
updateScriptPubKey(txOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSends(TransactionOutput txOutput) {
|
|
||||||
value.setValue(txOutput.getValue());
|
|
||||||
to.setVisible(false);
|
|
||||||
Address toAddress = txOutput.getScript().getToAddress();
|
|
||||||
SilentPaymentAddress silentPaymentAddress = outputForm.getSilentPaymentAddress(txOutput);
|
|
||||||
if(toAddress != null) {
|
|
||||||
to.setVisible(true);
|
|
||||||
address.setAddress(toAddress);
|
|
||||||
} else if(silentPaymentAddress != null) {
|
|
||||||
to.setVisible(true);
|
|
||||||
address.setText(silentPaymentAddress.toAbbreviatedString());
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
txOutput.getScript().getToAddresses();
|
|
||||||
to.setVisible(true);
|
|
||||||
address.setText("multiple addresses");
|
|
||||||
} catch(NonStandardScriptException e) {
|
|
||||||
//ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateScriptPubKey(TransactionOutput txOutput) {
|
|
||||||
scriptPubKeyArea.clear();
|
scriptPubKeyArea.clear();
|
||||||
scriptPubKeyArea.appendScript(txOutput.getScript(), null, null);
|
scriptPubKeyArea.appendScript(txOutput.getScript(), null, null);
|
||||||
}
|
}
|
||||||
|
|
@ -126,14 +115,11 @@ public class OutputController extends TransactionFormController implements Initi
|
||||||
WalletTransaction.Output output = outputs.get(outputForm.getIndex());
|
WalletTransaction.Output output = outputs.get(outputForm.getIndex());
|
||||||
if(output instanceof WalletTransaction.NonAddressOutput) {
|
if(output instanceof WalletTransaction.NonAddressOutput) {
|
||||||
outputFieldset.setText(baseText);
|
outputFieldset.setText(baseText);
|
||||||
} else if(output instanceof WalletTransaction.SilentPaymentOutput) {
|
|
||||||
outputFieldset.setText(baseText + " - Silent Payment");
|
|
||||||
} else if(output instanceof WalletTransaction.ConsolidationOutput) {
|
|
||||||
outputFieldset.setText(baseText + " - Consolidation");
|
|
||||||
} else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) {
|
} else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) {
|
||||||
Payment payment = paymentOutput.getPayment();
|
Payment payment = paymentOutput.getPayment();
|
||||||
Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
|
Wallet toWallet = walletTx.getToWallet(AppServices.get().getOpenWallets().keySet(), payment);
|
||||||
outputFieldset.setText(baseText + (toWallet == null ? " - Payment" : " - Received to " + toWallet.getFullDisplayName()));
|
WalletNode toNode = walletTx.getWallet() != null && !walletTx.getWallet().isBip47() ? walletTx.getAddressNodeMap().get(payment.getAddress()) : null;
|
||||||
|
outputFieldset.setText(baseText + (toWallet == null ? (toNode != null ? " - Consolidation" : " - Payment") : " - Received to " + toWallet.getFullDisplayName()));
|
||||||
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
|
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
|
||||||
outputFieldset.setText(baseText + " - Change to " + changeOutput.getWalletNode().toString());
|
outputFieldset.setText(baseText + " - Change to " + changeOutput.getWalletNode().toString());
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -220,12 +206,4 @@ public class OutputController extends TransactionFormController implements Initi
|
||||||
updateOutputLegendFromWallet(outputForm.getTransactionOutput(), null);
|
updateOutputLegendFromWallet(outputForm.getTransactionOutput(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void transactionOutputsChanged(TransactionOutputsChangedEvent event) {
|
|
||||||
if(event.getTransaction().equals(outputForm.getTransaction())) {
|
|
||||||
updateSends(outputForm.getTransactionOutput());
|
|
||||||
updateScriptPubKey(outputForm.getTransactionOutput());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,7 @@ public class OutputForm extends IndexedTransactionForm {
|
||||||
}
|
}
|
||||||
} else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) {
|
} else if(output instanceof WalletTransaction.PaymentOutput paymentOutput) {
|
||||||
Payment payment = paymentOutput.getPayment();
|
Payment payment = paymentOutput.getPayment();
|
||||||
return new Label(payment.getLabel() != null && payment.getType() != Payment.Type.FAKE_MIX && payment.getType() != Payment.Type.MIX ? payment.getLabel() : payment.toString(),
|
return new Label(payment.getLabel() != null && payment.getType() != Payment.Type.FAKE_MIX && payment.getType() != Payment.Type.MIX ? payment.getLabel() : payment.getAddress().toString(),
|
||||||
GlyphUtils.getOutputGlyph(getWalletTransaction(), payment));
|
|
||||||
} else if(output instanceof WalletTransaction.ConsolidationOutput consolidationOutput) {
|
|
||||||
Payment payment = consolidationOutput.getWalletNodePayment();
|
|
||||||
return new Label(payment.getLabel() != null && payment.getType() != Payment.Type.FAKE_MIX && payment.getType() != Payment.Type.MIX ? payment.getLabel() : payment.toString(),
|
|
||||||
GlyphUtils.getOutputGlyph(getWalletTransaction(), payment));
|
GlyphUtils.getOutputGlyph(getWalletTransaction(), payment));
|
||||||
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
|
} else if(output instanceof WalletTransaction.ChangeOutput changeOutput) {
|
||||||
return new Label("Change", GlyphUtils.getChangeGlyph());
|
return new Label("Change", GlyphUtils.getChangeGlyph());
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.CopyableCoinLabel;
|
import com.sparrowwallet.sparrow.control.CopyableCoinLabel;
|
||||||
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
||||||
import com.sparrowwallet.sparrow.event.TransactionOutputsChangedEvent;
|
|
||||||
import com.sparrowwallet.sparrow.event.UnitFormatChangedEvent;
|
import com.sparrowwallet.sparrow.event.UnitFormatChangedEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
|
@ -61,11 +60,4 @@ public class OutputsController extends TransactionFormController implements Init
|
||||||
public void unitFormatChanged(UnitFormatChangedEvent event) {
|
public void unitFormatChanged(UnitFormatChangedEvent event) {
|
||||||
total.refresh(event.getUnitFormat(), event.getBitcoinUnit());
|
total.refresh(event.getUnitFormat(), event.getBitcoinUnit());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void transactionOutputsChanged(TransactionOutputsChangedEvent event) {
|
|
||||||
if(event.getTransaction().equals(outputsForm.getTransaction())) {
|
|
||||||
updatePieData(outputsPie, outputsForm.getTransaction().getOutputs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ package com.sparrowwallet.sparrow.transaction;
|
||||||
import com.sparrowwallet.drongo.KeyPurpose;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTOutput;
|
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
|
@ -195,16 +193,4 @@ public class TransactionData {
|
||||||
public Wallet getWallet() {
|
public Wallet getWallet() {
|
||||||
return getSigningWallet() != null ? getSigningWallet() : (getWalletTransaction() != null ? getWalletTransaction().getWallet() : null);
|
return getSigningWallet() != null ? getSigningWallet() : (getWalletTransaction() != null ? getWalletTransaction().getWallet() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SilentPaymentAddress getSilentPaymentAddress(TransactionOutput txOutput) {
|
|
||||||
if(getPsbt() != null && txOutput.getParent() != null) {
|
|
||||||
for(PSBTOutput psbtOutput : getPsbt().getPsbtOutputs()) {
|
|
||||||
if(psbtOutput.getOutput().getIndex() == txOutput.getIndex() && psbtOutput.getSilentPaymentAddress() != null) {
|
|
||||||
return psbtOutput.getSilentPaymentAddress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@ package com.sparrowwallet.sparrow.transaction;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionSignature;
|
import com.sparrowwallet.drongo.protocol.TransactionSignature;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
|
@ -114,10 +112,6 @@ public abstract class TransactionForm {
|
||||||
return txdata.getWallet();
|
return txdata.getWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SilentPaymentAddress getSilentPaymentAddress(TransactionOutput output) {
|
|
||||||
return txdata.getSilentPaymentAddress(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEditable() {
|
public boolean isEditable() {
|
||||||
if(getBlockTransaction() != null) {
|
if(getBlockTransaction() != null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
import com.sparrowwallet.drongo.protocol.NonStandardScriptException;
|
import com.sparrowwallet.drongo.protocol.NonStandardScriptException;
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.sparrow.UnitFormat;
|
import com.sparrowwallet.sparrow.UnitFormat;
|
||||||
import com.sparrowwallet.sparrow.BaseController;
|
import com.sparrowwallet.sparrow.BaseController;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
|
@ -34,7 +33,17 @@ public abstract class TransactionFormController extends BaseController {
|
||||||
long totalAmt = 0;
|
long totalAmt = 0;
|
||||||
for(int i = 0; i < outputs.size(); i++) {
|
for(int i = 0; i < outputs.size(); i++) {
|
||||||
TransactionOutput output = outputs.get(i);
|
TransactionOutput output = outputs.get(i);
|
||||||
String name = getPieDataName(i, output);
|
String name = "#" + i;
|
||||||
|
try {
|
||||||
|
Address[] addresses = output.getScript().getToAddresses();
|
||||||
|
if(addresses.length == 1) {
|
||||||
|
name = name + " " + addresses[0].getAddress();
|
||||||
|
} else {
|
||||||
|
name = name + " [" + addresses[0].getAddress() + ",...]";
|
||||||
|
}
|
||||||
|
} catch(NonStandardScriptException e) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
|
||||||
totalAmt += output.getValue();
|
totalAmt += output.getValue();
|
||||||
outputsPieData.add(new PieChart.Data(name, output.getValue()));
|
outputsPieData.add(new PieChart.Data(name, output.getValue()));
|
||||||
|
|
@ -43,34 +52,6 @@ public abstract class TransactionFormController extends BaseController {
|
||||||
addPieData(pie, outputsPieData);
|
addPieData(pie, outputsPieData);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updatePieData(PieChart pie, List<TransactionOutput> outputs) {
|
|
||||||
for(int i = 0; i < outputs.size(); i++) {
|
|
||||||
TransactionOutput output = outputs.get(i);
|
|
||||||
String name = getPieDataName(i, output);
|
|
||||||
pie.getData().get(i).setName(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getPieDataName(int i, TransactionOutput output) {
|
|
||||||
String name = "#" + i;
|
|
||||||
Address address = output.getScript().getToAddress();
|
|
||||||
SilentPaymentAddress silentPaymentAddress = getTransactionForm().getSilentPaymentAddress(output);
|
|
||||||
if(address != null) {
|
|
||||||
name = name + " " + address.getAddress();
|
|
||||||
} else if(silentPaymentAddress != null) {
|
|
||||||
name = name + " " + silentPaymentAddress.toAbbreviatedString();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
Address[] addresses = output.getScript().getToAddresses();
|
|
||||||
name = name + " [" + addresses[0].getAddress() + ",...]";
|
|
||||||
} catch(NonStandardScriptException e) {
|
|
||||||
//ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addCoinbasePieData(PieChart pie, long value) {
|
protected void addCoinbasePieData(PieChart pie, long value) {
|
||||||
ObservableList<PieChart.Data> outputsPieData = FXCollections.observableList(List.of(new PieChart.Data("Coinbase", value)));
|
ObservableList<PieChart.Data> outputsPieData = FXCollections.observableList(List.of(new PieChart.Data("Coinbase", value)));
|
||||||
addPieData(pie, outputsPieData);
|
addPieData(pie, outputsPieData);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.sparrowwallet.sparrow.wallet;
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.sparrowwallet.drongo.BitcoinUnit;
|
import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
import com.sparrowwallet.drongo.KeyPurpose;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
|
|
@ -10,15 +9,15 @@ import com.sparrowwallet.drongo.address.P2PKHAddress;
|
||||||
import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException;
|
import com.sparrowwallet.drongo.bip47.InvalidPaymentCodeException;
|
||||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.dns.DnsPaymentCache;
|
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
|
|
||||||
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
import com.sparrowwallet.drongo.uri.BitcoinURI;
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.*;
|
import com.sparrowwallet.sparrow.UnitFormat;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.CurrencyRate;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.*;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
|
@ -26,8 +25,6 @@ import com.sparrowwallet.sparrow.io.CardApi;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||||
import com.sparrowwallet.drongo.dns.DnsPayment;
|
|
||||||
import com.sparrowwallet.drongo.dns.DnsPaymentResolver;
|
|
||||||
import com.sparrowwallet.sparrow.paynym.PayNym;
|
import com.sparrowwallet.sparrow.paynym.PayNym;
|
||||||
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
import com.sparrowwallet.sparrow.paynym.PayNymDialog;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
|
@ -39,32 +36,21 @@ import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.concurrent.Service;
|
|
||||||
import javafx.concurrent.Task;
|
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.input.Clipboard;
|
|
||||||
import javafx.scene.input.ClipboardContent;
|
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
import org.controlsfx.glyphfont.Glyph;
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
import org.controlsfx.validation.ValidationResult;
|
import org.controlsfx.validation.ValidationResult;
|
||||||
import org.controlsfx.validation.ValidationSupport;
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
import org.controlsfx.validation.Validator;
|
import org.controlsfx.validation.Validator;
|
||||||
import org.girod.javafx.svgimage.SVGImage;
|
|
||||||
import org.girod.javafx.svgimage.SVGLoader;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -143,14 +129,8 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final ObjectProperty<WalletNode> consolidationNodeProperty = new SimpleObjectProperty<>();
|
|
||||||
|
|
||||||
private final ObjectProperty<PayNym> payNymProperty = new SimpleObjectProperty<>();
|
private final ObjectProperty<PayNym> payNymProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
private final ObjectProperty<SilentPaymentAddress> silentPaymentAddressProperty = new SimpleObjectProperty<>();
|
|
||||||
|
|
||||||
private final ObjectProperty<DnsPayment> dnsPaymentProperty = new SimpleObjectProperty<>();
|
|
||||||
|
|
||||||
private static final Wallet payNymWallet = new Wallet() {
|
private static final Wallet payNymWallet = new Wallet() {
|
||||||
@Override
|
@Override
|
||||||
public String getFullDisplayName() {
|
public String getFullDisplayName() {
|
||||||
|
|
@ -165,127 +145,6 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final ChangeListener<String> addressListener = new ChangeListener<>() {
|
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
|
|
||||||
address.leftProperty().set(null);
|
|
||||||
|
|
||||||
if(consolidationNodeProperty.get() != null && !newValue.equals(consolidationNodeProperty.get().getAddress().toString())) {
|
|
||||||
consolidationNodeProperty.set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(payNymProperty.get() != null && !newValue.equals(payNymProperty.get().nymName())) {
|
|
||||||
payNymProperty.set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dnsPaymentProperty.get() != null && !newValue.equals(dnsPaymentProperty.get().hrn())) {
|
|
||||||
dnsPaymentProperty.set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(silentPaymentAddressProperty.get() != null && !newValue.equals(silentPaymentAddressProperty.get().getAddress())) {
|
|
||||||
silentPaymentAddressProperty.set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
BitcoinURI bitcoinURI = new BitcoinURI(newValue);
|
|
||||||
Platform.runLater(() -> updateFromURI(bitcoinURI));
|
|
||||||
return;
|
|
||||||
} catch(Exception e) {
|
|
||||||
//ignore, not a URI
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<String> optDnsPaymentHrn = DnsPayment.getHrn(newValue);
|
|
||||||
if(optDnsPaymentHrn.isPresent()) {
|
|
||||||
String dnsPaymentHrn = optDnsPaymentHrn.get();
|
|
||||||
DnsPayment cachedDnsPayment = DnsPaymentCache.getDnsPayment(dnsPaymentHrn);
|
|
||||||
if(cachedDnsPayment != null) {
|
|
||||||
setDnsPayment(cachedDnsPayment);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Config.get().hasServer() && !AppServices.isConnected() && !AppServices.isConnecting()) {
|
|
||||||
if(Config.get().getConnectToResolve() == null || Config.get().getConnectToResolve() == Boolean.FALSE) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
ConfirmationAlert confirmationAlert = new ConfirmationAlert("Connect to resolve?", "You are currently offline. Connect to resolve the address?", ButtonType.NO, ButtonType.YES);
|
|
||||||
Optional<ButtonType> optType = confirmationAlert.showAndWait();
|
|
||||||
if(confirmationAlert.isDontAskAgain() && optType.isPresent()) {
|
|
||||||
Config.get().setConnectToResolve(optType.get() == ButtonType.YES);
|
|
||||||
}
|
|
||||||
if(optType.isPresent() && optType.get() == ButtonType.YES) {
|
|
||||||
EventManager.get().post(new RequestConnectEvent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Platform.runLater(() -> EventManager.get().post(new RequestConnectEvent()));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DnsPaymentService dnsPaymentService = new DnsPaymentService(dnsPaymentHrn);
|
|
||||||
dnsPaymentService.setOnSucceeded(_ -> dnsPaymentService.getValue().ifPresent(dnsPayment -> setDnsPayment(dnsPayment)));
|
|
||||||
dnsPaymentService.setOnFailed(failEvent -> {
|
|
||||||
if(failEvent.getSource().getException() != null && !(failEvent.getSource().getException().getCause() instanceof TimeoutException)) {
|
|
||||||
AppServices.showErrorDialog("Validation failed for " + dnsPaymentHrn, Throwables.getRootCause(failEvent.getSource().getException()).getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dnsPaymentService.start();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sendController.getWalletForm().getWallet().hasPaymentCode()) {
|
|
||||||
try {
|
|
||||||
PaymentCode paymentCode = new PaymentCode(newValue);
|
|
||||||
Wallet recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, sendController.getWalletForm().getWallet().getScriptType());
|
|
||||||
if(recipientBip47Wallet == null && sendController.getWalletForm().getWallet().getScriptType() != ScriptType.P2PKH) {
|
|
||||||
recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, ScriptType.P2PKH);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(recipientBip47Wallet != null) {
|
|
||||||
PayNym payNym = PayNym.fromWallet(recipientBip47Wallet);
|
|
||||||
Platform.runLater(() -> setPayNym(payNym));
|
|
||||||
} else if(!paymentCode.equals(sendController.getWalletForm().getWallet().getPaymentCode())) {
|
|
||||||
ButtonType previewType = new ButtonType("Preview Transaction", ButtonBar.ButtonData.YES);
|
|
||||||
Optional<ButtonType> optButton = AppServices.showAlertDialog("Send notification transaction?", "This payment code is not yet linked with a notification transaction. Send a notification transaction?", Alert.AlertType.CONFIRMATION, ButtonType.CANCEL, previewType);
|
|
||||||
if(optButton.isPresent() && optButton.get() == previewType) {
|
|
||||||
Payment payment = new Payment(paymentCode.getNotificationAddress(), "Link " + paymentCode.toAbbreviatedString(), MINIMUM_P2PKH_OUTPUT_SATS, false);
|
|
||||||
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(sendController.getWalletForm().getWallet(), List.of(payment), List.of(new byte[80]), paymentCode)));
|
|
||||||
} else {
|
|
||||||
Platform.runLater(() -> address.setText(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
//ignore, not a payment code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
SilentPaymentAddress silentPaymentAddress = SilentPaymentAddress.from(newValue);
|
|
||||||
setSilentPaymentAddress(silentPaymentAddress);
|
|
||||||
} catch(Exception e) {
|
|
||||||
//ignore, not a silent payment address
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Address toAddress = Address.fromString(newValue);
|
|
||||||
WalletNode walletNode = sendController.getWalletNode(toAddress);
|
|
||||||
if(walletNode != null) {
|
|
||||||
consolidationNodeProperty.set(walletNode);
|
|
||||||
}
|
|
||||||
label.requestFocus();
|
|
||||||
} catch(Exception e) {
|
|
||||||
//ignore, not an address
|
|
||||||
}
|
|
||||||
|
|
||||||
revalidateAmount();
|
|
||||||
maxButton.setDisable(!isMaxButtonEnabled());
|
|
||||||
sendController.updateTransaction();
|
|
||||||
|
|
||||||
if(validationSupport != null) {
|
|
||||||
validationSupport.setErrorDecorationEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
|
|
@ -351,28 +210,6 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
revalidateAmount();
|
revalidateAmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
silentPaymentAddressProperty.addListener((observable, oldValue, silentPaymentAddress) -> {
|
|
||||||
revalidateAmount();
|
|
||||||
});
|
|
||||||
|
|
||||||
dnsPaymentProperty.addListener((observable, oldValue, dnsPayment) -> {
|
|
||||||
if(dnsPayment != null) {
|
|
||||||
MenuItem copyMenuItem = new MenuItem("Copy URI");
|
|
||||||
copyMenuItem.setOnAction(e -> {
|
|
||||||
ClipboardContent content = new ClipboardContent();
|
|
||||||
content.putString(dnsPayment.bitcoinURI().toURIString());
|
|
||||||
Clipboard.getSystemClipboard().setContent(content);
|
|
||||||
});
|
|
||||||
address.setContextMenu(address.getCustomContextMenu(List.of(copyMenuItem)));
|
|
||||||
} else {
|
|
||||||
address.setContextMenu(address.getCustomContextMenu(Collections.emptyList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
revalidateAmount();
|
|
||||||
maxButton.setDisable(!isMaxButtonEnabled());
|
|
||||||
sendController.updateTransaction();
|
|
||||||
});
|
|
||||||
|
|
||||||
address.setTextFormatter(new TextFormatter<>(change -> {
|
address.setTextFormatter(new TextFormatter<>(change -> {
|
||||||
String controlNewText = change.getControlNewText();
|
String controlNewText = change.getControlNewText();
|
||||||
if(!controlNewText.equals(controlNewText.trim())) {
|
if(!controlNewText.equals(controlNewText.trim())) {
|
||||||
|
|
@ -385,8 +222,55 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
return change;
|
return change;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
address.textProperty().addListener(addressListener);
|
address.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
address.setContextMenu(address.getCustomContextMenu(Collections.emptyList()));
|
address.leftProperty().set(null);
|
||||||
|
|
||||||
|
if(payNymProperty.get() != null && !newValue.equals(payNymProperty.get().nymName())) {
|
||||||
|
payNymProperty.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
BitcoinURI bitcoinURI = new BitcoinURI(newValue);
|
||||||
|
Platform.runLater(() -> updateFromURI(bitcoinURI));
|
||||||
|
return;
|
||||||
|
} catch(Exception e) {
|
||||||
|
//ignore, not a URI
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sendController.getWalletForm().getWallet().hasPaymentCode()) {
|
||||||
|
try {
|
||||||
|
PaymentCode paymentCode = new PaymentCode(newValue);
|
||||||
|
Wallet recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, sendController.getWalletForm().getWallet().getScriptType());
|
||||||
|
if(recipientBip47Wallet == null && sendController.getWalletForm().getWallet().getScriptType() != ScriptType.P2PKH) {
|
||||||
|
recipientBip47Wallet = sendController.getWalletForm().getWallet().getChildWallet(paymentCode, ScriptType.P2PKH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(recipientBip47Wallet != null) {
|
||||||
|
PayNym payNym = PayNym.fromWallet(recipientBip47Wallet);
|
||||||
|
Platform.runLater(() -> setPayNym(payNym));
|
||||||
|
} else if(!paymentCode.equals(sendController.getWalletForm().getWallet().getPaymentCode())) {
|
||||||
|
ButtonType previewType = new ButtonType("Preview Transaction", ButtonBar.ButtonData.YES);
|
||||||
|
Optional<ButtonType> optButton = AppServices.showAlertDialog("Send notification transaction?", "This payment code is not yet linked with a notification transaction. Send a notification transaction?", Alert.AlertType.CONFIRMATION, ButtonType.CANCEL, previewType);
|
||||||
|
if(optButton.isPresent() && optButton.get() == previewType) {
|
||||||
|
Payment payment = new Payment(paymentCode.getNotificationAddress(), "Link " + paymentCode.toAbbreviatedString(), MINIMUM_P2PKH_OUTPUT_SATS, false);
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(sendController.getWalletForm().getWallet(), List.of(payment), List.of(new byte[80]), paymentCode)));
|
||||||
|
} else {
|
||||||
|
Platform.runLater(() -> address.setText(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
//ignore, not a payment code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revalidateAmount();
|
||||||
|
maxButton.setDisable(!isMaxButtonEnabled());
|
||||||
|
sendController.updateTransaction();
|
||||||
|
|
||||||
|
if(validationSupport != null) {
|
||||||
|
validationSupport.setErrorDecorationEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
label.textProperty().addListener((observable, oldValue, newValue) -> {
|
label.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
maxButton.setDisable(!isMaxButtonEnabled());
|
maxButton.setDisable(!isMaxButtonEnabled());
|
||||||
|
|
@ -444,37 +328,6 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDnsPayment(DnsPayment dnsPayment) {
|
|
||||||
if(dnsPayment.hasAddress()) {
|
|
||||||
DnsPaymentCache.putDnsPayment(dnsPayment.bitcoinURI().getAddress(), dnsPayment);
|
|
||||||
} else if(dnsPayment.hasSilentPaymentAddress()) {
|
|
||||||
DnsPaymentCache.putDnsPayment(dnsPayment.bitcoinURI().getSilentPaymentAddress(), dnsPayment);
|
|
||||||
setSilentPaymentAddress(dnsPayment.bitcoinURI().getSilentPaymentAddress());
|
|
||||||
} else {
|
|
||||||
AppServices.showWarningDialog("No Address Provided", "The DNS payment instruction for " + dnsPayment.hrn() + " resolved correctly but did not contain a bitcoin address.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsPaymentProperty.set(dnsPayment);
|
|
||||||
address.setText(dnsPayment.hrn());
|
|
||||||
revalidate(address, addressListener);
|
|
||||||
address.leftProperty().set(getBitcoinCharacter());
|
|
||||||
if(label.getText().isEmpty() || (label.getText().startsWith("₿") && !label.getText().contains(" "))) {
|
|
||||||
label.setText(dnsPayment.toString());
|
|
||||||
}
|
|
||||||
label.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSilentPaymentAddress(SilentPaymentAddress silentPaymentAddress) {
|
|
||||||
if(!sendController.getWalletForm().getWallet().canSendSilentPayments()) {
|
|
||||||
Platform.runLater(() -> AppServices.showErrorDialog("Silent Payments Unsupported", "This wallet does not support sending silent payments. Use a single signature software wallet."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
silentPaymentAddressProperty.set(silentPaymentAddress);
|
|
||||||
label.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateOpenWallets() {
|
private void updateOpenWallets() {
|
||||||
updateOpenWallets(AppServices.get().getOpenWallets().keySet());
|
updateOpenWallets(AppServices.get().getOpenWallets().keySet());
|
||||||
}
|
}
|
||||||
|
|
@ -546,16 +399,6 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
}
|
}
|
||||||
|
|
||||||
private Address getRecipientAddress() throws InvalidAddressException {
|
private Address getRecipientAddress() throws InvalidAddressException {
|
||||||
SilentPaymentAddress silentPaymentAddress = silentPaymentAddressProperty.get();
|
|
||||||
if(silentPaymentAddress != null) {
|
|
||||||
return SilentPayment.getDummyAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
DnsPayment dnsPayment = dnsPaymentProperty.get();
|
|
||||||
if(dnsPayment != null && dnsPayment.hasAddress()) {
|
|
||||||
return dnsPayment.bitcoinURI().getAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
PayNym payNym = payNymProperty.get();
|
PayNym payNym = payNymProperty.get();
|
||||||
if(payNym == null) {
|
if(payNym == null) {
|
||||||
return Address.fromString(address.getText());
|
return Address.fromString(address.getText());
|
||||||
|
|
@ -673,17 +516,7 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
Long value = sendAll ? Long.valueOf(getRecipientDustThreshold() + 1) : getRecipientValueSats();
|
Long value = sendAll ? Long.valueOf(getRecipientDustThreshold() + 1) : getRecipientValueSats();
|
||||||
|
|
||||||
if(!label.getText().isEmpty() && value != null && value >= getRecipientDustThreshold()) {
|
if(!label.getText().isEmpty() && value != null && value >= getRecipientDustThreshold()) {
|
||||||
Payment payment;
|
Payment payment = new Payment(recipientAddress, label.getText(), value, sendAll);
|
||||||
SilentPaymentAddress silentPaymentAddress = silentPaymentAddressProperty.get();
|
|
||||||
WalletNode consolidationNode = consolidationNodeProperty.get();
|
|
||||||
if(silentPaymentAddress != null) {
|
|
||||||
payment = new SilentPayment(silentPaymentAddress, label.getText(), value, sendAll);
|
|
||||||
} else if(consolidationNode != null) {
|
|
||||||
payment = new WalletNodePayment(consolidationNode, label.getText(), value, sendAll);
|
|
||||||
} else {
|
|
||||||
payment = new Payment(recipientAddress, label.getText(), value, sendAll);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(address.getUserData() != null) {
|
if(address.getUserData() != null) {
|
||||||
payment.setType((Payment.Type)address.getUserData());
|
payment.setType((Payment.Type)address.getUserData());
|
||||||
}
|
}
|
||||||
|
|
@ -700,14 +533,7 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
public void setPayment(Payment payment) {
|
public void setPayment(Payment payment) {
|
||||||
if(getRecipientValueSats() == null || payment.getAmount() != getRecipientValueSats()) {
|
if(getRecipientValueSats() == null || payment.getAmount() != getRecipientValueSats()) {
|
||||||
if(payment.getAddress() != null) {
|
if(payment.getAddress() != null) {
|
||||||
DnsPayment dnsPayment = DnsPaymentCache.getDnsPayment(payment);
|
address.setText(payment.getAddress().toString());
|
||||||
if(dnsPayment != null) {
|
|
||||||
address.setText(dnsPayment.hrn());
|
|
||||||
} else if(payment instanceof SilentPayment silentPayment) {
|
|
||||||
address.setText(silentPayment.getSilentPaymentAddress().getAddress());
|
|
||||||
} else {
|
|
||||||
address.setText(payment.getAddress().toString());
|
|
||||||
}
|
|
||||||
address.setUserData(payment.getType());
|
address.setUserData(payment.getType());
|
||||||
}
|
}
|
||||||
if(payment.getLabel() != null && !label.getText().equals(payment.getLabel())) {
|
if(payment.getLabel() != null && !label.getText().equals(payment.getLabel())) {
|
||||||
|
|
@ -738,10 +564,7 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
setSendMax(false);
|
setSendMax(false);
|
||||||
|
|
||||||
dustAmountProperty.set(false);
|
dustAmountProperty.set(false);
|
||||||
consolidationNodeProperty.set(null);
|
|
||||||
payNymProperty.set(null);
|
payNymProperty.set(null);
|
||||||
dnsPaymentProperty.set(null);
|
|
||||||
silentPaymentAddressProperty.set(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMaxInput(ActionEvent event) {
|
public void setMaxInput(ActionEvent event) {
|
||||||
|
|
@ -749,7 +572,8 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
if(utxoSelector == null) {
|
if(utxoSelector == null) {
|
||||||
MaxUtxoSelector maxUtxoSelector = new MaxUtxoSelector();
|
MaxUtxoSelector maxUtxoSelector = new MaxUtxoSelector();
|
||||||
sendController.utxoSelectorProperty().set(maxUtxoSelector);
|
sendController.utxoSelectorProperty().set(maxUtxoSelector);
|
||||||
} else if(utxoSelector instanceof PresetUtxoSelector presetUtxoSelector && !isValidAddressAndLabel() && sendController.getPaymentTabs().getTabs().size() == 1) {
|
} else if(utxoSelector instanceof PresetUtxoSelector && !isValidAddressAndLabel() && sendController.getPaymentTabs().getTabs().size() == 1) {
|
||||||
|
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
|
||||||
Payment payment = new Payment(null, null, presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(), true);
|
Payment payment = new Payment(null, null, presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum(), true);
|
||||||
setPayment(payment);
|
setPayment(payment);
|
||||||
return;
|
return;
|
||||||
|
|
@ -801,7 +625,7 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
setRecipientValueSats(bitcoinURI.getAmount());
|
setRecipientValueSats(bitcoinURI.getAmount());
|
||||||
setFiatAmount(AppServices.getFiatCurrencyExchangeRate(), bitcoinURI.getAmount());
|
setFiatAmount(AppServices.getFiatCurrencyExchangeRate(), bitcoinURI.getAmount());
|
||||||
}
|
}
|
||||||
if(bitcoinURI.getAddress() != null && bitcoinURI.getPayjoinUrl() != null) {
|
if(bitcoinURI.getPayjoinUrl() != null) {
|
||||||
AppServices.addPayjoinURI(bitcoinURI);
|
AppServices.addPayjoinURI(bitcoinURI);
|
||||||
}
|
}
|
||||||
sendController.updateTransaction();
|
sendController.updateTransaction();
|
||||||
|
|
@ -852,33 +676,10 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
public static Glyph getPayNymGlyph() {
|
public static Glyph getPayNymGlyph() {
|
||||||
Glyph payNymGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ROBOT);
|
Glyph payNymGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.ROBOT);
|
||||||
payNymGlyph.getStyleClass().add("paynym-icon");
|
payNymGlyph.getStyleClass().add("paynym-icon");
|
||||||
payNymGlyph.setFontSize(10);
|
payNymGlyph.setFontSize(12);
|
||||||
return payNymGlyph;
|
return payNymGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Node getBitcoinCharacter() {
|
|
||||||
try {
|
|
||||||
URL url;
|
|
||||||
if(Config.get().getTheme() == Theme.DARK) {
|
|
||||||
url = AppServices.class.getResource("/image/bitcoin-character-invert.svg");
|
|
||||||
} else {
|
|
||||||
url = AppServices.class.getResource("/image/bitcoin-character.svg");
|
|
||||||
}
|
|
||||||
if(url != null) {
|
|
||||||
SVGImage svgImage = SVGLoader.load(url);
|
|
||||||
HBox hBox = new HBox();
|
|
||||||
hBox.setAlignment(Pos.CENTER);
|
|
||||||
hBox.getChildren().add(svgImage);
|
|
||||||
hBox.setPadding(new Insets(0, 2, 0, 4));
|
|
||||||
return hBox;
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
log.error("Could not load bitcoin character");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Glyph getNfcCardGlyph() {
|
public static Glyph getNfcCardGlyph() {
|
||||||
Glyph nfcCardGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WIFI);
|
Glyph nfcCardGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WIFI);
|
||||||
nfcCardGlyph.getStyleClass().add("nfccard-icon");
|
nfcCardGlyph.getStyleClass().add("nfccard-icon");
|
||||||
|
|
@ -922,23 +723,4 @@ public class PaymentController extends WalletFormController implements Initializ
|
||||||
public void openWallets(OpenWalletsEvent event) {
|
public void openWallets(OpenWalletsEvent event) {
|
||||||
updateOpenWallets(event.getWallets());
|
updateOpenWallets(event.getWallets());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DnsPaymentService extends Service<Optional<DnsPayment>> {
|
|
||||||
private final String hrn;
|
|
||||||
|
|
||||||
public DnsPaymentService(String hrn) {
|
|
||||||
this.hrn = hrn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Task<Optional<DnsPayment>> createTask() {
|
|
||||||
return new Task<>() {
|
|
||||||
@Override
|
|
||||||
protected Optional<DnsPayment> call() throws Exception {
|
|
||||||
DnsPaymentResolver resolver = new DnsPaymentResolver(hrn);
|
|
||||||
return resolver.resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.Network;
|
import com.sparrowwallet.drongo.Network;
|
||||||
import com.sparrowwallet.drongo.SecureString;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import com.sparrowwallet.drongo.address.Address;
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
|
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
||||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||||
import com.sparrowwallet.drongo.bip47.SecretPoint;
|
import com.sparrowwallet.drongo.bip47.SecretPoint;
|
||||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.protocol.*;
|
import com.sparrowwallet.drongo.protocol.*;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
|
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.*;
|
import com.sparrowwallet.sparrow.*;
|
||||||
import com.sparrowwallet.sparrow.control.*;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
|
|
@ -172,7 +172,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
private final Set<WalletNode> excludedChangeNodes = new HashSet<>();
|
private final Set<WalletNode> excludedChangeNodes = new HashSet<>();
|
||||||
|
|
||||||
private final Map<Address, WalletNode> walletAddresses = new HashMap<>();
|
private final Map<Wallet, Map<Address, WalletNode>> addressNodeMap = new HashMap<>();
|
||||||
|
|
||||||
private final ChangeListener<String> feeListener = new ChangeListener<>() {
|
private final ChangeListener<String> feeListener = new ChangeListener<>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -484,6 +484,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
validationSupport.registerValidator(fee, Validator.combine(
|
validationSupport.registerValidator(fee, Validator.combine(
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", userFeeSet.get() && insufficientInputsProperty.get()),
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Inputs", userFeeSet.get() && insufficientInputsProperty.get()),
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee", getFeeValueSats() != null && getFeeValueSats() == 0),
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee Rate", isInsufficientFeeRate())
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Insufficient Fee Rate", isInsufficientFeeRate())
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
@ -605,25 +606,18 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
try {
|
try {
|
||||||
List<Payment> payments = transactionPayments != null ? transactionPayments : getPayments();
|
List<Payment> payments = transactionPayments != null ? transactionPayments : getPayments();
|
||||||
updateOptimizationButtons(payments);
|
updateOptimizationButtons(payments);
|
||||||
if(!userFeeSet.get() || getFeeValueSats() != null) {
|
if(!userFeeSet.get() || (getFeeValueSats() != null && getFeeValueSats() > 0)) {
|
||||||
Wallet wallet = getWalletForm().getWallet();
|
Wallet wallet = getWalletForm().getWallet();
|
||||||
Long userFee = userFeeSet.get() ? getFeeValueSats() : null;
|
Long userFee = userFeeSet.get() ? getFeeValueSats() : null;
|
||||||
double feeRate = getUserFeeRate();
|
double feeRate = getUserFeeRate();
|
||||||
double minRelayFeeRate = AppServices.getMinimumRelayFeeRate();
|
|
||||||
Integer currentBlockHeight = AppServices.getCurrentBlockHeight();
|
Integer currentBlockHeight = AppServices.getCurrentBlockHeight();
|
||||||
boolean groupByAddress = Config.get().isGroupByAddress();
|
boolean groupByAddress = Config.get().isGroupByAddress();
|
||||||
boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs();
|
boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs();
|
||||||
BlockTransaction replacedTransaction = replacedTransactionProperty.get();
|
BlockTransaction replacedTransaction = replacedTransactionProperty.get();
|
||||||
|
|
||||||
//Disable RBF for silent payments, as we can't guarantee RBF won't be attempted on another device without knowledge to recompute the address if necessary
|
walletTransactionService = new WalletTransactionService(addressNodeMap, wallet, getUtxoSelectors(payments), getTxoFilters(),
|
||||||
boolean allowRbf = (replacedTransaction == null || replacedTransaction.getTransaction().isReplaceByFee())
|
|
||||||
&& payments.stream().noneMatch(payment -> payment instanceof SilentPayment);
|
|
||||||
|
|
||||||
TransactionParameters params = new TransactionParameters(getUtxoSelectors(payments), getTxoFilters(),
|
|
||||||
payments, opReturnsList, excludedChangeNodes,
|
payments, opReturnsList, excludedChangeNodes,
|
||||||
feeRate, getMinimumFeeRate(), minRelayFeeRate, userFee,
|
feeRate, getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolOutputs, replacedTransaction);
|
||||||
currentBlockHeight, groupByAddress, includeMempoolOutputs, allowRbf);
|
|
||||||
walletTransactionService = new WalletTransactionService(wallet, params, replacedTransaction);
|
|
||||||
walletTransactionService.setOnSucceeded(event -> {
|
walletTransactionService.setOnSucceeded(event -> {
|
||||||
if(!walletTransactionService.isIgnoreResult()) {
|
if(!walletTransactionService.isIgnoreResult()) {
|
||||||
walletTransactionProperty.setValue(walletTransactionService.getValue());
|
walletTransactionProperty.setValue(walletTransactionService.getValue());
|
||||||
|
|
@ -658,12 +652,12 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
walletTransactionService.start();
|
walletTransactionService.start();
|
||||||
}
|
}
|
||||||
} catch(IllegalStateException e) {
|
} catch(InvalidAddressException | IllegalStateException e) {
|
||||||
walletTransactionProperty.setValue(null);
|
walletTransactionProperty.setValue(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UtxoSelector> getUtxoSelectors(List<Payment> payments) {
|
private List<UtxoSelector> getUtxoSelectors(List<Payment> payments) throws InvalidAddressException {
|
||||||
if(utxoSelectorProperty.get() != null) {
|
if(utxoSelectorProperty.get() != null) {
|
||||||
return List.of(utxoSelectorProperty.get());
|
return List.of(utxoSelectorProperty.get());
|
||||||
}
|
}
|
||||||
|
|
@ -685,14 +679,39 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class WalletTransactionService extends Service<WalletTransaction> {
|
private static class WalletTransactionService extends Service<WalletTransaction> {
|
||||||
|
private final Map<Wallet, Map<Address, WalletNode>> addressNodeMap;
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final TransactionParameters params;
|
private final List<UtxoSelector> utxoSelectors;
|
||||||
|
private final List<TxoFilter> txoFilters;
|
||||||
|
private final List<Payment> payments;
|
||||||
|
private final List<byte[]> opReturns;
|
||||||
|
private final Set<WalletNode> excludedChangeNodes;
|
||||||
|
private final double feeRate;
|
||||||
|
private final double longTermFeeRate;
|
||||||
|
private final Long fee;
|
||||||
|
private final Integer currentBlockHeight;
|
||||||
|
private final boolean groupByAddress;
|
||||||
|
private final boolean includeMempoolOutputs;
|
||||||
private final BlockTransaction replacedTransaction;
|
private final BlockTransaction replacedTransaction;
|
||||||
private boolean ignoreResult;
|
private boolean ignoreResult;
|
||||||
|
|
||||||
public WalletTransactionService(Wallet wallet, TransactionParameters params, BlockTransaction replacedTransaction) {
|
public WalletTransactionService(Map<Wallet, Map<Address, WalletNode>> addressNodeMap,
|
||||||
|
Wallet wallet, List<UtxoSelector> utxoSelectors, List<TxoFilter> txoFilters,
|
||||||
|
List<Payment> payments, List<byte[]> opReturns, Set<WalletNode> excludedChangeNodes,
|
||||||
|
double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolOutputs, BlockTransaction replacedTransaction) {
|
||||||
|
this.addressNodeMap = addressNodeMap;
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.params = params;
|
this.utxoSelectors = utxoSelectors;
|
||||||
|
this.txoFilters = txoFilters;
|
||||||
|
this.payments = payments;
|
||||||
|
this.opReturns = opReturns;
|
||||||
|
this.excludedChangeNodes = excludedChangeNodes;
|
||||||
|
this.feeRate = feeRate;
|
||||||
|
this.longTermFeeRate = longTermFeeRate;
|
||||||
|
this.fee = fee;
|
||||||
|
this.currentBlockHeight = currentBlockHeight;
|
||||||
|
this.groupByAddress = groupByAddress;
|
||||||
|
this.includeMempoolOutputs = includeMempoolOutputs;
|
||||||
this.replacedTransaction = replacedTransaction;
|
this.replacedTransaction = replacedTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -703,17 +722,16 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
try {
|
try {
|
||||||
return getWalletTransaction();
|
return getWalletTransaction();
|
||||||
} catch(InsufficientFundsException e) {
|
} catch(InsufficientFundsException e) {
|
||||||
if(e.getTargetValue() != null && replacedTransaction != null && wallet.isSafeToAddInputsOrOutputs(replacedTransaction)
|
if(e.getTargetValue() != null && replacedTransaction != null && utxoSelectors.size() == 1 && utxoSelectors.get(0) instanceof PresetUtxoSelector presetUtxoSelector) {
|
||||||
&& params.utxoSelectors().size() == 1 && params.utxoSelectors().getFirst() instanceof PresetUtxoSelector presetUtxoSelector) {
|
|
||||||
//Creating RBF transaction - include additional UTXOs if available to pay desired fee
|
//Creating RBF transaction - include additional UTXOs if available to pay desired fee
|
||||||
List<TxoFilter> filters = new ArrayList<>(params.txoFilters());
|
List<TxoFilter> filters = new ArrayList<>(txoFilters);
|
||||||
filters.add(presetUtxoSelector.asExcludeTxoFilter());
|
filters.add(presetUtxoSelector.asExcludeTxoFilter());
|
||||||
List<OutputGroup> outputGroups = wallet.getGroupedUtxos(filters, params.feeRate(), AppServices.getMinimumRelayFeeRate(), Config.get().isGroupByAddress())
|
List<OutputGroup> outputGroups = wallet.getGroupedUtxos(filters, feeRate, AppServices.getMinimumRelayFeeRate(), Config.get().isGroupByAddress())
|
||||||
.stream().filter(outputGroup -> outputGroup.getEffectiveValue() >= 0).collect(Collectors.toList());
|
.stream().filter(outputGroup -> outputGroup.getEffectiveValue() >= 0).collect(Collectors.toList());
|
||||||
Collections.shuffle(outputGroups);
|
Collections.shuffle(outputGroups);
|
||||||
|
|
||||||
while(!outputGroups.isEmpty() && presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum() < e.getTargetValue()) {
|
while(!outputGroups.isEmpty() && presetUtxoSelector.getPresetUtxos().stream().mapToLong(BlockTransactionHashIndex::getValue).sum() < e.getTargetValue()) {
|
||||||
OutputGroup outputGroup = outputGroups.removeFirst();
|
OutputGroup outputGroup = outputGroups.remove(0);
|
||||||
for(BlockTransactionHashIndex utxo : outputGroup.getUtxos()) {
|
for(BlockTransactionHashIndex utxo : outputGroup.getUtxos()) {
|
||||||
presetUtxoSelector.getPresetUtxos().add(utxo);
|
presetUtxoSelector.getPresetUtxos().add(utxo);
|
||||||
}
|
}
|
||||||
|
|
@ -727,12 +745,12 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
}
|
}
|
||||||
|
|
||||||
private WalletTransaction getWalletTransaction() throws InsufficientFundsException {
|
private WalletTransaction getWalletTransaction() throws InsufficientFundsException {
|
||||||
try {
|
updateMessage("Selecting UTXOs...");
|
||||||
updateMessage("Selecting UTXOs...");
|
WalletTransaction walletTransaction = wallet.createWalletTransaction(utxoSelectors, txoFilters, payments, opReturns, excludedChangeNodes,
|
||||||
return wallet.createWalletTransaction(params);
|
feeRate, longTermFeeRate, fee, currentBlockHeight, groupByAddress, includeMempoolOutputs);
|
||||||
} finally {
|
updateMessage("Deriving keys...");
|
||||||
updateMessage("");
|
walletTransaction.updateAddressNodeMap(addressNodeMap, walletTransaction.getWallet());
|
||||||
}
|
return walletTransaction;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -860,7 +878,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
* @return the fee rate to use when constructing a transaction
|
* @return the fee rate to use when constructing a transaction
|
||||||
*/
|
*/
|
||||||
public Double getUserFeeRate() {
|
public Double getUserFeeRate() {
|
||||||
return (userFeeSet.get() ? AppServices.getMinimumRelayFeeRate() : getFeeRate());
|
return (userFeeSet.get() ? Transaction.DEFAULT_MIN_RELAY_FEE : getFeeRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Double getFeeRate() {
|
public Double getFeeRate() {
|
||||||
|
|
@ -924,6 +942,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
private void setFeeRatePriority(Double feeRateAmt) {
|
private void setFeeRatePriority(Double feeRateAmt) {
|
||||||
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
Map<Integer, Double> targetBlocksFeeRates = getTargetBlocksFeeRates();
|
||||||
|
Integer targetBlocks = getTargetBlocks(feeRateAmt);
|
||||||
if(targetBlocksFeeRates.get(Integer.MAX_VALUE) != null) {
|
if(targetBlocksFeeRates.get(Integer.MAX_VALUE) != null) {
|
||||||
Double minFeeRate = targetBlocksFeeRates.get(Integer.MAX_VALUE);
|
Double minFeeRate = targetBlocksFeeRates.get(Integer.MAX_VALUE);
|
||||||
if(minFeeRate > 1.0 && feeRateAmt < minFeeRate) {
|
if(minFeeRate > 1.0 && feeRateAmt < minFeeRate) {
|
||||||
|
|
@ -944,10 +963,9 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer targetBlocks = getTargetBlocks(feeRateAmt);
|
|
||||||
if(targetBlocks != null) {
|
if(targetBlocks != null) {
|
||||||
if(targetBlocks < FeeRatesSource.BLOCKS_IN_HALF_HOUR) {
|
if(targetBlocks < FeeRatesSource.BLOCKS_IN_HALF_HOUR) {
|
||||||
Double maxFeeRate = AppServices.getFeeRatesRange().getLast();
|
Double maxFeeRate = FEE_RATES_RANGE.get(FEE_RATES_RANGE.size() - 1).doubleValue();
|
||||||
Double highestBlocksRate = targetBlocksFeeRates.get(TARGET_BLOCKS_RANGE.get(0));
|
Double highestBlocksRate = targetBlocksFeeRates.get(TARGET_BLOCKS_RANGE.get(0));
|
||||||
if(highestBlocksRate < maxFeeRate && feeRateAmt > (highestBlocksRate + ((maxFeeRate - highestBlocksRate) / 10))) {
|
if(highestBlocksRate < maxFeeRate && feeRateAmt > (highestBlocksRate + ((maxFeeRate - highestBlocksRate) / 10))) {
|
||||||
feeRatePriority.setText("Overpaid");
|
feeRatePriority.setText("Overpaid");
|
||||||
|
|
@ -1097,7 +1115,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
|
|
||||||
paymentCodeProperty.set(null);
|
paymentCodeProperty.set(null);
|
||||||
|
|
||||||
walletAddresses.clear();
|
addressNodeMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public UtxoSelector getUtxoSelector() {
|
public UtxoSelector getUtxoSelector() {
|
||||||
|
|
@ -1175,20 +1193,13 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
WalletTransaction walletTransaction = walletTransactionProperty.get();
|
WalletTransaction walletTransaction = walletTransactionProperty.get();
|
||||||
Set<WalletNode> nodes = new LinkedHashSet<>(walletTransaction.getSelectedUtxos().values());
|
Set<WalletNode> nodes = new LinkedHashSet<>(walletTransaction.getSelectedUtxos().values());
|
||||||
nodes.addAll(walletTransaction.getChangeMap().keySet());
|
nodes.addAll(walletTransaction.getChangeMap().keySet());
|
||||||
nodes.addAll(walletTransaction.getWalletNodePayments().stream().map(WalletNodePayment::getWalletNode).collect(Collectors.toList()));
|
Map<Address, WalletNode> addressNodeMap = walletTransaction.getAddressNodeMap();
|
||||||
|
nodes.addAll(addressNodeMap.values().stream().filter(Objects::nonNull).collect(Collectors.toList()));
|
||||||
|
|
||||||
//All wallet nodes applicable to this transaction are stored so when the subscription status for one is updated, the history for all can be fetched in one atomic update
|
//All wallet nodes applicable to this transaction are stored so when the subscription status for one is updated, the history for all can be fetched in one atomic update
|
||||||
walletForm.addWalletTransactionNodes(nodes);
|
walletForm.addWalletTransactionNodes(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletNode getWalletNode(Address address) {
|
|
||||||
if(walletAddresses.isEmpty()) {
|
|
||||||
walletAddresses.putAll(getWalletForm().getWallet().getWalletAddresses());
|
|
||||||
}
|
|
||||||
|
|
||||||
return walletAddresses.get(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void broadcastNotification(ActionEvent event) {
|
public void broadcastNotification(ActionEvent event) {
|
||||||
Wallet wallet = getWalletForm().getWallet();
|
Wallet wallet = getWalletForm().getWallet();
|
||||||
Storage storage = AppServices.get().getOpenWallets().get(wallet);
|
Storage storage = AppServices.get().getOpenWallets().get(wallet);
|
||||||
|
|
@ -1232,14 +1243,11 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
List<UtxoSelector> utxoSelectors = List.of(new PresetUtxoSelector(walletTransaction.getSelectedUtxos().keySet(), true, false));
|
List<UtxoSelector> utxoSelectors = List.of(new PresetUtxoSelector(walletTransaction.getSelectedUtxos().keySet(), true, false));
|
||||||
Long userFee = userFeeSet.get() ? getFeeValueSats() : null;
|
Long userFee = userFeeSet.get() ? getFeeValueSats() : null;
|
||||||
double feeRate = getUserFeeRate();
|
double feeRate = getUserFeeRate();
|
||||||
Double minRelayFeeRate = AppServices.getMinimumRelayFeeRate();
|
|
||||||
Integer currentBlockHeight = AppServices.getCurrentBlockHeight();
|
Integer currentBlockHeight = AppServices.getCurrentBlockHeight();
|
||||||
boolean groupByAddress = Config.get().isGroupByAddress();
|
boolean groupByAddress = Config.get().isGroupByAddress();
|
||||||
boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs();
|
boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs();
|
||||||
|
|
||||||
TransactionParameters params = new TransactionParameters(utxoSelectors, getTxoFilters(), walletTransaction.getPayments(), List.of(blindedPaymentCode),
|
WalletTransaction finalWalletTx = decryptedWallet.createWalletTransaction(utxoSelectors, getTxoFilters(), walletTransaction.getPayments(), List.of(blindedPaymentCode), excludedChangeNodes, feeRate, getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolOutputs);
|
||||||
excludedChangeNodes, feeRate, getMinimumFeeRate(), minRelayFeeRate, userFee, currentBlockHeight, groupByAddress, includeMempoolOutputs, true);
|
|
||||||
WalletTransaction finalWalletTx = decryptedWallet.createWalletTransaction(params);
|
|
||||||
PSBT psbt = finalWalletTx.createPSBT();
|
PSBT psbt = finalWalletTx.createPSBT();
|
||||||
decryptedWallet.sign(psbt);
|
decryptedWallet.sign(psbt);
|
||||||
decryptedWallet.finalise(psbt);
|
decryptedWallet.finalise(psbt);
|
||||||
|
|
@ -1498,7 +1506,7 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
notificationButton.setVisible(isNotificationTransaction);
|
notificationButton.setVisible(isNotificationTransaction);
|
||||||
notificationButton.setDefaultButton(isNotificationTransaction);
|
notificationButton.setDefaultButton(isNotificationTransaction);
|
||||||
|
|
||||||
setInputFieldsDisabled(!event.allowPaymentChanges(), false);
|
setInputFieldsDisabled(isNotificationTransaction, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1627,26 +1635,18 @@ public class SendController extends WalletFormController implements Initializabl
|
||||||
recentBlocksView.updateFeeRatesSource(event.getFeeRateSource());
|
recentBlocksView.updateFeeRatesSource(event.getFeeRateSource());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void connection(ConnectionEvent event) {
|
|
||||||
if(!Objects.equals(event.getMinimumRelayFeeRate(), event.getPreviousMinimumRelayFeeRate())) {
|
|
||||||
feeRange.updateFeeRange(event.getMinimumRelayFeeRate(), event.getPreviousMinimumRelayFeeRate());
|
|
||||||
updateTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PrivacyAnalysisTooltip extends VBox {
|
private class PrivacyAnalysisTooltip extends VBox {
|
||||||
private final List<Label> analysisLabels = new ArrayList<>();
|
private final List<Label> analysisLabels = new ArrayList<>();
|
||||||
|
|
||||||
public PrivacyAnalysisTooltip(WalletTransaction walletTransaction) {
|
public PrivacyAnalysisTooltip(WalletTransaction walletTransaction) {
|
||||||
List<Payment> payments = walletTransaction.getPayments();
|
List<Payment> payments = walletTransaction.getPayments();
|
||||||
List<Payment> userPayments = payments.stream().filter(payment -> payment.getType() != Payment.Type.FAKE_MIX).collect(Collectors.toList());
|
List<Payment> userPayments = payments.stream().filter(payment -> payment.getType() != Payment.Type.FAKE_MIX).collect(Collectors.toList());
|
||||||
List<WalletNodePayment> walletNodePayments = walletTransaction.getWalletNodePayments();
|
Map<Address, WalletNode> walletAddresses = walletTransaction.getAddressNodeMap();
|
||||||
OptimizationStrategy optimizationStrategy = getPreferredOptimizationStrategy();
|
OptimizationStrategy optimizationStrategy = getPreferredOptimizationStrategy();
|
||||||
boolean fakeMixPresent = payments.stream().anyMatch(payment -> payment.getType() == Payment.Type.FAKE_MIX);
|
boolean fakeMixPresent = payments.stream().anyMatch(payment -> payment.getType() == Payment.Type.FAKE_MIX);
|
||||||
boolean roundPaymentAmounts = userPayments.stream().anyMatch(payment -> payment.getAmount() % 100 == 0);
|
boolean roundPaymentAmounts = userPayments.stream().anyMatch(payment -> payment.getAmount() % 100 == 0);
|
||||||
boolean mixedAddressTypes = userPayments.stream().anyMatch(payment -> payment.getAddress().getScriptType() != getWalletForm().getWallet().getFreshNode(KeyPurpose.RECEIVE).getAddress().getScriptType());
|
boolean mixedAddressTypes = userPayments.stream().anyMatch(payment -> payment.getAddress().getScriptType() != getWalletForm().getWallet().getFreshNode(KeyPurpose.RECEIVE).getAddress().getScriptType());
|
||||||
boolean addressReuse = walletNodePayments.stream().anyMatch(walletNodePayment -> !walletNodePayment.getWalletNode().getTransactionOutputs().isEmpty());
|
boolean addressReuse = userPayments.stream().anyMatch(payment -> walletAddresses.get(payment.getAddress()) != null && !walletAddresses.get(payment.getAddress()).getTransactionOutputs().isEmpty());
|
||||||
boolean payjoinPresent = userPayments.stream().anyMatch(payment -> AppServices.getPayjoinURI(payment.getAddress()) != null);
|
boolean payjoinPresent = userPayments.stream().anyMatch(payment -> AppServices.getPayjoinURI(payment.getAddress()) != null);
|
||||||
|
|
||||||
if(optimizationStrategy == OptimizationStrategy.PRIVACY) {
|
if(optimizationStrategy == OptimizationStrategy.PRIVACY) {
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,8 @@ import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.sparrowwallet.drongo.OutputDescriptor.KEY_ORIGIN_PATTERN;
|
|
||||||
import static com.sparrowwallet.drongo.OutputDescriptor.XPUB_PATTERN;
|
|
||||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||||
import static com.sparrowwallet.sparrow.AppServices.showWarningDialog;
|
import static com.sparrowwallet.sparrow.AppServices.showWarningDialog;
|
||||||
|
|
||||||
|
|
@ -458,26 +455,6 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
AppServices.showWarningDialog("Legacy multisig wallet detected", "Sparrow supports BIP67 compatible multisig wallets only.\n\nThe public keys will be lexicographically sorted, and the output descriptor represented with sortedmulti.");
|
AppServices.showWarningDialog("Legacy multisig wallet detected", "Sparrow supports BIP67 compatible multisig wallets only.\n\nThe public keys will be lexicographically sorted, and the output descriptor represented with sortedmulti.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Matcher matcher = XPUB_PATTERN.matcher(text.get());
|
|
||||||
while(matcher.find()) {
|
|
||||||
String keyDerivationPath = null;
|
|
||||||
if(matcher.group(1) != null) {
|
|
||||||
Matcher keyOriginMatcher = KEY_ORIGIN_PATTERN.matcher(matcher.group(1));
|
|
||||||
if(keyOriginMatcher.matches()) {
|
|
||||||
keyDerivationPath = keyOriginMatcher.group(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String extKey = matcher.group(2);
|
|
||||||
String childDerivationPath = matcher.group(3);
|
|
||||||
|
|
||||||
if(ExtendedKey.Header.getHeaders(Network.get()).stream().anyMatch(header -> header.isPrivateKey() && extKey.startsWith(header.name())) &&
|
|
||||||
(keyDerivationPath != null || (childDerivationPath != null && !(childDerivationPath.equals("/0/*") || childDerivationPath.equals("/1/*") || childDerivationPath.equals("/<0;1>/*"))))) {
|
|
||||||
AppServices.showWarningDialog("Private extended key detected", "Sparrow will convert the provided private key to a public key for use in a watch only wallet.\n\nTo import a private key, use the Master Private Key option when creating a Software Wallet.");
|
|
||||||
} else if(childDerivationPath != null && !(childDerivationPath.endsWith("/0/*") || childDerivationPath.endsWith("/1/*") || childDerivationPath.endsWith("/<0;1>/*"))) {
|
|
||||||
AppServices.showWarningDialog("Non standard child derivation detected", "Sparrow does not support non-BIP32 wallets without standard receive and change chains.\n\nThe provided descriptor will be amended if necessary.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDescriptorText(text.get().replace("\n", ""));
|
setDescriptorText(text.get().replace("\n", ""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,13 @@ open module com.sparrowwallet.sparrow {
|
||||||
requires com.google.gson;
|
requires com.google.gson;
|
||||||
requires org.jdbi.v3.core;
|
requires org.jdbi.v3.core;
|
||||||
requires org.jdbi.v3.sqlobject;
|
requires org.jdbi.v3.sqlobject;
|
||||||
requires io.leangen.geantyref;
|
|
||||||
requires org.flywaydb.core;
|
requires org.flywaydb.core;
|
||||||
requires com.zaxxer.hikari;
|
requires com.zaxxer.hikari;
|
||||||
requires com.h2database;
|
requires com.h2database;
|
||||||
requires com.sparrowwallet.hummingbird;
|
requires com.sparrowwallet.hummingbird;
|
||||||
requires org.fxmisc.flowless;
|
requires org.fxmisc.flowless;
|
||||||
requires openpnp.capture.java;
|
requires openpnp.capture.java;
|
||||||
requires nsmenufx;
|
requires centerdevice.nsmenufx;
|
||||||
requires org.jcommander;
|
requires org.jcommander;
|
||||||
requires jul.to.slf4j;
|
requires jul.to.slf4j;
|
||||||
requires net.sourceforge.javacsv;
|
requires net.sourceforge.javacsv;
|
||||||
|
|
@ -57,5 +56,4 @@ open module com.sparrowwallet.sparrow {
|
||||||
requires com.sparrowwallet.tern;
|
requires com.sparrowwallet.tern;
|
||||||
requires com.sparrowwallet.lark;
|
requires com.sparrowwallet.lark;
|
||||||
requires com.sun.jna;
|
requires com.sun.jna;
|
||||||
requires io.github.doblon8.jzbar;
|
|
||||||
}
|
}
|
||||||
76
src/main/java/net/sourceforge/zbar/Config.java
Normal file
76
src/main/java/net/sourceforge/zbar/Config.java
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Config
|
||||||
|
*
|
||||||
|
* Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder configuration options.
|
||||||
|
*/
|
||||||
|
public class Config {
|
||||||
|
/**
|
||||||
|
* Enable symbology/feature.
|
||||||
|
*/
|
||||||
|
public static final int ENABLE = 0;
|
||||||
|
/**
|
||||||
|
* Enable check digit when optional.
|
||||||
|
*/
|
||||||
|
public static final int ADD_CHECK = 1;
|
||||||
|
/**
|
||||||
|
* Return check digit when present.
|
||||||
|
*/
|
||||||
|
public static final int EMIT_CHECK = 2;
|
||||||
|
/**
|
||||||
|
* Enable full ASCII character set.
|
||||||
|
*/
|
||||||
|
public static final int ASCII = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum data length for valid decode.
|
||||||
|
*/
|
||||||
|
public static final int MIN_LEN = 0x20;
|
||||||
|
/**
|
||||||
|
* Maximum data length for valid decode.
|
||||||
|
*/
|
||||||
|
public static final int MAX_LEN = 0x21;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required video consistency frames.
|
||||||
|
*/
|
||||||
|
public static final int UNCERTAINTY = 0x40;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable scanner to collect position data.
|
||||||
|
*/
|
||||||
|
public static final int POSITION = 0x80;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image scanner vertical scan density.
|
||||||
|
*/
|
||||||
|
public static final int X_DENSITY = 0x100;
|
||||||
|
/**
|
||||||
|
* Image scanner horizontal scan density.
|
||||||
|
*/
|
||||||
|
public static final int Y_DENSITY = 0x101;
|
||||||
|
}
|
||||||
197
src/main/java/net/sourceforge/zbar/Image.java
Normal file
197
src/main/java/net/sourceforge/zbar/Image.java
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Image
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stores image data samples along with associated format and size
|
||||||
|
* metadata.
|
||||||
|
*/
|
||||||
|
public class Image implements Closeable {
|
||||||
|
static {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C pointer to a zbar_symbol_t.
|
||||||
|
*/
|
||||||
|
private long peer;
|
||||||
|
private Object data;
|
||||||
|
|
||||||
|
public Image() {
|
||||||
|
peer = create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image(int width, int height) {
|
||||||
|
this();
|
||||||
|
setSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image(int width, int height, String format) {
|
||||||
|
this();
|
||||||
|
setSize(width, height);
|
||||||
|
setFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image(String format) {
|
||||||
|
this();
|
||||||
|
setFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
Image(long peer) {
|
||||||
|
this.peer = peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an associated peer instance.
|
||||||
|
*/
|
||||||
|
private native long create();
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up native data associated with an instance.
|
||||||
|
*/
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if(peer != 0) {
|
||||||
|
destroy(peer);
|
||||||
|
peer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the associated peer instance.
|
||||||
|
*/
|
||||||
|
private native void destroy(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image format conversion.
|
||||||
|
*
|
||||||
|
* @returns a @em new image with the sample data from the original
|
||||||
|
* image converted to the requested format fourcc. the original
|
||||||
|
* image is unaffected.
|
||||||
|
*/
|
||||||
|
public Image convert(String format) {
|
||||||
|
long newpeer = convert(peer, format);
|
||||||
|
if(newpeer == 0) {
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
return (new Image(newpeer));
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long convert(long peer, String format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the image format fourcc.
|
||||||
|
*/
|
||||||
|
public native String getFormat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the fourcc image format code for image sample data.
|
||||||
|
*/
|
||||||
|
public native void setFormat(String format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a "sequence" (page/frame) number associated with this
|
||||||
|
* image.
|
||||||
|
*/
|
||||||
|
public native int getSequence();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate a "sequence" (page/frame) number with this image.
|
||||||
|
*/
|
||||||
|
public native void setSequence(int seq);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the width of the image.
|
||||||
|
*/
|
||||||
|
public native int getWidth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the height of the image.
|
||||||
|
*/
|
||||||
|
public native int getHeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the size of the image.
|
||||||
|
*/
|
||||||
|
public native int[] getSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the pixel size of the image.
|
||||||
|
*/
|
||||||
|
public native void setSize(int[] size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the pixel size of the image.
|
||||||
|
*/
|
||||||
|
public native void setSize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the crop region of the image.
|
||||||
|
*/
|
||||||
|
public native int[] getCrop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the crop region of the image.
|
||||||
|
*/
|
||||||
|
public native void setCrop(int[] crop);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the crop region of the image.
|
||||||
|
*/
|
||||||
|
public native void setCrop(int x, int y, int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the image sample data.
|
||||||
|
*/
|
||||||
|
public native byte[] getData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify image sample data.
|
||||||
|
*/
|
||||||
|
public native void setData(byte[] data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify image sample data.
|
||||||
|
*/
|
||||||
|
public native void setData(int[] data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the decoded results associated with this image.
|
||||||
|
*/
|
||||||
|
public SymbolSet getSymbols() {
|
||||||
|
return (new SymbolSet(getSymbols(peer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long getSymbols(long peer);
|
||||||
|
|
||||||
|
}
|
||||||
110
src/main/java/net/sourceforge/zbar/ImageScanner.java
Normal file
110
src/main/java/net/sourceforge/zbar/ImageScanner.java
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* ImageScanner
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read barcodes from 2-D images.
|
||||||
|
*/
|
||||||
|
public class ImageScanner implements Closeable {
|
||||||
|
static {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C pointer to a zbar_image_scanner_t.
|
||||||
|
*/
|
||||||
|
private long peer;
|
||||||
|
|
||||||
|
public ImageScanner() {
|
||||||
|
peer = create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an associated peer instance.
|
||||||
|
*/
|
||||||
|
private native long create();
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up native data associated with an instance.
|
||||||
|
*/
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if(peer != 0) {
|
||||||
|
destroy(peer);
|
||||||
|
peer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the associated peer instance.
|
||||||
|
*/
|
||||||
|
private native void destroy(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set config for indicated symbology (0 for all) to specified value.
|
||||||
|
*/
|
||||||
|
public native void setConfig(int symbology, int config, int value) throws IllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse configuration string and apply to image scanner.
|
||||||
|
*/
|
||||||
|
public native void parseConfig(String config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable the inter-image result cache (default disabled).
|
||||||
|
* Mostly useful for scanning video frames, the cache filters duplicate
|
||||||
|
* results from consecutive images, while adding some consistency
|
||||||
|
* checking and hysteresis to the results. Invoking this method also
|
||||||
|
* clears the cache.
|
||||||
|
*/
|
||||||
|
public native void enableCache(boolean enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve decode results for last scanned image.
|
||||||
|
*
|
||||||
|
* @returns the SymbolSet result container
|
||||||
|
*/
|
||||||
|
public SymbolSet getResults() {
|
||||||
|
return (new SymbolSet(getResults(peer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long getResults(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan for symbols in provided Image.
|
||||||
|
* The image format must currently be "Y800" or "GRAY".
|
||||||
|
*
|
||||||
|
* @returns the number of symbols successfully decoded from the image.
|
||||||
|
*/
|
||||||
|
public native int scanImage(Image image);
|
||||||
|
}
|
||||||
44
src/main/java/net/sourceforge/zbar/Modifier.java
Normal file
44
src/main/java/net/sourceforge/zbar/Modifier.java
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Modifier
|
||||||
|
*
|
||||||
|
* Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder symbology modifiers.
|
||||||
|
*/
|
||||||
|
public class Modifier {
|
||||||
|
/**
|
||||||
|
* barcode tagged as GS1 (EAN.UCC) reserved
|
||||||
|
* (eg, FNC1 before first data character).
|
||||||
|
* data may be parsed as a sequence of GS1 AIs
|
||||||
|
*/
|
||||||
|
public static final int GS1 = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* barcode tagged as AIM reserved
|
||||||
|
* (eg, FNC1 after first character or digit pair)
|
||||||
|
*/
|
||||||
|
public static final int AIM = 1;
|
||||||
|
}
|
||||||
52
src/main/java/net/sourceforge/zbar/Orientation.java
Normal file
52
src/main/java/net/sourceforge/zbar/Orientation.java
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Orientation
|
||||||
|
*
|
||||||
|
* Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoded symbol coarse orientation.
|
||||||
|
*/
|
||||||
|
public class Orientation {
|
||||||
|
/**
|
||||||
|
* Unable to determine orientation.
|
||||||
|
*/
|
||||||
|
public static final int UNKNOWN = -1;
|
||||||
|
/**
|
||||||
|
* Upright, read left to right.
|
||||||
|
*/
|
||||||
|
public static final int UP = 0;
|
||||||
|
/**
|
||||||
|
* sideways, read top to bottom
|
||||||
|
*/
|
||||||
|
public static final int RIGHT = 1;
|
||||||
|
/**
|
||||||
|
* upside-down, read right to left
|
||||||
|
*/
|
||||||
|
public static final int DOWN = 2;
|
||||||
|
/**
|
||||||
|
* sideways, read bottom to top
|
||||||
|
*/
|
||||||
|
public static final int LEFT = 3;
|
||||||
|
}
|
||||||
265
src/main/java/net/sourceforge/zbar/Symbol.java
Normal file
265
src/main/java/net/sourceforge/zbar/Symbol.java
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Symbol
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable container for decoded result symbols associated with an image
|
||||||
|
* or a composite symbol.
|
||||||
|
*/
|
||||||
|
public class Symbol implements Closeable {
|
||||||
|
/**
|
||||||
|
* No symbol decoded.
|
||||||
|
*/
|
||||||
|
public static final int NONE = 0;
|
||||||
|
/**
|
||||||
|
* Symbol detected but not decoded.
|
||||||
|
*/
|
||||||
|
public static final int PARTIAL = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EAN-8.
|
||||||
|
*/
|
||||||
|
public static final int EAN8 = 8;
|
||||||
|
/**
|
||||||
|
* UPC-E.
|
||||||
|
*/
|
||||||
|
public static final int UPCE = 9;
|
||||||
|
/**
|
||||||
|
* ISBN-10 (from EAN-13).
|
||||||
|
*/
|
||||||
|
public static final int ISBN10 = 10;
|
||||||
|
/**
|
||||||
|
* UPC-A.
|
||||||
|
*/
|
||||||
|
public static final int UPCA = 12;
|
||||||
|
/**
|
||||||
|
* EAN-13.
|
||||||
|
*/
|
||||||
|
public static final int EAN13 = 13;
|
||||||
|
/**
|
||||||
|
* ISBN-13 (from EAN-13).
|
||||||
|
*/
|
||||||
|
public static final int ISBN13 = 14;
|
||||||
|
/**
|
||||||
|
* Interleaved 2 of 5.
|
||||||
|
*/
|
||||||
|
public static final int I25 = 25;
|
||||||
|
/**
|
||||||
|
* DataBar (RSS-14).
|
||||||
|
*/
|
||||||
|
public static final int DATABAR = 34;
|
||||||
|
/**
|
||||||
|
* DataBar Expanded.
|
||||||
|
*/
|
||||||
|
public static final int DATABAR_EXP = 35;
|
||||||
|
/**
|
||||||
|
* Codabar.
|
||||||
|
*/
|
||||||
|
public static final int CODABAR = 38;
|
||||||
|
/**
|
||||||
|
* Code 39.
|
||||||
|
*/
|
||||||
|
public static final int CODE39 = 39;
|
||||||
|
/**
|
||||||
|
* PDF417.
|
||||||
|
*/
|
||||||
|
public static final int PDF417 = 57;
|
||||||
|
/**
|
||||||
|
* QR Code.
|
||||||
|
*/
|
||||||
|
public static final int QRCODE = 64;
|
||||||
|
/**
|
||||||
|
* Code 93.
|
||||||
|
*/
|
||||||
|
public static final int CODE93 = 93;
|
||||||
|
/**
|
||||||
|
* Code 128.
|
||||||
|
*/
|
||||||
|
public static final int CODE128 = 128;
|
||||||
|
|
||||||
|
static {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C pointer to a zbar_symbol_t.
|
||||||
|
*/
|
||||||
|
private long peer;
|
||||||
|
/**
|
||||||
|
* Cached attributes.
|
||||||
|
*/
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symbols are only created by other package methods.
|
||||||
|
*/
|
||||||
|
Symbol(long peer) {
|
||||||
|
this.peer = peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void init();
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up native data associated with an instance.
|
||||||
|
*/
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if(peer != 0) {
|
||||||
|
destroy(peer);
|
||||||
|
peer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the associated peer instance.
|
||||||
|
*/
|
||||||
|
private native void destroy(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve type of decoded symbol.
|
||||||
|
*/
|
||||||
|
public int getType() {
|
||||||
|
if(type == 0) {
|
||||||
|
type = getType(peer);
|
||||||
|
}
|
||||||
|
return (type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native int getType(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve symbology boolean configs settings used during decode.
|
||||||
|
*/
|
||||||
|
public native int getConfigMask();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve symbology characteristics detected during decode.
|
||||||
|
*/
|
||||||
|
public native int getModifierMask();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve data decoded from symbol as a String.
|
||||||
|
*/
|
||||||
|
public native String getData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve raw data bytes decoded from symbol.
|
||||||
|
*/
|
||||||
|
public native byte[] getDataBytes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a symbol confidence metric. Quality is an unscaled,
|
||||||
|
* relative quantity: larger values are better than smaller
|
||||||
|
* values, where "large" and "small" are application dependent.
|
||||||
|
*/
|
||||||
|
public native int getQuality();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve current cache count. When the cache is enabled for
|
||||||
|
* the image_scanner this provides inter-frame reliability and
|
||||||
|
* redundancy information for video streams.
|
||||||
|
*
|
||||||
|
* @returns < 0 if symbol is still uncertain
|
||||||
|
* @returns 0 if symbol is newly verified
|
||||||
|
* @returns > 0 for duplicate symbols
|
||||||
|
*/
|
||||||
|
public native int getCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an approximate, axis-aligned bounding box for the
|
||||||
|
* symbol.
|
||||||
|
*/
|
||||||
|
public int[] getBounds() {
|
||||||
|
int n = getLocationSize(peer);
|
||||||
|
if(n <= 0) {
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] bounds = new int[4];
|
||||||
|
int xmin = Integer.MAX_VALUE;
|
||||||
|
int xmax = Integer.MIN_VALUE;
|
||||||
|
int ymin = Integer.MAX_VALUE;
|
||||||
|
int ymax = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
for(int i = 0; i < n; i++) {
|
||||||
|
int x = getLocationX(peer, i);
|
||||||
|
if(xmin > x) {
|
||||||
|
xmin = x;
|
||||||
|
}
|
||||||
|
if(xmax < x) {
|
||||||
|
xmax = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int y = getLocationY(peer, i);
|
||||||
|
if(ymin > y) {
|
||||||
|
ymin = y;
|
||||||
|
}
|
||||||
|
if(ymax < y) {
|
||||||
|
ymax = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bounds[0] = xmin;
|
||||||
|
bounds[1] = ymin;
|
||||||
|
bounds[2] = xmax - xmin;
|
||||||
|
bounds[3] = ymax - ymin;
|
||||||
|
return (bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native int getLocationSize(long peer);
|
||||||
|
|
||||||
|
private native int getLocationX(long peer, int idx);
|
||||||
|
|
||||||
|
private native int getLocationY(long peer, int idx);
|
||||||
|
|
||||||
|
public int[] getLocationPoint(int idx) {
|
||||||
|
int[] p = new int[2];
|
||||||
|
p[0] = getLocationX(peer, idx);
|
||||||
|
p[1] = getLocationY(peer, idx);
|
||||||
|
return (p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve general axis-aligned, orientation of decoded
|
||||||
|
* symbol.
|
||||||
|
*/
|
||||||
|
public native int getOrientation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve components of a composite result.
|
||||||
|
*/
|
||||||
|
public SymbolSet getComponents() {
|
||||||
|
return (new SymbolSet(getComponents(peer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long getComponents(long peer);
|
||||||
|
|
||||||
|
native long next();
|
||||||
|
}
|
||||||
75
src/main/java/net/sourceforge/zbar/SymbolIterator.java
Normal file
75
src/main/java/net/sourceforge/zbar/SymbolIterator.java
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* SymbolIterator
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator over a SymbolSet.
|
||||||
|
*/
|
||||||
|
public class SymbolIterator implements java.util.Iterator<Symbol> {
|
||||||
|
/**
|
||||||
|
* Next symbol to be returned by the iterator.
|
||||||
|
*/
|
||||||
|
private Symbol current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SymbolIterators are only created by internal interface methods.
|
||||||
|
*/
|
||||||
|
SymbolIterator(Symbol first) {
|
||||||
|
current = first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the iteration has more elements.
|
||||||
|
*/
|
||||||
|
public boolean hasNext() {
|
||||||
|
return (current != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the next element in the iteration.
|
||||||
|
*/
|
||||||
|
public Symbol next() {
|
||||||
|
if(current == null) {
|
||||||
|
throw (new java.util.NoSuchElementException("access past end of SymbolIterator"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Symbol result = current;
|
||||||
|
long sym = current.next();
|
||||||
|
if(sym != 0) {
|
||||||
|
current = new Symbol(sym);
|
||||||
|
} else {
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raises UnsupportedOperationException.
|
||||||
|
*/
|
||||||
|
public void remove() {
|
||||||
|
throw (new UnsupportedOperationException("SymbolIterator is immutable"));
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/main/java/net/sourceforge/zbar/SymbolSet.java
Normal file
93
src/main/java/net/sourceforge/zbar/SymbolSet.java
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* SymbolSet
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable container for decoded result symbols associated with an image
|
||||||
|
* or a composite symbol.
|
||||||
|
*/
|
||||||
|
public class SymbolSet extends java.util.AbstractCollection<Symbol> implements Closeable {
|
||||||
|
static {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C pointer to a zbar_symbol_set_t.
|
||||||
|
*/
|
||||||
|
private long peer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SymbolSets are only created by other package methods.
|
||||||
|
*/
|
||||||
|
SymbolSet(long peer) {
|
||||||
|
this.peer = peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void init();
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up native data associated with an instance.
|
||||||
|
*/
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if(peer != 0) {
|
||||||
|
destroy(peer);
|
||||||
|
peer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the associated peer instance.
|
||||||
|
*/
|
||||||
|
private native void destroy(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an iterator over the Symbol elements in this collection.
|
||||||
|
*/
|
||||||
|
public java.util.Iterator<Symbol> iterator() {
|
||||||
|
long sym = firstSymbol(peer);
|
||||||
|
if(sym == 0) {
|
||||||
|
return (new SymbolIterator(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new SymbolIterator(new Symbol(sym)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the number of elements in the collection.
|
||||||
|
*/
|
||||||
|
public native int size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve C pointer to first symbol in the set.
|
||||||
|
*/
|
||||||
|
private native long firstSymbol(long peer);
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,29 @@
|
||||||
package com.sparrowwallet.sparrow.io;
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
import io.github.doblon8.jzbar.Config;
|
import com.sparrowwallet.sparrow.net.NativeUtils;
|
||||||
import io.github.doblon8.jzbar.Image;
|
|
||||||
import io.github.doblon8.jzbar.ImageScanner;
|
|
||||||
import io.github.doblon8.jzbar.SymbolType;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBufferByte;
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
public class ZBar {
|
public class ZBar {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ZBar.class);
|
private static final Logger log = LoggerFactory.getLogger(ZBar.class);
|
||||||
|
|
||||||
|
private final static boolean enabled;
|
||||||
|
|
||||||
|
static { // static initializer
|
||||||
|
if(com.sparrowwallet.sparrow.io.Config.get().isUseZbar()) {
|
||||||
|
enabled = loadLibrary();
|
||||||
|
} else {
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isEnabled() {
|
public static boolean isEnabled() {
|
||||||
return com.sparrowwallet.sparrow.io.Config.get().isUseZbar();
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Scan scan(BufferedImage bufferedImage) {
|
public static Scan scan(BufferedImage bufferedImage) {
|
||||||
|
|
@ -33,12 +41,19 @@ public class ZBar {
|
||||||
image.setData(data);
|
image.setData(data);
|
||||||
|
|
||||||
try(ImageScanner scanner = new ImageScanner()) {
|
try(ImageScanner scanner = new ImageScanner()) {
|
||||||
scanner.setConfig(SymbolType.NONE, Config.ENABLE, 0);
|
scanner.setConfig(Symbol.NONE, Config.ENABLE, 0);
|
||||||
scanner.setConfig(SymbolType.QRCODE, Config.ENABLE, 1);
|
scanner.setConfig(Symbol.QRCODE, Config.ENABLE, 1);
|
||||||
int result = scanner.scanImage(image);
|
int result = scanner.scanImage(image);
|
||||||
if(result != 0) {
|
if(result != 0) {
|
||||||
String symbolData = image.getFirstSymbol().getData();
|
try(SymbolSet results = scanner.getResults()) {
|
||||||
return new Scan(getRawBytes(symbolData), symbolData);
|
Scan scan = null;
|
||||||
|
for(Iterator<Symbol> iter = results.iterator(); iter.hasNext(); ) {
|
||||||
|
try(Symbol symbol = iter.next()) {
|
||||||
|
scan = new Scan(getRawBytes(symbol.getData()), symbol.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scan;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,6 +97,31 @@ public class ZBar {
|
||||||
return outputData;
|
return outputData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean loadLibrary() {
|
||||||
|
try {
|
||||||
|
String osName = System.getProperty("os.name");
|
||||||
|
String osArch = System.getProperty("os.arch");
|
||||||
|
if(osName.startsWith("Mac") && osArch.equals("aarch64")) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/osx/aarch64/libzbar.dylib");
|
||||||
|
} else if(osName.startsWith("Mac")) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/osx/x64/libzbar.dylib");
|
||||||
|
} else if(osName.startsWith("Windows")) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/windows/x64/iconv-2.dll");
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/windows/x64/zbar.dll");
|
||||||
|
} else if(osArch.equals("aarch64")) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/linux/aarch64/libzbar.so");
|
||||||
|
} else {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/linux/x64/libzbar.so");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.warn("Could not load ZBar native libraries, disabling. " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] getRawBytes(String str) {
|
private static byte[] getRawBytes(String str) {
|
||||||
char[] chars = str.toCharArray();
|
char[] chars = str.toCharArray();
|
||||||
byte[] bytes = new byte[chars.length];
|
byte[] bytes = new byte[chars.length];
|
||||||
|
|
@ -329,10 +329,6 @@ HorizontalHeaderColumn > TableColumnHeader.column-header.table-column{
|
||||||
-fx-stroke: #696c77;
|
-fx-stroke: #696c77;
|
||||||
}
|
}
|
||||||
|
|
||||||
#blockchainForm #blockStatus {
|
|
||||||
-fx-text-fill: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root .progress-indicator.progress-timer.warn > .determinate-indicator > .indicator {
|
.root .progress-indicator.progress-timer.warn > .determinate-indicator > .indicator {
|
||||||
-fx-background-color: -fx-box-border, radial-gradient(center 50% 50%, radius 50%, #e06c75 70%, derive(-fx-control-inner-background, -9%) 100%);
|
-fx-background-color: -fx-box-border, radial-gradient(center 50% 50%, radius 50%, #e06c75 70%, derive(-fx-control-inner-background, -9%) 100%);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
.id, .fixed-width {
|
.id, .fixed-width {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-separator {
|
.form-separator {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
.virtualized-scroll-pane .code-area, .uneditable-codearea {
|
.virtualized-scroll-pane .code-area, .uneditable-codearea {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
-fx-padding: 4;
|
-fx-padding: 4;
|
||||||
-fx-fill: -fx-text-inner-color;
|
-fx-fill: -fx-text-inner-color;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
|
|
||||||
#transactionDiagram .input-label, #transactionDiagram .recipient-label, #transactionDiagram .change-label, #transactionDiagram .fee-tooltip, #transactionDiagram .transaction-tooltip {
|
#transactionDiagram .input-label, #transactionDiagram .recipient-label, #transactionDiagram .change-label, #transactionDiagram .fee-tooltip, #transactionDiagram .transaction-tooltip {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
}
|
}
|
||||||
|
|
||||||
#transactionDiagram .fee-warning-icon {
|
#transactionDiagram .fee-warning-icon {
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,7 @@
|
||||||
<ColumnConstraints percentWidth="50" />
|
<ColumnConstraints percentWidth="50" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
<rowConstraints>
|
<rowConstraints>
|
||||||
<RowConstraints />
|
<RowConstraints />
|
||||||
<RowConstraints />
|
|
||||||
<RowConstraints vgrow="SOMETIMES" />
|
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<Form GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnSpan="2">
|
<Form GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnSpan="2">
|
||||||
<Fieldset text="Transaction" inputGrow="SOMETIMES" wrapWidth="620">
|
<Fieldset text="Transaction" inputGrow="SOMETIMES" wrapWidth="620">
|
||||||
|
|
@ -76,11 +74,9 @@
|
||||||
|
|
||||||
<TabPane side="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="2" styleClass="headers-tabs">
|
<TabPane side="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="2" styleClass="headers-tabs">
|
||||||
<Tab text="Overview" closable="false">
|
<Tab text="Overview" closable="false">
|
||||||
<VBox spacing="8" alignment="CENTER">
|
<VBox spacing="8">
|
||||||
<Region VBox.vgrow="SOMETIMES" />
|
<TransactionDiagram fx:id="transactionDiagram" maxWidth="700" final="true"/>
|
||||||
<TransactionDiagram fx:id="transactionDiagram" final="true"/>
|
<TransactionDiagramLabel fx:id="transactionDiagramLabel" maxWidth="640" prefWidth="640" />
|
||||||
<TransactionDiagramLabel fx:id="transactionDiagramLabel" />
|
|
||||||
<Region VBox.vgrow="SOMETIMES" />
|
|
||||||
</VBox>
|
</VBox>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab text="Detail" closable="false">
|
<Tab text="Detail" closable="false">
|
||||||
|
|
@ -180,44 +176,19 @@
|
||||||
|
|
||||||
<Separator GridPane.columnIndex="0" GridPane.rowIndex="5" GridPane.columnSpan="2" styleClass="form-separator"/>
|
<Separator GridPane.columnIndex="0" GridPane.rowIndex="5" GridPane.columnSpan="2" styleClass="form-separator"/>
|
||||||
|
|
||||||
<GridPane GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="6">
|
<DynamicForm fx:id="blockchainForm" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="6">
|
||||||
<columnConstraints>
|
<Fieldset text="Blockchain" inputGrow="SOMETIMES">
|
||||||
<ColumnConstraints percentWidth="80" />
|
<Field text="Status:">
|
||||||
<ColumnConstraints percentWidth="20" />
|
<Label fx:id="blockStatus" contentDisplay="RIGHT" graphicTextGap="5" />
|
||||||
</columnConstraints>
|
</Field>
|
||||||
<DynamicForm fx:id="blockchainForm" GridPane.columnIndex="0" GridPane.rowIndex="0">
|
<Field fx:id="blockHeightField" text="Block Height:">
|
||||||
<Fieldset text="Blockchain" inputGrow="SOMETIMES">
|
<CopyableLabel fx:id="blockHeight" />
|
||||||
<Field text="Status:">
|
</Field>
|
||||||
<Label fx:id="blockStatus" contentDisplay="RIGHT" graphicTextGap="5" />
|
<Field fx:id="blockTimestampField" text="Timestamp:">
|
||||||
</Field>
|
<CopyableLabel fx:id="blockTimestamp" />
|
||||||
<Field fx:id="blockHeightField" text="Block Height:">
|
</Field>
|
||||||
<CopyableLabel fx:id="blockHeight" />
|
</Fieldset>
|
||||||
</Field>
|
</DynamicForm>
|
||||||
<Field fx:id="blockTimestampField" text="Timestamp:">
|
|
||||||
<CopyableLabel fx:id="blockTimestamp" />
|
|
||||||
</Field>
|
|
||||||
<Field fx:id="signedByField" text="Signed by:">
|
|
||||||
<CopyableLabel fx:id="signedBy" />
|
|
||||||
</Field>
|
|
||||||
</Fieldset>
|
|
||||||
</DynamicForm>
|
|
||||||
<Form fx:id="blockchainSpacerForm" GridPane.columnIndex="1" GridPane.rowIndex="0" visible="false">
|
|
||||||
<Fieldset text="Spacer" inputGrow="SOMETIMES">
|
|
||||||
<VBox>
|
|
||||||
<ProgressBar styleClass="signatures-progress-bar" maxWidth="Infinity" minHeight="50" prefHeight="50" progress="0" />
|
|
||||||
</VBox>
|
|
||||||
<VBox>
|
|
||||||
<HBox styleClass="signatures-buttons" spacing="20">
|
|
||||||
<Button HBox.hgrow="ALWAYS" textAlignment="CENTER" text="Spacer" contentDisplay="TOP" wrapText="true">
|
|
||||||
<graphic>
|
|
||||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="SEARCH" />
|
|
||||||
</graphic>
|
|
||||||
</Button>
|
|
||||||
</HBox>
|
|
||||||
</VBox>
|
|
||||||
</Fieldset>
|
|
||||||
</Form>
|
|
||||||
</GridPane>
|
|
||||||
|
|
||||||
<Form fx:id="signingWalletForm" GridPane.columnIndex="0" GridPane.rowIndex="6">
|
<Form fx:id="signingWalletForm" GridPane.columnIndex="0" GridPane.rowIndex="6">
|
||||||
<Fieldset text="Signatures" inputGrow="SOMETIMES" styleClass="relaxedLabelFieldSet">
|
<Fieldset text="Signatures" inputGrow="SOMETIMES" styleClass="relaxedLabelFieldSet">
|
||||||
|
|
@ -265,7 +236,7 @@
|
||||||
<Fieldset text="Signatures" inputGrow="SOMETIMES">
|
<Fieldset text="Signatures" inputGrow="SOMETIMES">
|
||||||
<VBox>
|
<VBox>
|
||||||
<SignaturesProgressBar fx:id="signaturesProgressBar" />
|
<SignaturesProgressBar fx:id="signaturesProgressBar" />
|
||||||
<ProgressBar fx:id="broadcastProgressBar" maxWidth="Infinity" minHeight="50" prefHeight="50" />
|
<ProgressBar fx:id="broadcastProgressBar" maxWidth="Infinity" prefHeight="50" />
|
||||||
</VBox>
|
</VBox>
|
||||||
<VBox>
|
<VBox>
|
||||||
<HBox fx:id="signButtonBox" styleClass="signatures-buttons" spacing="20">
|
<HBox fx:id="signButtonBox" styleClass="signatures-buttons" spacing="20">
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
.chart-legend-item {
|
.chart-legend-item {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
}
|
}
|
||||||
|
|
||||||
.default-color0.chart-pie { -fx-pie-color: #ca1243 }
|
.default-color0.chart-pie { -fx-pie-color: #ca1243 }
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
.chart-legend-item {
|
.chart-legend-item {
|
||||||
-fx-font-size: 13;
|
-fx-font-size: 13;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
}
|
}
|
||||||
|
|
||||||
.default-color7.chart-pie { -fx-pie-color: #0184bc }
|
.default-color7.chart-pie { -fx-pie-color: #0184bc }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#txhex {
|
#txhex {
|
||||||
-fx-background-color: -fx-control-inner-background;
|
-fx-background-color: -fx-control-inner-background;
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
-fx-padding: 2;
|
-fx-padding: 2;
|
||||||
color-0: #ca1243;
|
color-0: #ca1243;
|
||||||
color-1: #d75f00;
|
color-1: #d75f00;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
#fingerprint, #derivation, #xpub {
|
#fingerprint, #derivation, #xpub {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
}
|
}
|
||||||
|
|
||||||
#type {
|
#type {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<Insets top="10.0" bottom="10.0" />
|
<Insets top="10.0" bottom="10.0" />
|
||||||
</padding>
|
</padding>
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints prefWidth="410" hgrow="SOMETIMES" />
|
<ColumnConstraints prefWidth="410" />
|
||||||
<ColumnConstraints prefWidth="200" />
|
<ColumnConstraints prefWidth="200" />
|
||||||
<ColumnConstraints prefWidth="105" />
|
<ColumnConstraints prefWidth="105" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
<Insets left="25.0" right="25.0" top="25.0" />
|
<Insets left="25.0" right="25.0" top="25.0" />
|
||||||
</padding>
|
</padding>
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints prefWidth="620" hgrow="SOMETIMES" />
|
<ColumnConstraints prefWidth="620" />
|
||||||
<ColumnConstraints prefWidth="140" />
|
<ColumnConstraints prefWidth="140" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
<rowConstraints>
|
<rowConstraints>
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
|
|
||||||
#transactionDiagram .input-label, #transactionDiagram .recipient-label, #transactionDiagram .change-label, #transactionDiagram .fee-tooltip, #transactionDiagram .transaction-tooltip {
|
#transactionDiagram .input-label, #transactionDiagram .recipient-label, #transactionDiagram .change-label, #transactionDiagram .fee-tooltip, #transactionDiagram .transaction-tooltip {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
}
|
}
|
||||||
|
|
||||||
#transactionDiagram .fee-warning-icon {
|
#transactionDiagram .fee-warning-icon {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
<Insets left="25.0" right="25.0" top="25.0" />
|
<Insets left="25.0" right="25.0" top="25.0" />
|
||||||
</padding>
|
</padding>
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints prefWidth="410" hgrow="SOMETIMES" />
|
<ColumnConstraints prefWidth="410" />
|
||||||
<ColumnConstraints prefWidth="200" />
|
<ColumnConstraints prefWidth="200" />
|
||||||
<ColumnConstraints prefWidth="140" />
|
<ColumnConstraints prefWidth="140" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
|
|
@ -152,9 +152,9 @@
|
||||||
<RecentBlocksView fx:id="recentBlocksView" styleClass="feeRatesChart" AnchorPane.topAnchor="10" AnchorPane.leftAnchor="74" translateY="30" minHeight="135"/>
|
<RecentBlocksView fx:id="recentBlocksView" styleClass="feeRatesChart" AnchorPane.topAnchor="10" AnchorPane.leftAnchor="74" translateY="30" minHeight="135"/>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
<StackPane VBox.vgrow="SOMETIMES">
|
<AnchorPane>
|
||||||
<TransactionDiagram fx:id="transactionDiagram" />
|
<TransactionDiagram fx:id="transactionDiagram" maxWidth="700" AnchorPane.leftAnchor="100" />
|
||||||
</StackPane>
|
</AnchorPane>
|
||||||
</VBox>
|
</VBox>
|
||||||
</center>
|
</center>
|
||||||
<bottom>
|
<bottom>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
.address-cell, .utxo-row.entry-cell {
|
.address-cell, .utxo-row.entry-cell {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell > .hyperlink {
|
.cell > .hyperlink {
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
|
|
||||||
.address-text-field {
|
.address-text-field {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Fragment Mono Regular';
|
-fx-font-family: 'Roboto Mono';
|
||||||
}
|
}
|
||||||
|
|
||||||
.unconfirmed-row {
|
.unconfirmed-row {
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/font/RobotoMono-Italic.ttf
Normal file
BIN
src/main/resources/font/RobotoMono-Italic.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/font/RobotoMono-Regular.ttf
Normal file
BIN
src/main/resources/font/RobotoMono-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="7px" height="13px" viewBox="0 0 7 13" version="1.1">
|
|
||||||
<g id="surface1">
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(255, 255, 255);fill-opacity:1;" d="M 3.84375 1.777344 L 3.867188 1.371094 C 3.382812 1.339844 3.144531 1.339844 2.78125 1.339844 L 0.410156 1.339844 C 0.300781 1.339844 0.195312 1.386719 0.121094 1.460938 C 0.0429688 1.539062 0 1.644531 0 1.75 L 0 11.121094 C 0 11.226562 0.0429688 11.332031 0.121094 11.410156 C 0.195312 11.484375 0.300781 11.527344 0.410156 11.527344 L 3.023438 11.527344 C 3.351562 11.527344 3.625 11.527344 3.984375 11.5 L 3.949219 11.09375 L 3.976562 11.5 C 4.753906 11.457031 5.496094 11.1875 6.054688 10.699219 C 6.335938 10.457031 6.566406 10.15625 6.726562 9.808594 C 6.886719 9.460938 6.976562 9.0625 6.976562 8.632812 C 6.976562 7.921875 6.777344 7.257812 6.34375 6.746094 C 6.125 6.488281 5.855469 6.273438 5.535156 6.109375 C 5.21875 5.945312 4.851562 5.832031 4.445312 5.773438 L 4.386719 6.175781 L 4.449219 6.582031 C 5.097656 6.480469 5.65625 6.183594 6.039062 5.722656 C 6.421875 5.265625 6.625 4.660156 6.621094 3.992188 C 6.621094 3.617188 6.554688 3.261719 6.417969 2.949219 C 6.21875 2.476562 5.867188 2.089844 5.425781 1.824219 C 4.984375 1.558594 4.453125 1.410156 3.871094 1.371094 L 3.867188 1.371094 L 3.84375 1.777344 L 3.8125 2.1875 C 4.453125 2.230469 4.949219 2.433594 5.28125 2.734375 C 5.445312 2.886719 5.574219 3.0625 5.660156 3.269531 C 5.75 3.476562 5.800781 3.714844 5.800781 3.992188 C 5.800781 4.503906 5.652344 4.902344 5.40625 5.199219 C 5.160156 5.492188 4.804688 5.695312 4.324219 5.773438 C 4.121094 5.804688 3.972656 5.976562 3.976562 6.179688 C 3.976562 6.382812 4.125 6.554688 4.324219 6.582031 C 4.65625 6.632812 4.929688 6.71875 5.15625 6.835938 C 5.5 7.015625 5.738281 7.253906 5.902344 7.550781 C 6.066406 7.847656 6.152344 8.210938 6.152344 8.636719 C 6.152344 8.953125 6.089844 9.226562 5.980469 9.464844 C 5.8125 9.824219 5.539062 10.109375 5.183594 10.320312 C 4.828125 10.527344 4.394531 10.65625 3.925781 10.683594 L 3.917969 10.683594 C 3.59375 10.710938 3.351562 10.710938 3.023438 10.710938 L 0.820312 10.710938 L 0.820312 2.160156 L 2.777344 2.160156 C 3.148438 2.160156 3.347656 2.160156 3.816406 2.1875 L 3.84375 1.777344 L 3.8125 2.1875 Z M 3.84375 1.777344 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(255, 255, 255);fill-opacity:1;" d="M 4.460938 5.808594 L 0.5 5.808594 C 0.273438 5.808594 0.0898438 5.988281 0.0898438 6.214844 C 0.0898438 6.441406 0.273438 6.625 0.5 6.625 L 4.460938 6.625 C 4.691406 6.625 4.875 6.441406 4.875 6.214844 C 4.875 5.988281 4.691406 5.808594 4.460938 5.808594 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(255, 255, 255);fill-opacity:1;" d="M 0.96875 0.410156 L 0.96875 1.6875 C 0.96875 1.914062 1.152344 2.097656 1.378906 2.097656 C 1.605469 2.097656 1.789062 1.914062 1.789062 1.6875 L 1.789062 0.410156 C 1.789062 0.183594 1.605469 0 1.378906 0 C 1.152344 0 0.96875 0.183594 0.96875 0.410156 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(255, 255, 255);fill-opacity:1;" d="M 2.820312 0.410156 L 2.820312 1.6875 C 2.820312 1.914062 3.003906 2.097656 3.230469 2.097656 C 3.457031 2.097656 3.640625 1.914062 3.640625 1.6875 L 3.640625 0.410156 C 3.640625 0.183594 3.457031 0 3.230469 0 C 3.003906 0 2.820312 0.183594 2.820312 0.410156 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(255, 255, 255);fill-opacity:1;" d="M 0.851562 11.300781 L 0.851562 12.578125 C 0.851562 12.804688 1.035156 12.988281 1.261719 12.988281 C 1.488281 12.988281 1.671875 12.804688 1.671875 12.578125 L 1.671875 11.300781 C 1.671875 11.074219 1.488281 10.890625 1.261719 10.890625 C 1.035156 10.890625 0.851562 11.074219 0.851562 11.300781 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(255, 255, 255);fill-opacity:1;" d="M 2.699219 11.300781 L 2.699219 12.578125 C 2.699219 12.804688 2.882812 12.988281 3.113281 12.988281 C 3.339844 12.988281 3.523438 12.804688 3.523438 12.578125 L 3.523438 11.300781 C 3.523438 11.074219 3.339844 10.890625 3.113281 10.890625 C 2.886719 10.890625 2.699219 11.074219 2.699219 11.300781 "/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.1 KiB |
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="7px" height="13px" viewBox="0 0 7 13" version="1.1">
|
|
||||||
<g id="surface1">
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,14.117648%,14.117648%);fill-opacity:1;" d="M 3.84375 1.777344 L 3.867188 1.371094 C 3.382812 1.339844 3.144531 1.339844 2.78125 1.339844 L 0.410156 1.339844 C 0.300781 1.339844 0.195312 1.386719 0.121094 1.460938 C 0.0429688 1.539062 0 1.644531 0 1.75 L 0 11.121094 C 0 11.226562 0.0429688 11.332031 0.121094 11.410156 C 0.195312 11.484375 0.300781 11.527344 0.410156 11.527344 L 3.023438 11.527344 C 3.351562 11.527344 3.625 11.527344 3.984375 11.5 L 3.949219 11.09375 L 3.976562 11.5 C 4.753906 11.457031 5.496094 11.1875 6.054688 10.699219 C 6.335938 10.457031 6.566406 10.15625 6.726562 9.808594 C 6.886719 9.460938 6.976562 9.0625 6.976562 8.632812 C 6.976562 7.921875 6.777344 7.257812 6.34375 6.746094 C 6.125 6.488281 5.855469 6.273438 5.535156 6.109375 C 5.21875 5.945312 4.851562 5.832031 4.445312 5.773438 L 4.386719 6.175781 L 4.449219 6.582031 C 5.097656 6.480469 5.65625 6.183594 6.039062 5.722656 C 6.421875 5.265625 6.625 4.660156 6.621094 3.992188 C 6.621094 3.617188 6.554688 3.261719 6.417969 2.949219 C 6.21875 2.476562 5.867188 2.089844 5.425781 1.824219 C 4.984375 1.558594 4.453125 1.410156 3.871094 1.371094 L 3.867188 1.371094 L 3.84375 1.777344 L 3.8125 2.1875 C 4.453125 2.230469 4.949219 2.433594 5.28125 2.734375 C 5.445312 2.886719 5.574219 3.0625 5.660156 3.269531 C 5.75 3.476562 5.800781 3.714844 5.800781 3.992188 C 5.800781 4.503906 5.652344 4.902344 5.40625 5.199219 C 5.160156 5.492188 4.804688 5.695312 4.324219 5.773438 C 4.121094 5.804688 3.972656 5.976562 3.976562 6.179688 C 3.976562 6.382812 4.125 6.554688 4.324219 6.582031 C 4.65625 6.632812 4.929688 6.71875 5.15625 6.835938 C 5.5 7.015625 5.738281 7.253906 5.902344 7.550781 C 6.066406 7.847656 6.152344 8.210938 6.152344 8.636719 C 6.152344 8.953125 6.089844 9.226562 5.980469 9.464844 C 5.8125 9.824219 5.539062 10.109375 5.183594 10.320312 C 4.828125 10.527344 4.394531 10.65625 3.925781 10.683594 L 3.917969 10.683594 C 3.59375 10.710938 3.351562 10.710938 3.023438 10.710938 L 0.820312 10.710938 L 0.820312 2.160156 L 2.777344 2.160156 C 3.148438 2.160156 3.347656 2.160156 3.816406 2.1875 L 3.84375 1.777344 L 3.8125 2.1875 Z M 3.84375 1.777344 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,14.117648%,14.117648%);fill-opacity:1;" d="M 4.460938 5.808594 L 0.5 5.808594 C 0.273438 5.808594 0.0898438 5.988281 0.0898438 6.214844 C 0.0898438 6.441406 0.273438 6.625 0.5 6.625 L 4.460938 6.625 C 4.691406 6.625 4.875 6.441406 4.875 6.214844 C 4.875 5.988281 4.691406 5.808594 4.460938 5.808594 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,14.117648%,14.117648%);fill-opacity:1;" d="M 0.96875 0.410156 L 0.96875 1.6875 C 0.96875 1.914062 1.152344 2.097656 1.378906 2.097656 C 1.605469 2.097656 1.789062 1.914062 1.789062 1.6875 L 1.789062 0.410156 C 1.789062 0.183594 1.605469 0 1.378906 0 C 1.152344 0 0.96875 0.183594 0.96875 0.410156 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,14.117648%,14.117648%);fill-opacity:1;" d="M 2.820312 0.410156 L 2.820312 1.6875 C 2.820312 1.914062 3.003906 2.097656 3.230469 2.097656 C 3.457031 2.097656 3.640625 1.914062 3.640625 1.6875 L 3.640625 0.410156 C 3.640625 0.183594 3.457031 0 3.230469 0 C 3.003906 0 2.820312 0.183594 2.820312 0.410156 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,14.117648%,14.117648%);fill-opacity:1;" d="M 0.851562 11.300781 L 0.851562 12.578125 C 0.851562 12.804688 1.035156 12.988281 1.261719 12.988281 C 1.488281 12.988281 1.671875 12.804688 1.671875 12.578125 L 1.671875 11.300781 C 1.671875 11.074219 1.488281 10.890625 1.261719 10.890625 C 1.035156 10.890625 0.851562 11.074219 0.851562 11.300781 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117648%,14.117648%,14.117648%);fill-opacity:1;" d="M 2.699219 11.300781 L 2.699219 12.578125 C 2.699219 12.804688 2.882812 12.988281 3.113281 12.988281 C 3.339844 12.988281 3.523438 12.804688 3.523438 12.578125 L 3.523438 11.300781 C 3.523438 11.074219 3.339844 10.890625 3.113281 10.890625 C 2.886719 10.890625 2.699219 11.074219 2.699219 11.300781 "/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB |
|
|
@ -36,7 +36,6 @@
|
||||||
<logger name="org.springframework.web.HttpLogging" level="OFF" />
|
<logger name="org.springframework.web.HttpLogging" level="OFF" />
|
||||||
<logger name="org.springframework.web.socket.sockjs.client.SockJsClient" level="OFF" />
|
<logger name="org.springframework.web.socket.sockjs.client.SockJsClient" level="OFF" />
|
||||||
<logger name="org.springframework.web.socket.sockjs.client.DefaultTransportRequest" level="OFF" />
|
<logger name="org.springframework.web.socket.sockjs.client.DefaultTransportRequest" level="OFF" />
|
||||||
<logger name="org.xbill.DNS.dnssec.DnsSecVerifier" level="ERROR" />
|
|
||||||
|
|
||||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
|
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
|
||||||
|
|
||||||
|
|
|
||||||
BIN
src/main/resources/native/linux/aarch64/libzbar.so
Executable file
BIN
src/main/resources/native/linux/aarch64/libzbar.so
Executable file
Binary file not shown.
BIN
src/main/resources/native/linux/x64/libzbar.so
Executable file
BIN
src/main/resources/native/linux/x64/libzbar.so
Executable file
Binary file not shown.
BIN
src/main/resources/native/osx/aarch64/libzbar.dylib
Executable file
BIN
src/main/resources/native/osx/aarch64/libzbar.dylib
Executable file
Binary file not shown.
BIN
src/main/resources/native/osx/x64/libzbar.dylib
Executable file
BIN
src/main/resources/native/osx/x64/libzbar.dylib
Executable file
Binary file not shown.
BIN
src/main/resources/native/windows/x64/iconv-2.dll
Normal file
BIN
src/main/resources/native/windows/x64/iconv-2.dll
Normal file
Binary file not shown.
BIN
src/main/resources/native/windows/x64/zbar.dll
Normal file
BIN
src/main/resources/native/windows/x64/zbar.dll
Normal file
Binary file not shown.
Loading…
Reference in a new issue