@breeztech/breez-sdk-spark 0.18.0-dev4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/breez-sdk-spark.tgz +0 -0
- package/bundler/breez_sdk_spark_wasm.d.ts +162 -0
- package/bundler/breez_sdk_spark_wasm.js +1 -1
- package/bundler/breez_sdk_spark_wasm_bg.js +407 -22
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -5
- package/deno/breez_sdk_spark_wasm.d.ts +162 -0
- package/deno/breez_sdk_spark_wasm.js +407 -22
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -5
- package/nodejs/breez_sdk_spark_wasm.d.ts +162 -0
- package/nodejs/breez_sdk_spark_wasm.js +413 -22
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -5
- package/nodejs/index.mjs +6 -0
- package/nodejs/mysql-token-store/index.cjs +212 -70
- package/nodejs/mysql-tree-store/index.cjs +158 -26
- package/nodejs/postgres-token-store/index.cjs +207 -69
- package/nodejs/postgres-tree-store/index.cjs +157 -31
- package/package.json +1 -1
- package/ssr/index.js +36 -0
- package/web/breez_sdk_spark_wasm.d.ts +187 -5
- package/web/breez_sdk_spark_wasm.js +407 -22
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +25 -5
|
@@ -18,6 +18,8 @@ export const bitcoinchainservicehandle_recommendedFees: (a: number) => any;
|
|
|
18
18
|
export const breezsdk_addContact: (a: number, b: any) => any;
|
|
19
19
|
export const breezsdk_addEventListener: (a: number, b: any) => any;
|
|
20
20
|
export const breezsdk_authorizeLightningAddressTransfer: (a: number, b: any) => any;
|
|
21
|
+
export const breezsdk_buildUnsignedLnurlPayPackage: (a: number, b: any) => any;
|
|
22
|
+
export const breezsdk_buildUnsignedTransferPackage: (a: number, b: any) => any;
|
|
21
23
|
export const breezsdk_buyBitcoin: (a: number, b: any) => any;
|
|
22
24
|
export const breezsdk_checkLightningAddressAvailable: (a: number, b: any) => any;
|
|
23
25
|
export const breezsdk_checkMessage: (a: number, b: any) => any;
|
|
@@ -48,6 +50,8 @@ export const breezsdk_optimizeLeaves: (a: number, b: any) => any;
|
|
|
48
50
|
export const breezsdk_parse: (a: number, b: number, c: number) => any;
|
|
49
51
|
export const breezsdk_prepareLnurlPay: (a: number, b: any) => any;
|
|
50
52
|
export const breezsdk_prepareSendPayment: (a: number, b: any) => any;
|
|
53
|
+
export const breezsdk_publishSignedLnurlPayPackage: (a: number, b: any) => any;
|
|
54
|
+
export const breezsdk_publishSignedTransferPackage: (a: number, b: any) => any;
|
|
51
55
|
export const breezsdk_receivePayment: (a: number, b: any) => any;
|
|
52
56
|
export const breezsdk_recommendedFees: (a: number) => any;
|
|
53
57
|
export const breezsdk_refundDeposit: (a: number, b: any) => any;
|
|
@@ -63,13 +67,18 @@ export const breezsdk_updateContact: (a: number, b: any) => any;
|
|
|
63
67
|
export const breezsdk_updateUserSettings: (a: number, b: any) => any;
|
|
64
68
|
export const connect: (a: any) => any;
|
|
65
69
|
export const connectWithSigner: (a: any, b: any, c: any, d: number, e: number) => any;
|
|
70
|
+
export const connectWithSigningOnlySigner: (a: any, b: any, c: any, d: number, e: number) => any;
|
|
66
71
|
export const createTurnkeySigner: (a: any) => any;
|
|
72
|
+
export const createTurnkeySigningOnlySigner: (a: any) => any;
|
|
67
73
|
export const defaultConfig: (a: any) => any;
|
|
68
74
|
export const defaultExternalSigners: (a: number, b: number, c: number, d: number, e: any, f: number) => [number, number, number];
|
|
69
75
|
export const defaultMysqlStorageConfig: (a: number, b: number) => any;
|
|
70
76
|
export const defaultPostgresStorageConfig: (a: number, b: number) => any;
|
|
71
77
|
export const defaultServerConfig: (a: any) => any;
|
|
78
|
+
export const defaultSessionStore: (a: number, b: any, c: number, d: number) => any;
|
|
72
79
|
export const defaultStorage: (a: number, b: number) => number;
|
|
80
|
+
export const defaultsessionstore_getSession: (a: number, b: number, c: number) => any;
|
|
81
|
+
export const defaultsessionstore_setSession: (a: number, b: number, c: number, d: any) => any;
|
|
73
82
|
export const externalbreezsignerhandle_decryptEcies: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
74
83
|
export const externalbreezsignerhandle_derivePublicKey: (a: number, b: number, c: number) => any;
|
|
75
84
|
export const externalbreezsignerhandle_encryptEcies: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
@@ -79,6 +88,10 @@ export const externalbreezsignerhandle_signEcdsaRecoverable: (a: number, b: any,
|
|
|
79
88
|
export const externalbreezsignerhandle_signHashSchnorr: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
80
89
|
export const externalsigners_breezSigner: (a: number) => number;
|
|
81
90
|
export const externalsigners_sparkSigner: (a: number) => number;
|
|
91
|
+
export const externalsigningsignerhandle_derivePublicKey: (a: number, b: number, c: number) => any;
|
|
92
|
+
export const externalsigningsignerhandle_signEcdsa: (a: number, b: any, c: number, d: number) => any;
|
|
93
|
+
export const externalsigningsignerhandle_signEcdsaRecoverable: (a: number, b: any, c: number, d: number) => any;
|
|
94
|
+
export const externalsigningsignerhandle_signHashSchnorr: (a: number, b: number, c: number, d: number, e: number) => any;
|
|
82
95
|
export const externalsparksignerhandle_getIdentityPublicKey: (a: number) => any;
|
|
83
96
|
export const externalsparksignerhandle_getPublicKeyForLeaf: (a: number, b: any) => any;
|
|
84
97
|
export const externalsparksignerhandle_getStaticDepositPublicKey: (a: number, b: number) => any;
|
|
@@ -110,6 +123,7 @@ export const postgresStorage: (a: any) => number;
|
|
|
110
123
|
export const sdkbuilder_build: (a: number) => any;
|
|
111
124
|
export const sdkbuilder_new: (a: any, b: any) => number;
|
|
112
125
|
export const sdkbuilder_newWithSigner: (a: any, b: any, c: any) => number;
|
|
126
|
+
export const sdkbuilder_newWithSigningOnlySigner: (a: any, b: any, c: any) => number;
|
|
113
127
|
export const sdkbuilder_withAccountNumber: (a: number, b: number) => number;
|
|
114
128
|
export const sdkbuilder_withChainService: (a: number, b: any) => number;
|
|
115
129
|
export const sdkbuilder_withDefaultStorage: (a: number, b: number, c: number) => any;
|
|
@@ -119,6 +133,7 @@ export const sdkbuilder_withMysqlBackend: (a: number, b: any) => [number, number
|
|
|
119
133
|
export const sdkbuilder_withPaymentObserver: (a: number, b: any) => number;
|
|
120
134
|
export const sdkbuilder_withPostgresBackend: (a: number, b: any) => [number, number, number];
|
|
121
135
|
export const sdkbuilder_withRestChainService: (a: number, b: number, c: number, d: any, e: number) => number;
|
|
136
|
+
export const sdkbuilder_withSessionStore: (a: number, b: any) => number;
|
|
122
137
|
export const sdkbuilder_withSharedContext: (a: number, b: number) => number;
|
|
123
138
|
export const sdkbuilder_withStorage: (a: number, b: any) => number;
|
|
124
139
|
export const sdkbuilder_withStorageBackend: (a: number, b: number) => number;
|
|
@@ -149,12 +164,17 @@ export const intounderlyingsink_write: (a: number, b: any) => any;
|
|
|
149
164
|
export const intounderlyingsource_cancel: (a: number) => void;
|
|
150
165
|
export const intounderlyingsource_pull: (a: number, b: any) => any;
|
|
151
166
|
export const __wbg_externalbreezsignerhandle_free: (a: number, b: number) => void;
|
|
167
|
+
export const __wbg_defaultsessionstore_free: (a: number, b: number) => void;
|
|
168
|
+
export const __wbg_externalsigningsignerhandle_free: (a: number, b: number) => void;
|
|
152
169
|
export const __wbg_externalsparksignerhandle_free: (a: number, b: number) => void;
|
|
153
|
-
export const
|
|
154
|
-
export const
|
|
155
|
-
export const
|
|
156
|
-
export const
|
|
157
|
-
export const
|
|
170
|
+
export const __wbg_signingonlyexternalsigners_free: (a: number, b: number) => void;
|
|
171
|
+
export const signingonlyexternalsigners_breezSigner: (a: number) => number;
|
|
172
|
+
export const signingonlyexternalsigners_sparkSigner: (a: number) => number;
|
|
173
|
+
export const wasm_bindgen__convert__closures_____invoke__h21f6855640435575: (a: number, b: number, c: any) => [number, number];
|
|
174
|
+
export const wasm_bindgen__convert__closures_____invoke__h21f6855640435575_6: (a: number, b: number, c: any) => [number, number];
|
|
175
|
+
export const wasm_bindgen__convert__closures_____invoke__h21f6855640435575_7: (a: number, b: number, c: any) => [number, number];
|
|
176
|
+
export const wasm_bindgen__convert__closures_____invoke__h21f6855640435575_8: (a: number, b: number, c: any) => [number, number];
|
|
177
|
+
export const wasm_bindgen__convert__closures_____invoke__h21f6855640435575_9: (a: number, b: number, c: any) => [number, number];
|
|
158
178
|
export const wasm_bindgen__convert__closures_____invoke__h41057d61edf43a32: (a: number, b: number, c: any, d: any) => void;
|
|
159
179
|
export const wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2: (a: number, b: number, c: any) => void;
|
|
160
180
|
export const wasm_bindgen__convert__closures_____invoke__h3120db8c4a8a92b2_2: (a: number, b: number, c: any) => void;
|
package/nodejs/index.mjs
CHANGED
|
@@ -6,12 +6,15 @@ import pkg from './index.js';
|
|
|
6
6
|
export const {
|
|
7
7
|
connect,
|
|
8
8
|
connectWithSigner,
|
|
9
|
+
connectWithSigningOnlySigner,
|
|
9
10
|
createTurnkeySigner,
|
|
11
|
+
createTurnkeySigningOnlySigner,
|
|
10
12
|
defaultConfig,
|
|
11
13
|
defaultExternalSigners,
|
|
12
14
|
defaultMysqlStorageConfig,
|
|
13
15
|
defaultPostgresStorageConfig,
|
|
14
16
|
defaultServerConfig,
|
|
17
|
+
defaultSessionStore,
|
|
15
18
|
defaultStorage,
|
|
16
19
|
getSparkStatus,
|
|
17
20
|
initLogging,
|
|
@@ -23,8 +26,10 @@ export const {
|
|
|
23
26
|
task_worker_entry_point,
|
|
24
27
|
BitcoinChainServiceHandle,
|
|
25
28
|
BreezSdk,
|
|
29
|
+
DefaultSessionStore,
|
|
26
30
|
ExternalBreezSignerHandle,
|
|
27
31
|
ExternalSigners,
|
|
32
|
+
ExternalSigningSignerHandle,
|
|
28
33
|
ExternalSparkSignerHandle,
|
|
29
34
|
IntoUnderlyingByteSource,
|
|
30
35
|
IntoUnderlyingSink,
|
|
@@ -32,6 +37,7 @@ export const {
|
|
|
32
37
|
PasskeyClient,
|
|
33
38
|
PasskeyLabels,
|
|
34
39
|
SdkBuilder,
|
|
40
|
+
SigningOnlyExternalSigners,
|
|
35
41
|
TokenIssuer,
|
|
36
42
|
WasmSdkContext,
|
|
37
43
|
WasmStorageConfig,
|
|
@@ -658,7 +658,7 @@ class MysqlTokenStore {
|
|
|
658
658
|
|
|
659
659
|
let outputs = outputRows.map((row) => this._outputFromRow(row));
|
|
660
660
|
|
|
661
|
-
if (preferredOutputs
|
|
661
|
+
if (preferredOutputs) {
|
|
662
662
|
const preferredOutpoints = new Set(
|
|
663
663
|
preferredOutputs.map((p) => `${p.prevTxHash}:${p.prevTxVout}`)
|
|
664
664
|
);
|
|
@@ -667,75 +667,11 @@ class MysqlTokenStore {
|
|
|
667
667
|
);
|
|
668
668
|
}
|
|
669
669
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const totalAvailable = outputs.reduce(
|
|
676
|
-
(sum, o) => sum + BigInt(o.output.tokenAmount),
|
|
677
|
-
0n
|
|
678
|
-
);
|
|
679
|
-
if (totalAvailable < amount) {
|
|
680
|
-
throw new TokenStoreError("InsufficientFunds");
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const exactMatch = outputs.find(
|
|
684
|
-
(o) => BigInt(o.output.tokenAmount) === amount
|
|
685
|
-
);
|
|
686
|
-
if (exactMatch) {
|
|
687
|
-
selectedOutputs = [exactMatch];
|
|
688
|
-
} else {
|
|
689
|
-
if (selectionStrategy === "LargestFirst") {
|
|
690
|
-
outputs.sort(
|
|
691
|
-
(a, b) =>
|
|
692
|
-
Number(
|
|
693
|
-
BigInt(b.output.tokenAmount) - BigInt(a.output.tokenAmount)
|
|
694
|
-
)
|
|
695
|
-
);
|
|
696
|
-
} else {
|
|
697
|
-
outputs.sort(
|
|
698
|
-
(a, b) =>
|
|
699
|
-
Number(
|
|
700
|
-
BigInt(a.output.tokenAmount) - BigInt(b.output.tokenAmount)
|
|
701
|
-
)
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
selectedOutputs = [];
|
|
706
|
-
let remaining = amount;
|
|
707
|
-
for (const output of outputs) {
|
|
708
|
-
if (remaining <= 0n) break;
|
|
709
|
-
selectedOutputs.push(output);
|
|
710
|
-
remaining -= BigInt(output.output.tokenAmount);
|
|
711
|
-
}
|
|
712
|
-
if (remaining > 0n) {
|
|
713
|
-
throw new TokenStoreError("InsufficientFunds");
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
} else if (target.type === "maxOutputCount") {
|
|
717
|
-
const count = target.value;
|
|
718
|
-
|
|
719
|
-
if (selectionStrategy === "LargestFirst") {
|
|
720
|
-
outputs.sort(
|
|
721
|
-
(a, b) =>
|
|
722
|
-
Number(
|
|
723
|
-
BigInt(b.output.tokenAmount) - BigInt(a.output.tokenAmount)
|
|
724
|
-
)
|
|
725
|
-
);
|
|
726
|
-
} else {
|
|
727
|
-
outputs.sort(
|
|
728
|
-
(a, b) =>
|
|
729
|
-
Number(
|
|
730
|
-
BigInt(a.output.tokenAmount) - BigInt(b.output.tokenAmount)
|
|
731
|
-
)
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
selectedOutputs = outputs.slice(0, count);
|
|
736
|
-
} else {
|
|
737
|
-
throw new TokenStoreError(`Unknown target type: ${target.type}`);
|
|
738
|
-
}
|
|
670
|
+
const selectedOutputs = this._selectOutputs(
|
|
671
|
+
outputs,
|
|
672
|
+
target,
|
|
673
|
+
selectionStrategy
|
|
674
|
+
);
|
|
739
675
|
|
|
740
676
|
const reservationId = this._generateId();
|
|
741
677
|
|
|
@@ -773,6 +709,212 @@ class MysqlTokenStore {
|
|
|
773
709
|
}
|
|
774
710
|
}
|
|
775
711
|
|
|
712
|
+
_selectOutputs(outputs, target, selectionStrategy) {
|
|
713
|
+
if (target.type === "minTotalValue") {
|
|
714
|
+
const amount = BigInt(target.value);
|
|
715
|
+
const totalAvailable = outputs.reduce(
|
|
716
|
+
(sum, o) => sum + BigInt(o.output.tokenAmount),
|
|
717
|
+
0n
|
|
718
|
+
);
|
|
719
|
+
if (totalAvailable < amount) {
|
|
720
|
+
throw new TokenStoreError("InsufficientFunds");
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const exactMatch = outputs.find(
|
|
724
|
+
(o) => BigInt(o.output.tokenAmount) === amount
|
|
725
|
+
);
|
|
726
|
+
if (exactMatch) {
|
|
727
|
+
return [exactMatch];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (selectionStrategy === "LargestFirst") {
|
|
731
|
+
outputs.sort(
|
|
732
|
+
(a, b) =>
|
|
733
|
+
Number(BigInt(b.output.tokenAmount) - BigInt(a.output.tokenAmount))
|
|
734
|
+
);
|
|
735
|
+
} else {
|
|
736
|
+
outputs.sort(
|
|
737
|
+
(a, b) =>
|
|
738
|
+
Number(BigInt(a.output.tokenAmount) - BigInt(b.output.tokenAmount))
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const selected = [];
|
|
743
|
+
let remaining = amount;
|
|
744
|
+
for (const output of outputs) {
|
|
745
|
+
if (remaining <= 0n) break;
|
|
746
|
+
selected.push(output);
|
|
747
|
+
remaining -= BigInt(output.output.tokenAmount);
|
|
748
|
+
}
|
|
749
|
+
if (remaining > 0n) {
|
|
750
|
+
throw new TokenStoreError("InsufficientFunds");
|
|
751
|
+
}
|
|
752
|
+
return selected;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (target.type === "maxOutputCount") {
|
|
756
|
+
const count = target.value;
|
|
757
|
+
if (selectionStrategy === "LargestFirst") {
|
|
758
|
+
outputs.sort(
|
|
759
|
+
(a, b) =>
|
|
760
|
+
Number(BigInt(b.output.tokenAmount) - BigInt(a.output.tokenAmount))
|
|
761
|
+
);
|
|
762
|
+
} else {
|
|
763
|
+
outputs.sort(
|
|
764
|
+
(a, b) =>
|
|
765
|
+
Number(BigInt(a.output.tokenAmount) - BigInt(b.output.tokenAmount))
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
return outputs.slice(0, count);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
throw new TokenStoreError(`Unknown target type: ${target.type}`);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
async selectTokenOutputs(
|
|
775
|
+
tokenIdentifier,
|
|
776
|
+
target,
|
|
777
|
+
preferredOutputs,
|
|
778
|
+
selectionStrategy
|
|
779
|
+
) {
|
|
780
|
+
try {
|
|
781
|
+
if (
|
|
782
|
+
target.type === "minTotalValue" &&
|
|
783
|
+
(!target.value || target.value === "0")
|
|
784
|
+
) {
|
|
785
|
+
throw new TokenStoreError("Amount to reserve must be greater than zero");
|
|
786
|
+
}
|
|
787
|
+
if (
|
|
788
|
+
target.type === "maxOutputCount" &&
|
|
789
|
+
(!target.value || target.value === 0)
|
|
790
|
+
) {
|
|
791
|
+
throw new TokenStoreError("Count to reserve must be greater than zero");
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const [metadataRows] = await this.pool.query(
|
|
795
|
+
"SELECT * FROM brz_token_metadata WHERE user_id = ? AND identifier = ?",
|
|
796
|
+
[this.identity, tokenIdentifier]
|
|
797
|
+
);
|
|
798
|
+
if (metadataRows.length === 0) {
|
|
799
|
+
throw new TokenStoreError(
|
|
800
|
+
`Token outputs not found for identifier: ${tokenIdentifier}`
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
const metadata = this._metadataFromRow(metadataRows[0]);
|
|
804
|
+
|
|
805
|
+
const [outputRows] = await this.pool.query(
|
|
806
|
+
`SELECT o.owner_public_key, o.revocation_commitment,
|
|
807
|
+
o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
|
|
808
|
+
o.token_public_key, o.token_amount, o.token_identifier,
|
|
809
|
+
o.prev_tx_hash, o.prev_tx_vout
|
|
810
|
+
FROM brz_token_outputs o
|
|
811
|
+
WHERE o.user_id = ? AND o.token_identifier = ? AND o.reservation_id IS NULL`,
|
|
812
|
+
[this.identity, tokenIdentifier]
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
let outputs = outputRows.map((row) => this._outputFromRow(row));
|
|
816
|
+
|
|
817
|
+
if (preferredOutputs) {
|
|
818
|
+
const preferredOutpoints = new Set(
|
|
819
|
+
preferredOutputs.map((p) => `${p.prevTxHash}:${p.prevTxVout}`)
|
|
820
|
+
);
|
|
821
|
+
outputs = outputs.filter((o) =>
|
|
822
|
+
preferredOutpoints.has(`${o.prevTxHash}:${o.prevTxVout}`)
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const selectedOutputs = this._selectOutputs(
|
|
827
|
+
outputs,
|
|
828
|
+
target,
|
|
829
|
+
selectionStrategy
|
|
830
|
+
);
|
|
831
|
+
return { metadata, outputs: selectedOutputs };
|
|
832
|
+
} catch (error) {
|
|
833
|
+
if (error instanceof TokenStoreError) throw error;
|
|
834
|
+
throw new TokenStoreError(
|
|
835
|
+
`Failed to select token outputs: ${error.message}`,
|
|
836
|
+
error
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async reserveTokenOutputsByOutpoints(tokenIdentifier, outpoints, purpose) {
|
|
842
|
+
try {
|
|
843
|
+
if (!outpoints || outpoints.length === 0) {
|
|
844
|
+
throw new TokenStoreError("No outpoints provided");
|
|
845
|
+
}
|
|
846
|
+
return await this._withWriteTransaction(async (conn) => {
|
|
847
|
+
const [metadataRows] = await conn.query(
|
|
848
|
+
"SELECT * FROM brz_token_metadata WHERE user_id = ? AND identifier = ?",
|
|
849
|
+
[this.identity, tokenIdentifier]
|
|
850
|
+
);
|
|
851
|
+
if (metadataRows.length === 0) {
|
|
852
|
+
throw new TokenStoreError(
|
|
853
|
+
`Token outputs not found for identifier: ${tokenIdentifier}`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
const metadata = this._metadataFromRow(metadataRows[0]);
|
|
857
|
+
|
|
858
|
+
const pairPlaceholders = outpoints.map(() => "(?, ?)").join(", ");
|
|
859
|
+
const selectParams = [this.identity, tokenIdentifier];
|
|
860
|
+
for (const o of outpoints) {
|
|
861
|
+
selectParams.push(o.prevTxHash, o.prevTxVout);
|
|
862
|
+
}
|
|
863
|
+
const [outputRows] = await conn.query(
|
|
864
|
+
`SELECT o.owner_public_key, o.revocation_commitment,
|
|
865
|
+
o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
|
|
866
|
+
o.token_public_key, o.token_amount, o.token_identifier,
|
|
867
|
+
o.prev_tx_hash, o.prev_tx_vout
|
|
868
|
+
FROM brz_token_outputs o
|
|
869
|
+
WHERE o.user_id = ? AND o.token_identifier = ? AND o.reservation_id IS NULL
|
|
870
|
+
AND (o.prev_tx_hash, o.prev_tx_vout) IN (${pairPlaceholders})`,
|
|
871
|
+
selectParams
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
const selectedOutputs = outputRows.map((row) =>
|
|
875
|
+
this._outputFromRow(row)
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
const distinct = new Set(
|
|
879
|
+
outpoints.map((o) => `${o.prevTxHash}:${o.prevTxVout}`)
|
|
880
|
+
);
|
|
881
|
+
if (selectedOutputs.length !== distinct.size) {
|
|
882
|
+
throw new TokenStoreError("InsufficientFunds");
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const reservationId = this._generateId();
|
|
886
|
+
await conn.query(
|
|
887
|
+
"INSERT INTO brz_token_reservations (user_id, id, purpose, created_at) VALUES (?, ?, ?, UTC_TIMESTAMP(6))",
|
|
888
|
+
[this.identity, reservationId, purpose]
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
const updatePlaceholders = selectedOutputs
|
|
892
|
+
.map(() => "(?, ?)")
|
|
893
|
+
.join(", ");
|
|
894
|
+
const updateParams = [reservationId, this.identity];
|
|
895
|
+
for (const o of selectedOutputs) {
|
|
896
|
+
updateParams.push(o.prevTxHash, o.prevTxVout);
|
|
897
|
+
}
|
|
898
|
+
await conn.query(
|
|
899
|
+
`UPDATE brz_token_outputs SET reservation_id = ? WHERE user_id = ?
|
|
900
|
+
AND (prev_tx_hash, prev_tx_vout) IN (${updatePlaceholders})`,
|
|
901
|
+
updateParams
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
return {
|
|
905
|
+
id: reservationId,
|
|
906
|
+
tokenOutputs: { metadata, outputs: selectedOutputs },
|
|
907
|
+
};
|
|
908
|
+
});
|
|
909
|
+
} catch (error) {
|
|
910
|
+
if (error instanceof TokenStoreError) throw error;
|
|
911
|
+
throw new TokenStoreError(
|
|
912
|
+
`Failed to reserve token outputs by outpoints: ${error.message}`,
|
|
913
|
+
error
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
776
918
|
async cancelReservation(id) {
|
|
777
919
|
try {
|
|
778
920
|
await this._withTransaction(async (conn) => {
|
|
@@ -43,6 +43,36 @@ const WRITE_LOCK_TIMEOUT_SECS = 30;
|
|
|
43
43
|
const RESERVATION_TIMEOUT_SECS = 300;
|
|
44
44
|
const SPENT_MARKER_CLEANUP_THRESHOLD_MS = 5 * 60 * 1000;
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Slim projection: only (id, value) for leaves the selection might use.
|
|
48
|
+
* Includes all leaves with value <= the max target (covers exact-match + the
|
|
49
|
+
* small-leaf accumulators for the minimum-amount path) plus the single
|
|
50
|
+
* smallest leaf with a larger value (covers the minimum-amount fallback case
|
|
51
|
+
* where one larger leaf is sufficient).
|
|
52
|
+
* Params: user id, max target, user id, max target.
|
|
53
|
+
*/
|
|
54
|
+
const SLIM_LEAF_CANDIDATES_SQL = `SELECT id, value
|
|
55
|
+
FROM brz_tree_leaves
|
|
56
|
+
WHERE user_id = ?
|
|
57
|
+
AND status = 'Available'
|
|
58
|
+
AND is_missing_from_operators = 0
|
|
59
|
+
AND reservation_id IS NULL
|
|
60
|
+
AND (
|
|
61
|
+
value <= ?
|
|
62
|
+
OR id = (
|
|
63
|
+
SELECT id FROM (
|
|
64
|
+
SELECT id FROM brz_tree_leaves
|
|
65
|
+
WHERE user_id = ?
|
|
66
|
+
AND status = 'Available'
|
|
67
|
+
AND is_missing_from_operators = 0
|
|
68
|
+
AND reservation_id IS NULL
|
|
69
|
+
AND value > ?
|
|
70
|
+
ORDER BY value
|
|
71
|
+
LIMIT 1
|
|
72
|
+
) AS smallest_over
|
|
73
|
+
)
|
|
74
|
+
)`;
|
|
75
|
+
|
|
46
76
|
/**
|
|
47
77
|
* Derive a stable per-tenant lock name from a tenant identity pubkey. Hashes
|
|
48
78
|
* a domain prefix together with the identity (SHA-256, first 8 bytes hex).
|
|
@@ -245,6 +275,32 @@ class MysqlTreeStore {
|
|
|
245
275
|
}
|
|
246
276
|
}
|
|
247
277
|
|
|
278
|
+
async getVerifiedLeafKeys() {
|
|
279
|
+
try {
|
|
280
|
+
// Project just the two pubkeys out of the JSON, skipping each leaf's
|
|
281
|
+
// `data` blob (up to five transactions). The filter matches the verified
|
|
282
|
+
// categories the SDK expects: every reserved leaf plus every Available
|
|
283
|
+
// one, and nothing non-Available and unreserved.
|
|
284
|
+
const [rows] = await this.pool.query(
|
|
285
|
+
`SELECT l.id AS id,
|
|
286
|
+
l.data->>'$.verifying_public_key' AS verifying,
|
|
287
|
+
l.data->>'$.signing_keyshare.public_key' AS keyshare
|
|
288
|
+
FROM brz_tree_leaves l
|
|
289
|
+
LEFT JOIN brz_tree_reservations r
|
|
290
|
+
ON l.reservation_id = r.id AND l.user_id = r.user_id
|
|
291
|
+
WHERE l.user_id = ?
|
|
292
|
+
AND (r.purpose IS NOT NULL OR l.status = 'Available')`,
|
|
293
|
+
[this.identity]
|
|
294
|
+
);
|
|
295
|
+
return rows.map((row) => [row.id, row.verifying, row.keyshare]);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw new TreeStoreError(
|
|
298
|
+
`Failed to get verified leaf keys: ${error.message}`,
|
|
299
|
+
error
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
248
304
|
async getLeaves() {
|
|
249
305
|
try {
|
|
250
306
|
const [rows] = await this.pool.query(
|
|
@@ -462,30 +518,12 @@ class MysqlTreeStore {
|
|
|
462
518
|
);
|
|
463
519
|
const available = Number(totalRows[0].total);
|
|
464
520
|
|
|
465
|
-
const [slimRows] = await conn.query(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
AND reservation_id IS NULL
|
|
472
|
-
AND (
|
|
473
|
-
value <= ?
|
|
474
|
-
OR id = (
|
|
475
|
-
SELECT id FROM (
|
|
476
|
-
SELECT id FROM brz_tree_leaves
|
|
477
|
-
WHERE user_id = ?
|
|
478
|
-
AND status = 'Available'
|
|
479
|
-
AND is_missing_from_operators = 0
|
|
480
|
-
AND reservation_id IS NULL
|
|
481
|
-
AND value > ?
|
|
482
|
-
ORDER BY value
|
|
483
|
-
LIMIT 1
|
|
484
|
-
) AS smallest_over
|
|
485
|
-
)
|
|
486
|
-
)`,
|
|
487
|
-
[this.identity, maxTarget, this.identity, maxTarget]
|
|
488
|
-
);
|
|
521
|
+
const [slimRows] = await conn.query(SLIM_LEAF_CANDIDATES_SQL, [
|
|
522
|
+
this.identity,
|
|
523
|
+
maxTarget,
|
|
524
|
+
this.identity,
|
|
525
|
+
maxTarget,
|
|
526
|
+
]);
|
|
489
527
|
|
|
490
528
|
const slimLeaves = slimRows.map((r) => ({
|
|
491
529
|
id: r.id,
|
|
@@ -579,6 +617,86 @@ class MysqlTreeStore {
|
|
|
579
617
|
}
|
|
580
618
|
}
|
|
581
619
|
|
|
620
|
+
async trySelectLeaves(targetAmounts) {
|
|
621
|
+
try {
|
|
622
|
+
const targetAmount = targetAmounts ? this._totalSats(targetAmounts) : 0;
|
|
623
|
+
const maxTarget = this._maxTargetForPrefilter(targetAmounts);
|
|
624
|
+
|
|
625
|
+
return await this._withTransaction(async (conn) => {
|
|
626
|
+
const [slimRows] = await conn.query(SLIM_LEAF_CANDIDATES_SQL, [
|
|
627
|
+
this.identity,
|
|
628
|
+
maxTarget,
|
|
629
|
+
this.identity,
|
|
630
|
+
maxTarget,
|
|
631
|
+
]);
|
|
632
|
+
|
|
633
|
+
const slimLeaves = slimRows.map((r) => ({
|
|
634
|
+
id: r.id,
|
|
635
|
+
value: Number(r.value),
|
|
636
|
+
}));
|
|
637
|
+
|
|
638
|
+
const selected = this._selectLeavesByTargetAmounts(slimLeaves, targetAmounts);
|
|
639
|
+
if (selected !== null && selected.length > 0) {
|
|
640
|
+
const fullLeaves = await this._fetchFullLeavesByIds(
|
|
641
|
+
conn,
|
|
642
|
+
selected.map((l) => l.id)
|
|
643
|
+
);
|
|
644
|
+
return { type: "exact", leaves: fullLeaves };
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const minSelected = this._selectLeavesByMinimumAmount(slimLeaves, targetAmount);
|
|
648
|
+
if (minSelected !== null) {
|
|
649
|
+
const fullLeaves = await this._fetchFullLeavesByIds(
|
|
650
|
+
conn,
|
|
651
|
+
minSelected.map((l) => l.id)
|
|
652
|
+
);
|
|
653
|
+
return { type: "swapNeeded", leaves: fullLeaves };
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return { type: "insufficientFunds" };
|
|
657
|
+
});
|
|
658
|
+
} catch (error) {
|
|
659
|
+
if (error instanceof TreeStoreError) throw error;
|
|
660
|
+
throw new TreeStoreError(
|
|
661
|
+
`Failed to try select leaves: ${error.message}`,
|
|
662
|
+
error
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async tryReserveLeavesByIds(leafIds, purpose) {
|
|
668
|
+
try {
|
|
669
|
+
return await this._withWriteTransaction(async (conn) => {
|
|
670
|
+
if (!leafIds || leafIds.length === 0) {
|
|
671
|
+
throw new TreeStoreError("NonReservableLeaves");
|
|
672
|
+
}
|
|
673
|
+
// Every requested leaf must be available and unreserved; otherwise
|
|
674
|
+
// reserve nothing (the transaction rolls back).
|
|
675
|
+
const placeholders = leafIds.map(() => "?").join(", ");
|
|
676
|
+
const [availableRows] = await conn.query(
|
|
677
|
+
`SELECT id FROM brz_tree_leaves
|
|
678
|
+
WHERE user_id = ? AND id IN (${placeholders})
|
|
679
|
+
AND status = 'Available' AND is_missing_from_operators = 0
|
|
680
|
+
AND reservation_id IS NULL`,
|
|
681
|
+
[this.identity, ...leafIds]
|
|
682
|
+
);
|
|
683
|
+
if (availableRows.length !== leafIds.length) {
|
|
684
|
+
throw new TreeStoreError("NonReservableLeaves");
|
|
685
|
+
}
|
|
686
|
+
const fullLeaves = await this._fetchFullLeavesByIds(conn, leafIds);
|
|
687
|
+
const reservationId = this._generateId();
|
|
688
|
+
await this._createReservation(conn, reservationId, fullLeaves, purpose, 0);
|
|
689
|
+
return { id: reservationId, leaves: fullLeaves };
|
|
690
|
+
});
|
|
691
|
+
} catch (error) {
|
|
692
|
+
if (error instanceof TreeStoreError) throw error;
|
|
693
|
+
throw new TreeStoreError(
|
|
694
|
+
`Failed to try reserve leaves by ids: ${error.message}`,
|
|
695
|
+
error
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
582
700
|
async now() {
|
|
583
701
|
try {
|
|
584
702
|
const [rows] = await this.pool.query("SELECT UTC_TIMESTAMP(6) AS now");
|
|
@@ -682,10 +800,24 @@ class MysqlTreeStore {
|
|
|
682
800
|
if (!ids || ids.length === 0) return [];
|
|
683
801
|
const placeholders = ids.map(() => "?").join(", ");
|
|
684
802
|
const [rows] = await conn.query(
|
|
685
|
-
`SELECT data FROM brz_tree_leaves WHERE user_id = ? AND id IN (${placeholders})`,
|
|
803
|
+
`SELECT id, data FROM brz_tree_leaves WHERE user_id = ? AND id IN (${placeholders})`,
|
|
686
804
|
[this.identity, ...ids]
|
|
687
805
|
);
|
|
688
|
-
|
|
806
|
+
const byId = new Map(rows.map((r) => [r.id, r.data]));
|
|
807
|
+
const ordered = ids
|
|
808
|
+
.map((id) => {
|
|
809
|
+
const data = byId.get(id);
|
|
810
|
+
byId.delete(id);
|
|
811
|
+
return data;
|
|
812
|
+
})
|
|
813
|
+
.filter((data) => data !== undefined)
|
|
814
|
+
.map((data) => parseJson(data));
|
|
815
|
+
if (ordered.length !== ids.length) {
|
|
816
|
+
throw new TreeStoreError(
|
|
817
|
+
`Could not resolve full data for all selected leaves (wanted ${ids.length}, got ${ordered.length})`
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
return ordered;
|
|
689
821
|
}
|
|
690
822
|
|
|
691
823
|
_selectLeavesByTargetAmounts(leaves, targetAmounts) {
|