@midnightntwrk/wallet-sdk-capabilities 3.3.1

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/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # @midnightntwrk/wallet-sdk-capabilities
2
+
3
+ Internal wallet capabilities for transaction balancing on the Midnight network.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @midnightntwrk/wallet-sdk-capabilities
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ This package provides core transaction balancing capabilities used internally by wallet implementations. It handles the
14
+ complex logic of selecting inputs and creating outputs to balance transactions while accounting for fee overhead costs.
15
+
16
+ Key features:
17
+
18
+ - Transaction balancing algorithms
19
+ - Coin selection strategies
20
+ - Imbalance tracking and resolution
21
+ - Fee-aware counter-offer generation
22
+
23
+ ## Usage
24
+
25
+ ### Basic Transaction Balancing
26
+
27
+ ```typescript
28
+ import { getBalanceRecipe, Imbalances, chooseCoin } from '@midnightntwrk/wallet-sdk-capabilities';
29
+
30
+ const recipe = getBalanceRecipe({
31
+ coins: availableCoins,
32
+ initialImbalances: Imbalances.fromEntry('NIGHT', -1000n), // Need 1000 NIGHT
33
+ transactionCostModel: {
34
+ inputFeeOverhead: 1000n,
35
+ outputFeeOverhead: 500n,
36
+ },
37
+ feeTokenType: 'DUST',
38
+ createOutput: (coin) => ({ type: coin.type, value: coin.value }),
39
+ isCoinEqual: (a, b) => a.id === b.id,
40
+ coinSelection: chooseCoin, // Optional: defaults to smallest-first selection
41
+ });
42
+
43
+ console.log('Inputs to add:', recipe.inputs);
44
+ console.log('Outputs to create:', recipe.outputs);
45
+ ```
46
+
47
+ ### Working with Imbalances
48
+
49
+ ```typescript
50
+ import { Imbalances } from '@midnightntwrk/wallet-sdk-capabilities';
51
+
52
+ // Create imbalances from entries
53
+ const imbalances = Imbalances.fromEntries([
54
+ ['NIGHT', -500n], // Need 500 NIGHT (negative = deficit)
55
+ ['TOKEN_A', 200n], // Have 200 TOKEN_A excess (positive = surplus)
56
+ ]);
57
+
58
+ // Merge imbalances
59
+ const merged = Imbalances.merge(imbalances1, imbalances2);
60
+
61
+ // Get value for a token type
62
+ const nightImbalance = Imbalances.getValue(imbalances, 'NIGHT');
63
+ ```
64
+
65
+ ### Custom Coin Selection
66
+
67
+ ```typescript
68
+ import { getBalanceRecipe, CoinSelection } from '@midnightntwrk/wallet-sdk-capabilities';
69
+
70
+ // Custom strategy: prefer larger coins
71
+ const largestFirst: CoinSelection<MyCoin> = (coins, tokenType, amountNeeded, costModel) => {
72
+ return coins
73
+ .filter((coin) => coin.type === tokenType)
74
+ .sort((a, b) => Number(b.value - a.value))
75
+ .at(0);
76
+ };
77
+
78
+ const recipe = getBalanceRecipe({
79
+ // ... other options
80
+ coinSelection: largestFirst,
81
+ });
82
+ ```
83
+
84
+ ### Error Handling
85
+
86
+ ```typescript
87
+ import { getBalanceRecipe, InsufficientFundsError } from '@midnightntwrk/wallet-sdk-capabilities';
88
+
89
+ try {
90
+ const recipe = getBalanceRecipe({
91
+ /* ... */
92
+ });
93
+ } catch (error) {
94
+ if (error instanceof InsufficientFundsError) {
95
+ console.log(`Cannot balance: insufficient ${error.tokenType}`);
96
+ }
97
+ }
98
+ ```
99
+
100
+ ## Exports
101
+
102
+ ### Balancer
103
+
104
+ - `getBalanceRecipe` - Main function to calculate inputs/outputs needed to balance a transaction
105
+ - `createCounterOffer` - Creates counter offers with cost model awareness
106
+ - `chooseCoin` - Default coin selection strategy (smallest coin first)
107
+ - `InsufficientFundsError` - Error thrown when balancing fails due to insufficient funds
108
+ - `BalanceRecipe` - Type for balance result with inputs and outputs
109
+ - `CoinSelection` - Type for coin selection function
110
+
111
+ ### Imbalances
112
+
113
+ - `Imbalances` - Utilities for creating and manipulating token imbalance maps
114
+ - `Imbalance` - Type representing a single token imbalance `[TokenType, TokenValue]`
115
+ - `TokenType` - String type alias for token identifiers
116
+ - `TokenValue` - Bigint type alias for token amounts
117
+ - `CoinRecipe` - Interface for basic coin structure with `type` and `value`
118
+
119
+ ### CounterOffer
120
+
121
+ - `CounterOffer` - Class for building balanced transactions with fee awareness
122
+ - `TransactionCostModel` - Interface defining `inputFeeOverhead` and `outputFeeOverhead`
123
+
124
+ ## License
125
+
126
+ Apache-2.0
@@ -0,0 +1,24 @@
1
+ import { CounterOffer, type TransactionCostModel } from './CounterOffer.js';
2
+ import type { CoinRecipe, Imbalances, TokenType, TokenValue } from './Imbalances.js';
3
+ export declare class InsufficientFundsError extends Error {
4
+ readonly tokenType: TokenType;
5
+ constructor(tokenType: TokenType);
6
+ }
7
+ export interface BalanceRecipe<TInput extends CoinRecipe, TOutput extends CoinRecipe> {
8
+ inputs: TInput[];
9
+ outputs: TOutput[];
10
+ }
11
+ export type CoinSelection<TInput extends CoinRecipe> = (coins: readonly TInput[], tokenType: TokenType, amountNeeded: TokenValue, costModel: TransactionCostModel) => TInput | undefined;
12
+ export type BalanceRecipeProps<TInput extends CoinRecipe, TOutput extends CoinRecipe> = {
13
+ coins: TInput[];
14
+ initialImbalances: Imbalances;
15
+ transactionCostModel: TransactionCostModel;
16
+ feeTokenType: string;
17
+ createOutput: (coin: CoinRecipe) => TOutput;
18
+ isCoinEqual: (a: TInput, b: TInput) => boolean;
19
+ coinSelection?: CoinSelection<TInput> | undefined;
20
+ targetImbalances?: Imbalances;
21
+ };
22
+ export declare const getBalanceRecipe: <TInput extends CoinRecipe, TOutput extends CoinRecipe>({ coins, initialImbalances, transactionCostModel, feeTokenType, createOutput, coinSelection, isCoinEqual, targetImbalances, }: BalanceRecipeProps<TInput, TOutput>) => BalanceRecipe<TInput, TOutput>;
23
+ export declare const createCounterOffer: <TInput extends CoinRecipe, TOutput extends CoinRecipe>(coins: TInput[], initialImbalances: Imbalances, transactionCostModel: TransactionCostModel, feeTokenType: string, coinSelection: CoinSelection<TInput>, createOutput: (coin: CoinRecipe) => TOutput, isCoinEqual: (a: TInput, b: TInput) => boolean, targetImbalances?: Imbalances) => CounterOffer<TInput, TOutput>;
24
+ export declare const chooseCoin: <TInput extends CoinRecipe>(coins: readonly TInput[], tokenType: TokenType) => TInput | undefined;
@@ -0,0 +1,68 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { CounterOffer } from './CounterOffer.js';
14
+ export class InsufficientFundsError extends Error {
15
+ tokenType;
16
+ constructor(tokenType) {
17
+ super(`Insufficient Funds: could not balance ${tokenType}`);
18
+ this.tokenType = tokenType;
19
+ }
20
+ }
21
+ export const getBalanceRecipe = ({ coins, initialImbalances, transactionCostModel, feeTokenType, createOutput, coinSelection, isCoinEqual, targetImbalances, }) => {
22
+ const counterOffer = createCounterOffer(coins, initialImbalances, transactionCostModel, feeTokenType, coinSelection ?? chooseCoin, createOutput, isCoinEqual, targetImbalances);
23
+ return {
24
+ inputs: counterOffer.inputs,
25
+ outputs: counterOffer.outputs,
26
+ };
27
+ };
28
+ export const createCounterOffer = (coins, initialImbalances, transactionCostModel, feeTokenType, coinSelection, createOutput, isCoinEqual, targetImbalances = new Map()) => {
29
+ const counterOffer = new CounterOffer(initialImbalances, transactionCostModel, feeTokenType, targetImbalances);
30
+ let imbalance;
31
+ while ((imbalance = counterOffer.findNonNativeImbalance())) {
32
+ coins = doBalance(imbalance, coins, counterOffer, coinSelection, createOutput, isCoinEqual);
33
+ }
34
+ while ((imbalance = counterOffer.findNativeImbalance())) {
35
+ coins = doBalance(imbalance, coins, counterOffer, coinSelection, createOutput, isCoinEqual);
36
+ }
37
+ return counterOffer;
38
+ };
39
+ const doBalance = (imbalance, coins, counterOffer, coinSelection, createOutput, isCoinEqual) => {
40
+ const [tokenType, imbalanceAmount] = imbalance;
41
+ const shouldAddOutput = (tokenType === counterOffer.feeTokenType &&
42
+ imbalanceAmount >=
43
+ counterOffer.getTargetImbalance(counterOffer.feeTokenType) +
44
+ counterOffer.transactionCostModel.outputFeeOverhead) ||
45
+ (tokenType !== counterOffer.feeTokenType && imbalanceAmount > counterOffer.getTargetImbalance(tokenType));
46
+ if (shouldAddOutput) {
47
+ const output = createOutput({
48
+ type: tokenType,
49
+ value: imbalanceAmount - counterOffer.getTargetImbalance(tokenType),
50
+ });
51
+ counterOffer.addOutput(output);
52
+ }
53
+ else {
54
+ const coin = coinSelection(coins, tokenType, imbalanceAmount, counterOffer.transactionCostModel);
55
+ if (typeof coin === 'undefined') {
56
+ throw new InsufficientFundsError(tokenType);
57
+ }
58
+ counterOffer.addInput(coin);
59
+ coins = coins.filter((c) => !isCoinEqual(c, coin));
60
+ }
61
+ return coins;
62
+ };
63
+ export const chooseCoin = (coins, tokenType) => {
64
+ return coins
65
+ .filter((coin) => coin.type === tokenType)
66
+ .sort((a, b) => Number(a.value - b.value))
67
+ .at(0);
68
+ };
@@ -0,0 +1,19 @@
1
+ import { type CoinRecipe, type Imbalance, Imbalances, type TokenType } from './Imbalances.js';
2
+ export interface TransactionCostModel {
3
+ inputFeeOverhead: bigint;
4
+ outputFeeOverhead: bigint;
5
+ }
6
+ export declare class CounterOffer<TInput extends CoinRecipe, TOutput extends CoinRecipe> {
7
+ readonly imbalances: Imbalances;
8
+ readonly transactionCostModel: TransactionCostModel;
9
+ readonly feeTokenType: string;
10
+ readonly inputs: TInput[];
11
+ readonly outputs: TOutput[];
12
+ readonly targetImbalances: Imbalances;
13
+ constructor(imbalances: Imbalances, transactionCostModel: TransactionCostModel, feeTokenType: string, targetImbalances: Imbalances);
14
+ getTargetImbalance(tokenType: TokenType): bigint;
15
+ findNonNativeImbalance(): Imbalance | undefined;
16
+ findNativeImbalance(): Imbalance | undefined;
17
+ addInput(input: TInput): void;
18
+ addOutput(output: TOutput): void;
19
+ }
@@ -0,0 +1,65 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Imbalances } from './Imbalances.js';
14
+ export class CounterOffer {
15
+ imbalances;
16
+ transactionCostModel;
17
+ feeTokenType;
18
+ inputs;
19
+ outputs;
20
+ targetImbalances;
21
+ constructor(imbalances, transactionCostModel, feeTokenType, targetImbalances) {
22
+ this.imbalances = Imbalances.ensureZerosFor(imbalances, Imbalances.typeSet(targetImbalances));
23
+ this.transactionCostModel = transactionCostModel;
24
+ this.feeTokenType = feeTokenType;
25
+ this.inputs = [];
26
+ this.outputs = [];
27
+ this.targetImbalances = targetImbalances;
28
+ }
29
+ getTargetImbalance(tokenType) {
30
+ return this.targetImbalances.get(tokenType) ?? 0n;
31
+ }
32
+ findNonNativeImbalance() {
33
+ return Array.from(this.imbalances.entries())
34
+ .filter(([tokenType]) => tokenType !== this.feeTokenType)
35
+ .find(([tokenType, value]) => value !== this.getTargetImbalance(tokenType));
36
+ }
37
+ findNativeImbalance() {
38
+ if (!this.feeTokenType) {
39
+ return undefined;
40
+ }
41
+ const nativeImbalance = this.imbalances.get(this.feeTokenType);
42
+ if (nativeImbalance !== undefined && nativeImbalance !== this.getTargetImbalance(this.feeTokenType)) {
43
+ return [this.feeTokenType, nativeImbalance];
44
+ }
45
+ return undefined;
46
+ }
47
+ addInput(input) {
48
+ this.inputs.push(input);
49
+ const imbalance = this.imbalances.get(input.type) || 0n;
50
+ this.imbalances.set(input.type, imbalance + input.value);
51
+ const nativeImbalance = this.imbalances.get(this.feeTokenType) || 0n;
52
+ this.imbalances.set(this.feeTokenType, nativeImbalance - this.transactionCostModel.inputFeeOverhead);
53
+ }
54
+ addOutput(output) {
55
+ const imbalance = this.imbalances.get(output.type) || 0n;
56
+ const subtractFee = output.type === this.feeTokenType ? this.transactionCostModel.outputFeeOverhead : 0n;
57
+ const absoluteCoinValue = output.value < 0n ? -output.value : output.value;
58
+ this.outputs.push({ ...output, type: output.type, value: absoluteCoinValue - subtractFee });
59
+ this.imbalances.set(output.type, imbalance - absoluteCoinValue);
60
+ if (output.type !== this.feeTokenType) {
61
+ const nativeImbalance = this.imbalances.get(this.feeTokenType) || 0n;
62
+ this.imbalances.set(this.feeTokenType, nativeImbalance - this.transactionCostModel.outputFeeOverhead);
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,19 @@
1
+ export type TokenType = string;
2
+ export type TokenValue = bigint;
3
+ export interface CoinRecipe {
4
+ type: TokenType;
5
+ value: TokenValue;
6
+ }
7
+ export type Imbalance = [TokenType, TokenValue];
8
+ export type Imbalances = Map<TokenType, TokenValue>;
9
+ export declare const Imbalances: {
10
+ empty: () => Imbalances;
11
+ fromEntry: (tokenType: TokenType, value: bigint) => Imbalances;
12
+ fromEntries: (entries: Iterable<readonly [TokenType, bigint]>) => Imbalances;
13
+ fromMap: (map: Map<TokenType, bigint>) => Imbalances;
14
+ fromMaybeMap: (map: Map<TokenType, bigint> | undefined) => Imbalances;
15
+ getValue: (map: Imbalances, tokenType: TokenType) => bigint;
16
+ typeSet: (map: Imbalances) => Set<TokenType>;
17
+ ensureZerosFor(map: Imbalances, types: Iterable<TokenType>): Imbalances;
18
+ merge: (a: Imbalances, b: Imbalances) => Imbalances;
19
+ };
@@ -0,0 +1,48 @@
1
+ export const Imbalances = new (class {
2
+ empty = () => {
3
+ return new Map();
4
+ };
5
+ fromEntry = (tokenType, value) => {
6
+ return new Map([[tokenType, value]]);
7
+ };
8
+ fromEntries = (entries) => {
9
+ const out = new Map();
10
+ for (const [tokenType, value] of entries) {
11
+ const existingValue = this.getValue(out, tokenType);
12
+ out.set(tokenType, value + existingValue);
13
+ }
14
+ return out;
15
+ };
16
+ fromMap = (map) => {
17
+ return this.fromEntries(map.entries());
18
+ };
19
+ fromMaybeMap = (map) => {
20
+ return this.fromMap(map ?? new Map());
21
+ };
22
+ getValue = (map, tokenType) => {
23
+ return map.get(tokenType) ?? 0n;
24
+ };
25
+ typeSet = (map) => {
26
+ return new Set(map.keys());
27
+ };
28
+ ensureZerosFor(map, types) {
29
+ const out = this.fromEntries(map.entries());
30
+ for (const tokenType of types) {
31
+ const existingValue = this.getValue(out, tokenType);
32
+ out.set(tokenType, existingValue);
33
+ }
34
+ return out;
35
+ }
36
+ merge = (a, b) => {
37
+ const allTokenTypes = this.typeSet(a).union(this.typeSet(b));
38
+ return this.fromEntries(allTokenTypes
39
+ .values()
40
+ .map((tokenType) => {
41
+ const aValue = this.getValue(a, tokenType);
42
+ const bValue = this.getValue(b, tokenType);
43
+ return [tokenType, aValue + bValue];
44
+ })
45
+ .filter(([, value]) => value !== 0n)
46
+ .toArray());
47
+ };
48
+ })();
@@ -0,0 +1,3 @@
1
+ export * from './Balancer.js';
2
+ export * from './CounterOffer.js';
3
+ export * from './Imbalances.js';
@@ -0,0 +1,15 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ export * from './Balancer.js';
14
+ export * from './CounterOffer.js';
15
+ export * from './Imbalances.js';
@@ -0,0 +1,5 @@
1
+ export * from './balancer/index.js';
2
+ export * from './pendingTransactions/index.js';
3
+ export * from './proving/index.js';
4
+ export * from './simulation/index.js';
5
+ export * from './submission/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ export * from './balancer/index.js';
14
+ export * from './pendingTransactions/index.js';
15
+ export * from './proving/index.js';
16
+ export * from './simulation/index.js';
17
+ export * from './submission/index.js';
@@ -0,0 +1,2 @@
1
+ export * from './pendingTransactionsService.js';
2
+ export * as PendingTransactions from './pendingTransactions.js';
@@ -0,0 +1,16 @@
1
+ /*
2
+ * This file is part of MIDNIGHT-WALLET-SDK.
3
+ * Copyright (C) Midnight Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * You may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ export * from './pendingTransactionsService.js';
16
+ export * as PendingTransactions from './pendingTransactions.js';
@@ -0,0 +1,61 @@
1
+ import { type DateTime, Either, type ParseResult, Schema } from 'effect';
2
+ export type TransactionTrait<TTransaction> = {
3
+ ids: (tx: TTransaction) => readonly string[];
4
+ firstId: (tx: TTransaction) => string;
5
+ areAllTxIdsIncluded: (tx: TTransaction, txIds: readonly string[]) => boolean;
6
+ isOneIncludedInOther: (tx: TTransaction, otherTx: TTransaction) => boolean;
7
+ hasTTLExpired: (tx: TTransaction, txCreationTime: DateTime.Utc, now: DateTime.Utc) => boolean;
8
+ serialize: (tx: TTransaction) => Uint8Array;
9
+ deserialize: (serialized: Uint8Array) => TTransaction;
10
+ isTx: (tx: unknown) => tx is TTransaction;
11
+ };
12
+ export type HasTransactionTrait<TTransaction> = {
13
+ txTrait: TransactionTrait<TTransaction>;
14
+ };
15
+ export type FailedTransactionResult = Readonly<{
16
+ segments: ReadonlyArray<{
17
+ id: number;
18
+ success: boolean;
19
+ }>;
20
+ status: 'PARTIAL_SUCCESS' | 'FAILURE';
21
+ }>;
22
+ export type SuccessTransactionResult = Readonly<{
23
+ segments: ReadonlyArray<{
24
+ id: number;
25
+ success: boolean;
26
+ }>;
27
+ status: 'SUCCESS';
28
+ }>;
29
+ export type TransactionResult = FailedTransactionResult | SuccessTransactionResult;
30
+ export type PendingItem<TTransaction> = Readonly<{
31
+ tx: TTransaction;
32
+ creationTime: DateTime.Utc;
33
+ }>;
34
+ export type CheckedItem<TTransaction> = PendingItem<TTransaction> & {
35
+ result: TransactionResult;
36
+ };
37
+ export type PendingTransactionsItem<TTransaction> = PendingItem<TTransaction> | CheckedItem<TTransaction>;
38
+ export type FailedTransactionItem<TTransaction> = PendingTransactionsItem<TTransaction> & {
39
+ result: FailedTransactionResult;
40
+ };
41
+ export type PendingTransactions<TTransaction> = Readonly<{
42
+ all: ReadonlyArray<PendingTransactionsItem<TTransaction>>;
43
+ }>;
44
+ export declare const has: <TTransaction>(transactions: PendingTransactions<TTransaction>, transaction: TTransaction, txTrait: TransactionTrait<TTransaction>) => boolean;
45
+ export declare const all: <TTransaction>(transactions: PendingTransactions<TTransaction>) => readonly TTransaction[];
46
+ export declare const allFailed: <TTransaction>(transactions: PendingTransactions<TTransaction>) => ReadonlyArray<FailedTransactionItem<TTransaction>>;
47
+ export declare const allPending: <TTransaction>(state: PendingTransactions<TTransaction>) => readonly PendingItem<TTransaction>[];
48
+ export declare const empty: <TTransaction>() => PendingTransactions<TTransaction>;
49
+ export declare const addPendingTransaction: <TTransaction>(state: PendingTransactions<TTransaction>, tx: TTransaction, now: DateTime.Utc, txTrait: TransactionTrait<TTransaction>) => PendingTransactions<TTransaction>;
50
+ export declare const clear: <TTransaction>(state: PendingTransactions<TTransaction>, tx: TTransaction, txTrait: TransactionTrait<TTransaction>) => PendingTransactions<TTransaction>;
51
+ export declare const saveResult: <TTransaction>(state: PendingTransactions<TTransaction>, tx: TTransaction, result: TransactionResult, txTrait: TransactionTrait<TTransaction>) => PendingTransactions<TTransaction>;
52
+ type Serialized<TTransaction> = Readonly<{
53
+ version: 'v1';
54
+ transactions: readonly PendingItem<TTransaction>[];
55
+ }>;
56
+ export declare const SerializedSchema: <TTransaction>(txTrait: TransactionTrait<TTransaction>) => Schema.Schema<Serialized<TTransaction>, any>;
57
+ export declare const serialize: <TTransaction>(state: PendingTransactions<TTransaction>, txTrait: TransactionTrait<TTransaction>) => string;
58
+ export declare const deserialize: <TTransaction>(serialized: string, txTrait: TransactionTrait<TTransaction>) => Either.Either<PendingTransactions<TTransaction>, ParseResult.ParseError>;
59
+ export declare const toSerialized: <TTransaction>(pendingTransactions: PendingTransactions<TTransaction>) => Serialized<TTransaction>;
60
+ export declare const fromSerialized: <TTransaction>(serialized: Serialized<TTransaction>) => PendingTransactions<TTransaction>;
61
+ export {};
@@ -0,0 +1,85 @@
1
+ /*
2
+ * This file is part of MIDNIGHT-WALLET-SDK.
3
+ * Copyright (C) Midnight Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * You may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ import { Array as Arr, Either, Order, pipe, Schema } from 'effect';
16
+ export const has = (transactions, transaction, txTrait) => {
17
+ return transactions.all.some((item) => txTrait.areAllTxIdsIncluded(transaction, txTrait.ids(item.tx)));
18
+ };
19
+ export const all = (transactions) => {
20
+ return transactions.all.map((item) => item.tx);
21
+ };
22
+ export const allFailed = (transactions) => {
23
+ return transactions.all.filter((item) => 'result' in item && (item.result?.status === 'FAILURE' || item.result?.status === 'PARTIAL_SUCCESS'));
24
+ };
25
+ export const allPending = (state) => {
26
+ return state.all.filter((item) => !('result' in item) || item.result === undefined);
27
+ };
28
+ export const empty = () => {
29
+ return {
30
+ all: [],
31
+ };
32
+ };
33
+ export const addPendingTransaction = (state, tx, now, txTrait) => {
34
+ const [rest, foundMatching] = pipe(state.all, Arr.partition((item) => txTrait.isOneIncludedInOther(tx, item.tx)));
35
+ const allMatchingTransactions = Arr.append(foundMatching, { tx, creationTime: now });
36
+ const theBiggestMatchingTx = Arr.max(allMatchingTransactions, pipe(Order.number, Order.mapInput((input) => txTrait.ids(input).length), Order.mapInput((input) => input.tx)));
37
+ return {
38
+ ...state,
39
+ all: Arr.append(rest, theBiggestMatchingTx),
40
+ };
41
+ };
42
+ export const clear = (state, tx, txTrait) => {
43
+ return {
44
+ ...state,
45
+ all: Arr.filter(state.all, (item) => !txTrait.areAllTxIdsIncluded(item.tx, txTrait.ids(tx))),
46
+ };
47
+ };
48
+ export const saveResult = (state, tx, result, txTrait) => {
49
+ return {
50
+ ...state,
51
+ all: Arr.map(state.all, (item) => {
52
+ return txTrait.areAllTxIdsIncluded(item.tx, txTrait.ids(tx)) ? { ...item, result } : item;
53
+ }),
54
+ };
55
+ };
56
+ export const SerializedSchema = (txTrait) => {
57
+ const TxSchema = Schema.declare((tx) => txTrait.isTx(tx));
58
+ const TxFromHex = Schema.transform(Schema.Uint8ArrayFromHex, TxSchema, {
59
+ encode: (tx) => txTrait.serialize(tx),
60
+ decode: (bytes) => txTrait.deserialize(bytes),
61
+ });
62
+ const TxItemSchema = Schema.Struct({
63
+ tx: TxFromHex,
64
+ creationTime: Schema.DateTimeUtc,
65
+ });
66
+ return Schema.Struct({
67
+ version: Schema.Literal('v1'),
68
+ transactions: Schema.Array(TxItemSchema),
69
+ });
70
+ };
71
+ export const serialize = (state, txTrait) => pipe(state, toSerialized, Schema.encodeSync(SerializedSchema(txTrait)), JSON.stringify);
72
+ export const deserialize = (serialized, txTrait) => {
73
+ return pipe(serialized, Schema.decodeUnknownEither(Schema.parseJson(SerializedSchema(txTrait))), Either.map((data) => fromSerialized(data)));
74
+ };
75
+ export const toSerialized = (pendingTransactions) => {
76
+ return {
77
+ version: 'v1',
78
+ transactions: pendingTransactions.all,
79
+ };
80
+ };
81
+ export const fromSerialized = (serialized) => {
82
+ return {
83
+ all: serialized.transactions,
84
+ };
85
+ };
@@ -0,0 +1,52 @@
1
+ import { type Clock, Effect, type ParseResult, Scope, Stream } from 'effect';
2
+ import * as PendingTransactions from './pendingTransactions.js';
3
+ import type * as rx from 'rxjs';
4
+ import { type QueryClient } from '@midnightntwrk/wallet-sdk-indexer-client/effect';
5
+ export type PendingTransactionsService<TTransaction> = {
6
+ start: () => Promise<void>;
7
+ stop: () => Promise<void>;
8
+ state: () => rx.Observable<PendingTransactions.PendingTransactions<TTransaction>>;
9
+ addPendingTransaction: (tx: TTransaction) => Promise<void>;
10
+ clear: (tx: TTransaction) => Promise<void>;
11
+ };
12
+ export type IndexerClientConnection = {
13
+ indexerHttpUrl: string;
14
+ indexerWsUrl?: string;
15
+ };
16
+ export type DefaultPendingTransactionsServiceConfiguration = {
17
+ indexerClientConnection: IndexerClientConnection;
18
+ };
19
+ export type InitParams<TTransaction> = {
20
+ txTrait: PendingTransactions.TransactionTrait<TTransaction>;
21
+ initialState?: PendingTransactions.PendingTransactions<TTransaction>;
22
+ configuration: DefaultPendingTransactionsServiceConfiguration;
23
+ };
24
+ export declare class PendingTransactionsServiceImpl<TTransaction> implements PendingTransactionsService<TTransaction> {
25
+ #private;
26
+ static init<TTransaction>(initParams: InitParams<TTransaction>): Promise<PendingTransactionsServiceImpl<TTransaction>>;
27
+ static restore<TTransaction>(data: string, txTrait: PendingTransactions.TransactionTrait<TTransaction>, configuration: DefaultPendingTransactionsServiceConfiguration): Promise<PendingTransactionsServiceImpl<TTransaction>>;
28
+ private static initEffect;
29
+ constructor(effectService: PendingTransactionsServiceEffect<TTransaction>, scope: Scope.CloseableScope, configuration: DefaultPendingTransactionsServiceConfiguration);
30
+ addPendingTransaction(tx: TTransaction): Promise<void>;
31
+ clear(tx: TTransaction): Promise<void>;
32
+ start(): Promise<void>;
33
+ state(): rx.Observable<PendingTransactions.PendingTransactions<TTransaction>>;
34
+ stop(): Promise<void>;
35
+ }
36
+ export type PendingTransactionsServiceEffect<TTransaction> = {
37
+ startPolling: (ticks: Stream.Stream<unknown>) => Effect.Effect<void, Error, QueryClient | Scope.Scope | Clock.Clock>;
38
+ state: () => Stream.Stream<PendingTransactions.PendingTransactions<TTransaction>>;
39
+ addPendingTransaction: (tx: TTransaction) => Effect.Effect<void, never, never>;
40
+ clear: (tx: TTransaction) => Effect.Effect<void, never, never>;
41
+ };
42
+ export declare class PendingTransactionsServiceEffectImpl<TTransaction> implements PendingTransactionsServiceEffect<TTransaction> {
43
+ #private;
44
+ static restore<TTransaction>(data: string, txTrait: PendingTransactions.TransactionTrait<TTransaction>): Effect.Effect<PendingTransactionsServiceEffectImpl<TTransaction>, ParseResult.ParseError>;
45
+ constructor(txTrait: PendingTransactions.TransactionTrait<TTransaction>, initialState?: PendingTransactions.PendingTransactions<TTransaction>);
46
+ state(): Stream.Stream<PendingTransactions.PendingTransactions<TTransaction>>;
47
+ startPolling(ticks: Stream.Stream<unknown>): Effect.Effect<void, Error, QueryClient | Scope.Scope>;
48
+ addPendingTransaction(tx: TTransaction): Effect.Effect<void>;
49
+ clear(tx: TTransaction): Effect.Effect<void>;
50
+ private saveResult;
51
+ private queryForStatus;
52
+ }