@breeztech/breez-sdk-spark 0.18.0 → 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 +96 -0
- package/bundler/breez_sdk_spark_wasm.js +1 -1
- package/bundler/breez_sdk_spark_wasm_bg.js +189 -22
- package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +15 -6
- package/deno/breez_sdk_spark_wasm.d.ts +96 -0
- package/deno/breez_sdk_spark_wasm.js +189 -22
- package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +15 -6
- package/nodejs/breez_sdk_spark_wasm.d.ts +96 -0
- package/nodejs/breez_sdk_spark_wasm.js +191 -22
- package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +15 -6
- package/nodejs/index.mjs +2 -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 +12 -0
- package/web/breez_sdk_spark_wasm.d.ts +111 -6
- package/web/breez_sdk_spark_wasm.js +189 -22
- package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
- package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +15 -6
|
@@ -668,7 +668,7 @@ class PostgresTokenStore {
|
|
|
668
668
|
let outputs = outputRows.rows.map((row) => this._outputFromRow(row));
|
|
669
669
|
|
|
670
670
|
// Filter by preferred if provided
|
|
671
|
-
if (preferredOutputs
|
|
671
|
+
if (preferredOutputs) {
|
|
672
672
|
const preferredOutpoints = new Set(
|
|
673
673
|
preferredOutputs.map((p) => `${p.prevTxHash}:${p.prevTxVout}`)
|
|
674
674
|
);
|
|
@@ -677,74 +677,11 @@ class PostgresTokenStore {
|
|
|
677
677
|
);
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
// Check sufficiency
|
|
687
|
-
const totalAvailable = outputs.reduce(
|
|
688
|
-
(sum, o) => sum + BigInt(o.output.tokenAmount),
|
|
689
|
-
0n
|
|
690
|
-
);
|
|
691
|
-
if (totalAvailable < amount) {
|
|
692
|
-
throw new TokenStoreError("InsufficientFunds");
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// Try exact match first
|
|
696
|
-
const exactMatch = outputs.find(
|
|
697
|
-
(o) => BigInt(o.output.tokenAmount) === amount
|
|
698
|
-
);
|
|
699
|
-
if (exactMatch) {
|
|
700
|
-
selectedOutputs = [exactMatch];
|
|
701
|
-
} else {
|
|
702
|
-
// Sort by selection strategy
|
|
703
|
-
if (selectionStrategy === "LargestFirst") {
|
|
704
|
-
outputs.sort(
|
|
705
|
-
(a, b) =>
|
|
706
|
-
Number(BigInt(b.output.tokenAmount) - BigInt(a.output.tokenAmount))
|
|
707
|
-
);
|
|
708
|
-
} else {
|
|
709
|
-
// Default: SmallestFirst
|
|
710
|
-
outputs.sort(
|
|
711
|
-
(a, b) =>
|
|
712
|
-
Number(BigInt(a.output.tokenAmount) - BigInt(b.output.tokenAmount))
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
selectedOutputs = [];
|
|
717
|
-
let remaining = amount;
|
|
718
|
-
for (const output of outputs) {
|
|
719
|
-
if (remaining <= 0n) break;
|
|
720
|
-
selectedOutputs.push(output);
|
|
721
|
-
remaining -= BigInt(output.output.tokenAmount);
|
|
722
|
-
}
|
|
723
|
-
if (remaining > 0n) {
|
|
724
|
-
throw new TokenStoreError("InsufficientFunds");
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
} else if (target.type === "maxOutputCount") {
|
|
728
|
-
const count = target.value;
|
|
729
|
-
|
|
730
|
-
// Sort by selection strategy
|
|
731
|
-
if (selectionStrategy === "LargestFirst") {
|
|
732
|
-
outputs.sort(
|
|
733
|
-
(a, b) =>
|
|
734
|
-
Number(BigInt(b.output.tokenAmount) - BigInt(a.output.tokenAmount))
|
|
735
|
-
);
|
|
736
|
-
} else {
|
|
737
|
-
// Default: SmallestFirst
|
|
738
|
-
outputs.sort(
|
|
739
|
-
(a, b) =>
|
|
740
|
-
Number(BigInt(a.output.tokenAmount) - BigInt(b.output.tokenAmount))
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
selectedOutputs = outputs.slice(0, count);
|
|
745
|
-
} else {
|
|
746
|
-
throw new TokenStoreError(`Unknown target type: ${target.type}`);
|
|
747
|
-
}
|
|
680
|
+
const selectedOutputs = this._selectOutputs(
|
|
681
|
+
outputs,
|
|
682
|
+
target,
|
|
683
|
+
selectionStrategy
|
|
684
|
+
);
|
|
748
685
|
|
|
749
686
|
// Create reservation
|
|
750
687
|
const reservationId = this._generateId();
|
|
@@ -785,6 +722,207 @@ class PostgresTokenStore {
|
|
|
785
722
|
}
|
|
786
723
|
}
|
|
787
724
|
|
|
725
|
+
_selectOutputs(outputs, target, selectionStrategy) {
|
|
726
|
+
if (target.type === "minTotalValue") {
|
|
727
|
+
const amount = BigInt(target.value);
|
|
728
|
+
const totalAvailable = outputs.reduce(
|
|
729
|
+
(sum, o) => sum + BigInt(o.output.tokenAmount),
|
|
730
|
+
0n
|
|
731
|
+
);
|
|
732
|
+
if (totalAvailable < amount) {
|
|
733
|
+
throw new TokenStoreError("InsufficientFunds");
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const exactMatch = outputs.find(
|
|
737
|
+
(o) => BigInt(o.output.tokenAmount) === amount
|
|
738
|
+
);
|
|
739
|
+
if (exactMatch) {
|
|
740
|
+
return [exactMatch];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (selectionStrategy === "LargestFirst") {
|
|
744
|
+
outputs.sort(
|
|
745
|
+
(a, b) =>
|
|
746
|
+
Number(BigInt(b.output.tokenAmount) - BigInt(a.output.tokenAmount))
|
|
747
|
+
);
|
|
748
|
+
} else {
|
|
749
|
+
outputs.sort(
|
|
750
|
+
(a, b) =>
|
|
751
|
+
Number(BigInt(a.output.tokenAmount) - BigInt(b.output.tokenAmount))
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const selected = [];
|
|
756
|
+
let remaining = amount;
|
|
757
|
+
for (const output of outputs) {
|
|
758
|
+
if (remaining <= 0n) break;
|
|
759
|
+
selected.push(output);
|
|
760
|
+
remaining -= BigInt(output.output.tokenAmount);
|
|
761
|
+
}
|
|
762
|
+
if (remaining > 0n) {
|
|
763
|
+
throw new TokenStoreError("InsufficientFunds");
|
|
764
|
+
}
|
|
765
|
+
return selected;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (target.type === "maxOutputCount") {
|
|
769
|
+
const count = target.value;
|
|
770
|
+
if (selectionStrategy === "LargestFirst") {
|
|
771
|
+
outputs.sort(
|
|
772
|
+
(a, b) =>
|
|
773
|
+
Number(BigInt(b.output.tokenAmount) - BigInt(a.output.tokenAmount))
|
|
774
|
+
);
|
|
775
|
+
} else {
|
|
776
|
+
outputs.sort(
|
|
777
|
+
(a, b) =>
|
|
778
|
+
Number(BigInt(a.output.tokenAmount) - BigInt(b.output.tokenAmount))
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
return outputs.slice(0, count);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
throw new TokenStoreError(`Unknown target type: ${target.type}`);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async selectTokenOutputs(
|
|
788
|
+
tokenIdentifier,
|
|
789
|
+
target,
|
|
790
|
+
preferredOutputs,
|
|
791
|
+
selectionStrategy
|
|
792
|
+
) {
|
|
793
|
+
try {
|
|
794
|
+
if (target.type === "minTotalValue" && (!target.value || target.value === "0")) {
|
|
795
|
+
throw new TokenStoreError("Amount to reserve must be greater than zero");
|
|
796
|
+
}
|
|
797
|
+
if (target.type === "maxOutputCount" && (!target.value || target.value === 0)) {
|
|
798
|
+
throw new TokenStoreError("Count to reserve must be greater than zero");
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const metadataResult = await this.pool.query(
|
|
802
|
+
"SELECT * FROM brz_token_metadata WHERE user_id = $1 AND identifier = $2",
|
|
803
|
+
[this.identity, tokenIdentifier]
|
|
804
|
+
);
|
|
805
|
+
if (metadataResult.rows.length === 0) {
|
|
806
|
+
throw new TokenStoreError(
|
|
807
|
+
`Token outputs not found for identifier: ${tokenIdentifier}`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
const metadata = this._metadataFromRow(metadataResult.rows[0]);
|
|
811
|
+
|
|
812
|
+
const outputRows = await this.pool.query(
|
|
813
|
+
`SELECT o.owner_public_key, o.revocation_commitment,
|
|
814
|
+
o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
|
|
815
|
+
o.token_public_key, o.token_amount, o.token_identifier,
|
|
816
|
+
o.prev_tx_hash, o.prev_tx_vout
|
|
817
|
+
FROM brz_token_outputs o
|
|
818
|
+
WHERE o.user_id = $1
|
|
819
|
+
AND o.token_identifier = $2
|
|
820
|
+
AND o.reservation_id IS NULL`,
|
|
821
|
+
[this.identity, tokenIdentifier]
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
let outputs = outputRows.rows.map((row) => this._outputFromRow(row));
|
|
825
|
+
|
|
826
|
+
if (preferredOutputs) {
|
|
827
|
+
const preferredOutpoints = new Set(
|
|
828
|
+
preferredOutputs.map((p) => `${p.prevTxHash}:${p.prevTxVout}`)
|
|
829
|
+
);
|
|
830
|
+
outputs = outputs.filter((o) =>
|
|
831
|
+
preferredOutpoints.has(`${o.prevTxHash}:${o.prevTxVout}`)
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const selectedOutputs = this._selectOutputs(
|
|
836
|
+
outputs,
|
|
837
|
+
target,
|
|
838
|
+
selectionStrategy
|
|
839
|
+
);
|
|
840
|
+
return { metadata, outputs: selectedOutputs };
|
|
841
|
+
} catch (error) {
|
|
842
|
+
if (error instanceof TokenStoreError) throw error;
|
|
843
|
+
throw new TokenStoreError(
|
|
844
|
+
`Failed to select token outputs: ${error.message}`,
|
|
845
|
+
error
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async reserveTokenOutputsByOutpoints(tokenIdentifier, outpoints, purpose) {
|
|
851
|
+
try {
|
|
852
|
+
if (!outpoints || outpoints.length === 0) {
|
|
853
|
+
throw new TokenStoreError("No outpoints provided");
|
|
854
|
+
}
|
|
855
|
+
return await this._withWriteTransaction(async (client) => {
|
|
856
|
+
const metadataResult = await client.query(
|
|
857
|
+
"SELECT * FROM brz_token_metadata WHERE user_id = $1 AND identifier = $2",
|
|
858
|
+
[this.identity, tokenIdentifier]
|
|
859
|
+
);
|
|
860
|
+
if (metadataResult.rows.length === 0) {
|
|
861
|
+
throw new TokenStoreError(
|
|
862
|
+
`Token outputs not found for identifier: ${tokenIdentifier}`
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
const metadata = this._metadataFromRow(metadataResult.rows[0]);
|
|
866
|
+
|
|
867
|
+
const txHashes = outpoints.map((o) => o.prevTxHash);
|
|
868
|
+
const vouts = outpoints.map((o) => o.prevTxVout);
|
|
869
|
+
const outputRows = await client.query(
|
|
870
|
+
`SELECT o.owner_public_key, o.revocation_commitment,
|
|
871
|
+
o.withdraw_bond_sats, o.withdraw_relative_block_locktime,
|
|
872
|
+
o.token_public_key, o.token_amount, o.token_identifier,
|
|
873
|
+
o.prev_tx_hash, o.prev_tx_vout
|
|
874
|
+
FROM brz_token_outputs o
|
|
875
|
+
WHERE o.user_id = $1
|
|
876
|
+
AND o.token_identifier = $2
|
|
877
|
+
AND o.reservation_id IS NULL
|
|
878
|
+
AND (o.prev_tx_hash, o.prev_tx_vout) IN (
|
|
879
|
+
SELECT * FROM UNNEST($3::text[], $4::int[])
|
|
880
|
+
)`,
|
|
881
|
+
[this.identity, tokenIdentifier, txHashes, vouts]
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
const selectedOutputs = outputRows.rows.map((row) =>
|
|
885
|
+
this._outputFromRow(row)
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
const distinct = new Set(
|
|
889
|
+
outpoints.map((o) => `${o.prevTxHash}:${o.prevTxVout}`)
|
|
890
|
+
);
|
|
891
|
+
if (selectedOutputs.length !== distinct.size) {
|
|
892
|
+
throw new TokenStoreError("InsufficientFunds");
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const reservationId = this._generateId();
|
|
896
|
+
await client.query(
|
|
897
|
+
"INSERT INTO brz_token_reservations (user_id, id, purpose) VALUES ($1, $2, $3)",
|
|
898
|
+
[this.identity, reservationId, purpose]
|
|
899
|
+
);
|
|
900
|
+
await client.query(
|
|
901
|
+
`UPDATE brz_token_outputs SET reservation_id = $1
|
|
902
|
+
WHERE user_id = $4
|
|
903
|
+
AND (prev_tx_hash, prev_tx_vout) IN (
|
|
904
|
+
SELECT * FROM UNNEST($2::text[], $3::int[])
|
|
905
|
+
)`,
|
|
906
|
+
[reservationId, txHashes, vouts, this.identity]
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
return {
|
|
910
|
+
id: reservationId,
|
|
911
|
+
tokenOutputs: {
|
|
912
|
+
metadata,
|
|
913
|
+
outputs: selectedOutputs,
|
|
914
|
+
},
|
|
915
|
+
};
|
|
916
|
+
});
|
|
917
|
+
} catch (error) {
|
|
918
|
+
if (error instanceof TokenStoreError) throw error;
|
|
919
|
+
throw new TokenStoreError(
|
|
920
|
+
`Failed to reserve token outputs by outpoints: ${error.message}`,
|
|
921
|
+
error
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
788
926
|
/**
|
|
789
927
|
* Cancel a reservation, releasing reserved outputs.
|
|
790
928
|
* @param {string} id - Reservation ID
|
|
@@ -40,6 +40,35 @@ const RESERVATION_TIMEOUT_SECS = 300; // 5 minutes
|
|
|
40
40
|
*/
|
|
41
41
|
const SPENT_MARKER_CLEANUP_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Slim projection: only (id, value) for leaves the selection might use.
|
|
45
|
+
* Includes all leaves with value <= $2 (covers exact-match + the small-leaf
|
|
46
|
+
* accumulators for the minimum-amount path) plus the single smallest leaf
|
|
47
|
+
* with value > $2 (covers the minimum-amount fallback case where one larger
|
|
48
|
+
* leaf is sufficient). $1 is the user id.
|
|
49
|
+
*/
|
|
50
|
+
const SLIM_LEAF_CANDIDATES_SQL = `
|
|
51
|
+
SELECT id, (data->>'value')::bigint AS value
|
|
52
|
+
FROM brz_tree_leaves
|
|
53
|
+
WHERE user_id = $1
|
|
54
|
+
AND status = 'Available'
|
|
55
|
+
AND is_missing_from_operators = FALSE
|
|
56
|
+
AND reservation_id IS NULL
|
|
57
|
+
AND (
|
|
58
|
+
(data->>'value')::bigint <= $2
|
|
59
|
+
OR id = (
|
|
60
|
+
SELECT id FROM brz_tree_leaves
|
|
61
|
+
WHERE user_id = $1
|
|
62
|
+
AND status = 'Available'
|
|
63
|
+
AND is_missing_from_operators = FALSE
|
|
64
|
+
AND reservation_id IS NULL
|
|
65
|
+
AND (data->>'value')::bigint > $2
|
|
66
|
+
ORDER BY (data->>'value')::bigint
|
|
67
|
+
LIMIT 1
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
`;
|
|
71
|
+
|
|
43
72
|
/**
|
|
44
73
|
* Derive a stable per-tenant 64-bit advisory-lock key by hashing a domain
|
|
45
74
|
* prefix together with the identity pubkey and folding the first 8 bytes of
|
|
@@ -217,6 +246,34 @@ class PostgresTreeStore {
|
|
|
217
246
|
}
|
|
218
247
|
}
|
|
219
248
|
|
|
249
|
+
async getVerifiedLeafKeys() {
|
|
250
|
+
try {
|
|
251
|
+
// Project just the two pubkeys out of the JSON, skipping each leaf's
|
|
252
|
+
// `data` blob (up to five transactions). The filter matches the verified
|
|
253
|
+
// categories the SDK expects: every reserved leaf plus every Available
|
|
254
|
+
// one, and nothing non-Available and unreserved.
|
|
255
|
+
const result = await this.pool.query(
|
|
256
|
+
`
|
|
257
|
+
SELECT l.id AS id,
|
|
258
|
+
l.data->>'verifying_public_key' AS verifying,
|
|
259
|
+
l.data->'signing_keyshare'->>'public_key' AS keyshare
|
|
260
|
+
FROM brz_tree_leaves l
|
|
261
|
+
LEFT JOIN brz_tree_reservations r
|
|
262
|
+
ON l.reservation_id = r.id AND l.user_id = r.user_id
|
|
263
|
+
WHERE l.user_id = $1
|
|
264
|
+
AND (r.purpose IS NOT NULL OR l.status = 'Available')
|
|
265
|
+
`,
|
|
266
|
+
[this.identity]
|
|
267
|
+
);
|
|
268
|
+
return result.rows.map((row) => [row.id, row.verifying, row.keyshare]);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
throw new TreeStoreError(
|
|
271
|
+
`Failed to get verified leaf keys: ${error.message}`,
|
|
272
|
+
error
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
220
277
|
async getLeaves() {
|
|
221
278
|
try {
|
|
222
279
|
const result = await this.pool.query(
|
|
@@ -485,35 +542,10 @@ class PostgresTreeStore {
|
|
|
485
542
|
);
|
|
486
543
|
const available = Number(totalResult.rows[0].total);
|
|
487
544
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// fallback case where one larger leaf is sufficient).
|
|
493
|
-
const slimResult = await client.query(
|
|
494
|
-
`
|
|
495
|
-
SELECT id, (data->>'value')::bigint AS value
|
|
496
|
-
FROM brz_tree_leaves
|
|
497
|
-
WHERE user_id = $1
|
|
498
|
-
AND status = 'Available'
|
|
499
|
-
AND is_missing_from_operators = FALSE
|
|
500
|
-
AND reservation_id IS NULL
|
|
501
|
-
AND (
|
|
502
|
-
(data->>'value')::bigint <= $2
|
|
503
|
-
OR id = (
|
|
504
|
-
SELECT id FROM brz_tree_leaves
|
|
505
|
-
WHERE user_id = $1
|
|
506
|
-
AND status = 'Available'
|
|
507
|
-
AND is_missing_from_operators = FALSE
|
|
508
|
-
AND reservation_id IS NULL
|
|
509
|
-
AND (data->>'value')::bigint > $2
|
|
510
|
-
ORDER BY (data->>'value')::bigint
|
|
511
|
-
LIMIT 1
|
|
512
|
-
)
|
|
513
|
-
)
|
|
514
|
-
`,
|
|
515
|
-
[this.identity, maxTarget]
|
|
516
|
-
);
|
|
545
|
+
const slimResult = await client.query(SLIM_LEAF_CANDIDATES_SQL, [
|
|
546
|
+
this.identity,
|
|
547
|
+
maxTarget,
|
|
548
|
+
]);
|
|
517
549
|
|
|
518
550
|
const slimLeaves = slimResult.rows.map((r) => ({
|
|
519
551
|
id: r.id,
|
|
@@ -594,6 +626,87 @@ class PostgresTreeStore {
|
|
|
594
626
|
}
|
|
595
627
|
}
|
|
596
628
|
|
|
629
|
+
async trySelectLeaves(targetAmounts) {
|
|
630
|
+
try {
|
|
631
|
+
const targetAmount = targetAmounts ? this._totalSats(targetAmounts) : 0;
|
|
632
|
+
const maxTarget = this._maxTargetForPrefilter(targetAmounts);
|
|
633
|
+
|
|
634
|
+
return await this._withTransaction(async (client) => {
|
|
635
|
+
const slimResult = await client.query(SLIM_LEAF_CANDIDATES_SQL, [
|
|
636
|
+
this.identity,
|
|
637
|
+
maxTarget,
|
|
638
|
+
]);
|
|
639
|
+
|
|
640
|
+
const slimLeaves = slimResult.rows.map((r) => ({
|
|
641
|
+
id: r.id,
|
|
642
|
+
value: Number(r.value),
|
|
643
|
+
}));
|
|
644
|
+
|
|
645
|
+
const selected = this._selectLeavesByTargetAmounts(slimLeaves, targetAmounts);
|
|
646
|
+
if (selected !== null && selected.length > 0) {
|
|
647
|
+
const fullLeaves = await this._fetchFullLeavesByIds(
|
|
648
|
+
client,
|
|
649
|
+
selected.map((l) => l.id)
|
|
650
|
+
);
|
|
651
|
+
return { type: "exact", leaves: fullLeaves };
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const minSelected = this._selectLeavesByMinimumAmount(slimLeaves, targetAmount);
|
|
655
|
+
if (minSelected !== null) {
|
|
656
|
+
const fullLeaves = await this._fetchFullLeavesByIds(
|
|
657
|
+
client,
|
|
658
|
+
minSelected.map((l) => l.id)
|
|
659
|
+
);
|
|
660
|
+
return { type: "swapNeeded", leaves: fullLeaves };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return { type: "insufficientFunds" };
|
|
664
|
+
});
|
|
665
|
+
} catch (error) {
|
|
666
|
+
if (error instanceof TreeStoreError) throw error;
|
|
667
|
+
throw new TreeStoreError(
|
|
668
|
+
`Failed to try select leaves: ${error.message}`,
|
|
669
|
+
error
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async tryReserveLeavesByIds(leafIds, purpose) {
|
|
675
|
+
try {
|
|
676
|
+
return await this._withWriteTransaction(async (client) => {
|
|
677
|
+
if (!leafIds || leafIds.length === 0) {
|
|
678
|
+
throw new TreeStoreError("NonReservableLeaves");
|
|
679
|
+
}
|
|
680
|
+
// Every requested leaf must be available and unreserved; otherwise
|
|
681
|
+
// reserve nothing (the transaction rolls back).
|
|
682
|
+
const availableResult = await client.query(
|
|
683
|
+
`
|
|
684
|
+
SELECT id FROM brz_tree_leaves
|
|
685
|
+
WHERE user_id = $1
|
|
686
|
+
AND id = ANY($2)
|
|
687
|
+
AND status = 'Available'
|
|
688
|
+
AND is_missing_from_operators = FALSE
|
|
689
|
+
AND reservation_id IS NULL
|
|
690
|
+
`,
|
|
691
|
+
[this.identity, leafIds]
|
|
692
|
+
);
|
|
693
|
+
if (availableResult.rows.length !== leafIds.length) {
|
|
694
|
+
throw new TreeStoreError("NonReservableLeaves");
|
|
695
|
+
}
|
|
696
|
+
const fullLeaves = await this._fetchFullLeavesByIds(client, leafIds);
|
|
697
|
+
const reservationId = this._generateId();
|
|
698
|
+
await this._createReservation(client, reservationId, fullLeaves, purpose, 0);
|
|
699
|
+
return { id: reservationId, leaves: fullLeaves };
|
|
700
|
+
});
|
|
701
|
+
} catch (error) {
|
|
702
|
+
if (error instanceof TreeStoreError) throw error;
|
|
703
|
+
throw new TreeStoreError(
|
|
704
|
+
`Failed to try reserve leaves by ids: ${error.message}`,
|
|
705
|
+
error
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
597
710
|
_maxTargetForPrefilter(targetAmounts) {
|
|
598
711
|
if (!targetAmounts) return Number.MAX_SAFE_INTEGER;
|
|
599
712
|
if (targetAmounts.type === "amountAndFee") {
|
|
@@ -612,10 +725,23 @@ class PostgresTreeStore {
|
|
|
612
725
|
async _fetchFullLeavesByIds(client, ids) {
|
|
613
726
|
if (!ids || ids.length === 0) return [];
|
|
614
727
|
const result = await client.query(
|
|
615
|
-
"SELECT data FROM brz_tree_leaves WHERE user_id = $2 AND id = ANY($1)",
|
|
728
|
+
"SELECT id, data FROM brz_tree_leaves WHERE user_id = $2 AND id = ANY($1)",
|
|
616
729
|
[ids, this.identity]
|
|
617
730
|
);
|
|
618
|
-
|
|
731
|
+
const byId = new Map(result.rows.map((r) => [r.id, r.data]));
|
|
732
|
+
const ordered = ids
|
|
733
|
+
.map((id) => {
|
|
734
|
+
const data = byId.get(id);
|
|
735
|
+
byId.delete(id);
|
|
736
|
+
return data;
|
|
737
|
+
})
|
|
738
|
+
.filter((data) => data !== undefined);
|
|
739
|
+
if (ordered.length !== ids.length) {
|
|
740
|
+
throw new TreeStoreError(
|
|
741
|
+
`Could not resolve full data for all selected leaves (wanted ${ids.length}, got ${ordered.length})`
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
return ordered;
|
|
619
745
|
}
|
|
620
746
|
|
|
621
747
|
/**
|
package/package.json
CHANGED
package/ssr/index.js
CHANGED
|
@@ -73,6 +73,11 @@ export function defaultServerConfig(...args) {
|
|
|
73
73
|
return _module.defaultServerConfig(...args);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export function defaultSessionStore(...args) {
|
|
77
|
+
if (!_module) _notInitialized('defaultSessionStore');
|
|
78
|
+
return _module.defaultSessionStore(...args);
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
export function defaultStorage(...args) {
|
|
77
82
|
if (!_module) _notInitialized('defaultStorage');
|
|
78
83
|
return _module.defaultStorage(...args);
|
|
@@ -137,6 +142,13 @@ export class BreezSdk {
|
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
|
|
145
|
+
export class DefaultSessionStore {
|
|
146
|
+
constructor(...args) {
|
|
147
|
+
if (!_module) _notInitialized('new DefaultSessionStore');
|
|
148
|
+
return new _module.DefaultSessionStore(...args);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
140
152
|
export class ExternalBreezSignerHandle {
|
|
141
153
|
constructor(...args) {
|
|
142
154
|
if (!_module) _notInitialized('new ExternalBreezSignerHandle');
|