@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 +126 -0
- package/dist/balancer/Balancer.d.ts +24 -0
- package/dist/balancer/Balancer.js +68 -0
- package/dist/balancer/CounterOffer.d.ts +19 -0
- package/dist/balancer/CounterOffer.js +65 -0
- package/dist/balancer/Imbalances.d.ts +19 -0
- package/dist/balancer/Imbalances.js +48 -0
- package/dist/balancer/index.d.ts +3 -0
- package/dist/balancer/index.js +15 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +17 -0
- package/dist/pendingTransactions/index.d.ts +2 -0
- package/dist/pendingTransactions/index.js +16 -0
- package/dist/pendingTransactions/pendingTransactions.d.ts +61 -0
- package/dist/pendingTransactions/pendingTransactions.js +85 -0
- package/dist/pendingTransactions/pendingTransactionsService.d.ts +52 -0
- package/dist/pendingTransactions/pendingTransactionsService.js +146 -0
- package/dist/proving/index.d.ts +1 -0
- package/dist/proving/index.js +15 -0
- package/dist/proving/provingService.d.ts +37 -0
- package/dist/proving/provingService.js +61 -0
- package/dist/simulation/Simulator.d.ts +180 -0
- package/dist/simulation/Simulator.js +453 -0
- package/dist/simulation/SimulatorState.d.ts +349 -0
- package/dist/simulation/SimulatorState.js +323 -0
- package/dist/simulation/index.d.ts +2 -0
- package/dist/simulation/index.js +26 -0
- package/dist/submission/index.d.ts +1 -0
- package/dist/submission/index.js +15 -0
- package/dist/submission/submissionService.d.ts +60 -0
- package/dist/submission/submissionService.js +86 -0
- package/package.json +79 -0
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,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';
|
package/dist/index.d.ts
ADDED
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,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
|
+
}
|