@midnight-ntwrk/wallet-sdk-unshielded-wallet 1.0.0-beta.16 → 1.0.0-beta.18
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/dist/UnshieldedWallet.d.ts +12 -10
- package/dist/UnshieldedWallet.js +17 -3
- package/dist/v1/RunningV1Variant.d.ts +11 -8
- package/dist/v1/RunningV1Variant.js +13 -3
- package/dist/v1/Transacting.d.ts +55 -10
- package/dist/v1/Transacting.js +235 -177
- package/dist/v1/TransactionOps.d.ts +22 -0
- package/dist/v1/TransactionOps.js +78 -0
- package/dist/v1/V1Builder.d.ts +28 -30
- package/dist/v1/V1Builder.js +1 -12
- package/dist/v1/index.d.ts +1 -1
- package/dist/v1/index.js +1 -1
- package/package.json +8 -7
- package/dist/v1/Transaction.d.ts +0 -25
- package/dist/v1/Transaction.js +0 -155
- package/dist/v1/TransactionImbalances.d.ts +0 -8
- package/dist/v1/TransactionImbalances.js +0 -21
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ProtocolState, ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
|
|
2
|
-
import { BaseV1Configuration, DefaultV1Configuration, V1Variant, CoreWallet } from './v1/index.js';
|
|
2
|
+
import { BaseV1Configuration, DefaultV1Configuration, V1Variant, CoreWallet, UnboundTransaction } from './v1/index.js';
|
|
3
3
|
import * as ledger from '@midnight-ntwrk/ledger-v7';
|
|
4
4
|
import * as rx from 'rxjs';
|
|
5
5
|
import { SerializationCapability } from './v1/Serialization.js';
|
|
6
6
|
import { TransactionHistoryService } from './v1/TransactionHistory.js';
|
|
7
7
|
import { CoinsAndBalancesCapability } from './v1/CoinsAndBalances.js';
|
|
8
8
|
import { KeysCapability } from './v1/Keys.js';
|
|
9
|
-
import { TokenTransfer } from './v1/Transacting.js';
|
|
9
|
+
import { TokenTransfer, FinalizedTransactionBalanceResult, UnboundTransactionBalanceResult, UnprovenTransactionBalanceResult } from './v1/Transacting.js';
|
|
10
10
|
import { WalletSyncUpdate } from './v1/SyncSchema.js';
|
|
11
11
|
import { UtxoWithMeta } from './v1/UnshieldedState.js';
|
|
12
12
|
import { Variant, VariantBuilder, WalletLike } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
|
|
@@ -35,12 +35,14 @@ export declare class UnshieldedWalletState<TSerialized = string> {
|
|
|
35
35
|
constructor(state: ProtocolState.ProtocolState<CoreWallet>, capabilities: UnshieldedWalletCapabilities<TSerialized>);
|
|
36
36
|
serialize(): TSerialized;
|
|
37
37
|
}
|
|
38
|
-
export type UnshieldedWallet = CustomizedUnshieldedWallet<
|
|
39
|
-
export type UnshieldedWalletClass = CustomizedUnshieldedWalletClass<
|
|
40
|
-
export interface CustomizedUnshieldedWallet<
|
|
38
|
+
export type UnshieldedWallet = CustomizedUnshieldedWallet<WalletSyncUpdate, string>;
|
|
39
|
+
export type UnshieldedWalletClass = CustomizedUnshieldedWalletClass<WalletSyncUpdate, string, DefaultV1Configuration>;
|
|
40
|
+
export interface CustomizedUnshieldedWallet<TSyncUpdate = WalletSyncUpdate, TSerialized = string> extends WalletLike.WalletLike<[Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate>>]> {
|
|
41
41
|
readonly state: rx.Observable<UnshieldedWalletState<TSerialized>>;
|
|
42
42
|
start(): Promise<void>;
|
|
43
|
-
|
|
43
|
+
balanceFinalizedTransaction(tx: ledger.FinalizedTransaction): Promise<FinalizedTransactionBalanceResult>;
|
|
44
|
+
balanceUnboundTransaction(tx: UnboundTransaction): Promise<UnboundTransactionBalanceResult>;
|
|
45
|
+
balanceUnprovenTransaction(tx: ledger.UnprovenTransaction): Promise<UnprovenTransactionBalanceResult>;
|
|
44
46
|
transferTransaction(outputs: readonly TokenTransfer[], ttl: Date): Promise<ledger.UnprovenTransaction>;
|
|
45
47
|
initSwap(desiredInputs: Record<ledger.RawTokenType, bigint>, desiredOutputs: readonly TokenTransfer[], ttl: Date): Promise<ledger.UnprovenTransaction>;
|
|
46
48
|
signTransaction(transaction: ledger.UnprovenTransaction, signSegment: (data: Uint8Array) => ledger.Signature): Promise<ledger.UnprovenTransaction>;
|
|
@@ -48,10 +50,10 @@ export interface CustomizedUnshieldedWallet<TTransaction = ledger.Transaction<le
|
|
|
48
50
|
waitForSyncedState(allowedGap?: bigint): Promise<UnshieldedWalletState<TSerialized>>;
|
|
49
51
|
getAddress(): Promise<UnshieldedAddress>;
|
|
50
52
|
}
|
|
51
|
-
export interface CustomizedUnshieldedWalletClass<
|
|
53
|
+
export interface CustomizedUnshieldedWalletClass<TSyncUpdate = WalletSyncUpdate, TSerialized = string, TConfig extends BaseV1Configuration = DefaultV1Configuration> extends WalletLike.BaseWalletClass<[Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate>>]> {
|
|
52
54
|
configuration: TConfig;
|
|
53
|
-
startWithPublicKey(publicKey: PublicKey): CustomizedUnshieldedWallet<
|
|
54
|
-
restore(serializedState: TSerialized): CustomizedUnshieldedWallet<
|
|
55
|
+
startWithPublicKey(publicKey: PublicKey): CustomizedUnshieldedWallet<TSyncUpdate, TSerialized>;
|
|
56
|
+
restore(serializedState: TSerialized): CustomizedUnshieldedWallet<TSyncUpdate, TSerialized>;
|
|
55
57
|
}
|
|
56
58
|
export declare function UnshieldedWallet(configuration: DefaultV1Configuration): UnshieldedWalletClass;
|
|
57
|
-
export declare function CustomUnshieldedWallet<TConfig extends BaseV1Configuration = DefaultV1Configuration,
|
|
59
|
+
export declare function CustomUnshieldedWallet<TConfig extends BaseV1Configuration = DefaultV1Configuration, TSyncUpdate = WalletSyncUpdate, TSerialized = string>(configuration: TConfig, builder: VariantBuilder.VariantBuilder<V1Variant<TSerialized, TSyncUpdate>, TConfig>): CustomizedUnshieldedWalletClass<TSyncUpdate, TSerialized, TConfig>;
|
package/dist/UnshieldedWallet.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
|
|
14
|
-
import { V1Builder, V1Tag, CoreWallet } from './v1/index.js';
|
|
14
|
+
import { V1Builder, V1Tag, CoreWallet, } from './v1/index.js';
|
|
15
15
|
import { Effect, Either } from 'effect';
|
|
16
16
|
import * as rx from 'rxjs';
|
|
17
17
|
import { WalletBuilder } from '@midnight-ntwrk/wallet-sdk-runtime';
|
|
@@ -77,10 +77,24 @@ export function CustomUnshieldedWallet(configuration, builder) {
|
|
|
77
77
|
start() {
|
|
78
78
|
return this.runtime.dispatch({ [V1Tag]: (v1) => v1.startSyncInBackground() }).pipe(Effect.runPromise);
|
|
79
79
|
}
|
|
80
|
-
|
|
80
|
+
balanceFinalizedTransaction(tx) {
|
|
81
81
|
return this.runtime
|
|
82
82
|
.dispatch({
|
|
83
|
-
[V1Tag]: (v1) => v1.
|
|
83
|
+
[V1Tag]: (v1) => v1.balanceFinalizedTransaction(tx),
|
|
84
|
+
})
|
|
85
|
+
.pipe(Effect.runPromise);
|
|
86
|
+
}
|
|
87
|
+
balanceUnboundTransaction(tx) {
|
|
88
|
+
return this.runtime
|
|
89
|
+
.dispatch({
|
|
90
|
+
[V1Tag]: (v1) => v1.balanceUnboundTransaction(tx),
|
|
91
|
+
})
|
|
92
|
+
.pipe(Effect.runPromise);
|
|
93
|
+
}
|
|
94
|
+
balanceUnprovenTransaction(tx) {
|
|
95
|
+
return this.runtime
|
|
96
|
+
.dispatch({
|
|
97
|
+
[V1Tag]: (v1) => v1.balanceUnprovenTransaction(tx),
|
|
84
98
|
})
|
|
85
99
|
.pipe(Effect.runPromise);
|
|
86
100
|
}
|
|
@@ -3,7 +3,8 @@ import { WalletRuntimeError, Variant, StateChange } from '@midnight-ntwrk/wallet
|
|
|
3
3
|
import { SerializationCapability } from './Serialization.js';
|
|
4
4
|
import { SyncCapability, SyncService } from './Sync.js';
|
|
5
5
|
import { WalletSyncUpdate } from './SyncSchema.js';
|
|
6
|
-
import { TransactingCapability, TokenTransfer } from './Transacting.js';
|
|
6
|
+
import { TransactingCapability, TokenTransfer, FinalizedTransactionBalanceResult, UnboundTransactionBalanceResult, UnprovenTransactionBalanceResult } from './Transacting.js';
|
|
7
|
+
import { UnboundTransaction } from './TransactionOps.js';
|
|
7
8
|
import { WalletError } from './WalletError.js';
|
|
8
9
|
import { CoinsAndBalancesCapability } from './CoinsAndBalances.js';
|
|
9
10
|
import { KeysCapability } from './Keys.js';
|
|
@@ -12,28 +13,30 @@ import { CoreWallet } from './CoreWallet.js';
|
|
|
12
13
|
import { TransactionHistoryService } from './TransactionHistory.js';
|
|
13
14
|
import * as ledger from '@midnight-ntwrk/ledger-v7';
|
|
14
15
|
export declare namespace RunningV1Variant {
|
|
15
|
-
type Context<TSerialized, TSyncUpdate
|
|
16
|
+
type Context<TSerialized, TSyncUpdate> = {
|
|
16
17
|
serializationCapability: SerializationCapability<CoreWallet, TSerialized>;
|
|
17
18
|
syncService: SyncService<CoreWallet, TSyncUpdate>;
|
|
18
19
|
syncCapability: SyncCapability<CoreWallet, TSyncUpdate>;
|
|
19
|
-
transactingCapability: TransactingCapability<
|
|
20
|
+
transactingCapability: TransactingCapability<CoreWallet>;
|
|
20
21
|
coinsAndBalancesCapability: CoinsAndBalancesCapability<CoreWallet>;
|
|
21
22
|
keysCapability: KeysCapability<CoreWallet>;
|
|
22
23
|
coinSelection: CoinSelection<ledger.Utxo>;
|
|
23
24
|
transactionHistoryService: TransactionHistoryService<WalletSyncUpdate>;
|
|
24
25
|
};
|
|
25
|
-
type AnyContext = Context<any, any
|
|
26
|
+
type AnyContext = Context<any, any>;
|
|
26
27
|
}
|
|
27
28
|
export declare const V1Tag: unique symbol;
|
|
28
|
-
export type DefaultRunningV1 = RunningV1Variant<string, WalletSyncUpdate
|
|
29
|
-
export declare class RunningV1Variant<TSerialized, TSyncUpdate
|
|
29
|
+
export type DefaultRunningV1 = RunningV1Variant<string, WalletSyncUpdate>;
|
|
30
|
+
export declare class RunningV1Variant<TSerialized, TSyncUpdate> implements Variant.RunningVariant<typeof V1Tag, CoreWallet> {
|
|
30
31
|
#private;
|
|
31
32
|
readonly __polyTag__: typeof V1Tag;
|
|
32
33
|
readonly state: Stream.Stream<StateChange.StateChange<CoreWallet>, WalletRuntimeError>;
|
|
33
|
-
constructor(scope: Scope.Scope, context: Variant.VariantContext<CoreWallet>, v1Context: RunningV1Variant.Context<TSerialized, TSyncUpdate
|
|
34
|
+
constructor(scope: Scope.Scope, context: Variant.VariantContext<CoreWallet>, v1Context: RunningV1Variant.Context<TSerialized, TSyncUpdate>);
|
|
34
35
|
startSyncInBackground(): Effect.Effect<void>;
|
|
35
36
|
startSync(): Stream.Stream<void, WalletError, Scope.Scope>;
|
|
36
|
-
|
|
37
|
+
balanceFinalizedTransaction(tx: ledger.FinalizedTransaction): Effect.Effect<FinalizedTransactionBalanceResult, WalletError>;
|
|
38
|
+
balanceUnboundTransaction(tx: UnboundTransaction): Effect.Effect<UnboundTransactionBalanceResult, WalletError>;
|
|
39
|
+
balanceUnprovenTransaction(tx: ledger.UnprovenTransaction): Effect.Effect<UnprovenTransactionBalanceResult, WalletError>;
|
|
37
40
|
transferTransaction(outputs: ReadonlyArray<TokenTransfer>, ttl: Date): Effect.Effect<ledger.UnprovenTransaction, WalletError>;
|
|
38
41
|
initSwap(desiredInputs: Record<string, bigint>, desiredOutputs: ReadonlyArray<TokenTransfer>, ttl: Date): Effect.Effect<ledger.UnprovenTransaction, WalletError>;
|
|
39
42
|
signTransaction(transaction: ledger.UnprovenTransaction, signSegment: (data: Uint8Array) => ledger.Signature): Effect.Effect<ledger.UnprovenTransaction, WalletError>;
|
|
@@ -67,14 +67,24 @@ export class RunningV1Variant {
|
|
|
67
67
|
return Duration.millis(Math.min(delayWithJitter, Duration.toMillis(maxDelay)));
|
|
68
68
|
}))));
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
balanceFinalizedTransaction(tx) {
|
|
71
71
|
return SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => {
|
|
72
|
-
return pipe(this.#v1Context.transactingCapability.
|
|
72
|
+
return pipe(this.#v1Context.transactingCapability.balanceFinalizedTransaction(state, tx), EitherOps.toEffect);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
balanceUnboundTransaction(tx) {
|
|
76
|
+
return SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => {
|
|
77
|
+
return pipe(this.#v1Context.transactingCapability.balanceUnboundTransaction(state, tx), EitherOps.toEffect);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
balanceUnprovenTransaction(tx) {
|
|
81
|
+
return SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => {
|
|
82
|
+
return pipe(this.#v1Context.transactingCapability.balanceUnprovenTransaction(state, tx), EitherOps.toEffect);
|
|
73
83
|
});
|
|
74
84
|
}
|
|
75
85
|
transferTransaction(outputs, ttl) {
|
|
76
86
|
return SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => {
|
|
77
|
-
return pipe(this.#v1Context.transactingCapability.makeTransfer(state, outputs, ttl), EitherOps.toEffect, Effect.
|
|
87
|
+
return pipe(this.#v1Context.transactingCapability.makeTransfer(state, outputs, ttl), EitherOps.toEffect, Effect.map(({ transaction, newState }) => [transaction, newState]));
|
|
78
88
|
});
|
|
79
89
|
}
|
|
80
90
|
initSwap(desiredInputs, desiredOutputs, ttl) {
|
package/dist/v1/Transacting.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Either } from 'effect';
|
|
|
4
4
|
import { CoreWallet } from './CoreWallet.js';
|
|
5
5
|
import { WalletError } from './WalletError.js';
|
|
6
6
|
import { CoinSelection } from '@midnight-ntwrk/wallet-sdk-capabilities';
|
|
7
|
-
import {
|
|
7
|
+
import { TransactionOps, UnboundTransaction } from './TransactionOps.js';
|
|
8
8
|
import { CoinsAndBalancesCapability } from './CoinsAndBalances.js';
|
|
9
9
|
import { KeysCapability } from './Keys.js';
|
|
10
10
|
export interface TokenTransfer {
|
|
@@ -12,14 +12,19 @@ export interface TokenTransfer {
|
|
|
12
12
|
readonly type: ledger.RawTokenType;
|
|
13
13
|
readonly receiverAddress: string;
|
|
14
14
|
}
|
|
15
|
+
export type FinalizedTransactionBalanceResult = ledger.UnprovenTransaction | undefined;
|
|
16
|
+
export type UnboundTransactionBalanceResult = UnboundTransaction | undefined;
|
|
17
|
+
export type UnprovenTransactionBalanceResult = ledger.UnprovenTransaction | undefined;
|
|
15
18
|
export type TransactingResult<TTransaction, TState> = {
|
|
16
19
|
readonly newState: TState;
|
|
17
20
|
readonly transaction: TTransaction;
|
|
18
21
|
};
|
|
19
|
-
export interface TransactingCapability<
|
|
22
|
+
export interface TransactingCapability<TState> {
|
|
20
23
|
makeTransfer(wallet: CoreWallet, outputs: ReadonlyArray<TokenTransfer>, ttl: Date): Either.Either<TransactingResult<ledger.UnprovenTransaction, TState>, WalletError>;
|
|
21
24
|
initSwap(wallet: CoreWallet, desiredInputs: Record<string, bigint>, outputs: ReadonlyArray<TokenTransfer>, ttl: Date): Either.Either<TransactingResult<ledger.UnprovenTransaction, TState>, WalletError>;
|
|
22
|
-
|
|
25
|
+
balanceFinalizedTransaction(wallet: CoreWallet, transaction: ledger.FinalizedTransaction): Either.Either<[FinalizedTransactionBalanceResult, CoreWallet], WalletError>;
|
|
26
|
+
balanceUnboundTransaction(wallet: CoreWallet, transaction: UnboundTransaction): Either.Either<[UnboundTransactionBalanceResult, CoreWallet], WalletError>;
|
|
27
|
+
balanceUnprovenTransaction(wallet: CoreWallet, transaction: ledger.UnprovenTransaction): Either.Either<[UnprovenTransactionBalanceResult, CoreWallet], WalletError>;
|
|
23
28
|
signTransaction(transaction: ledger.UnprovenTransaction, signSegment: (data: Uint8Array) => ledger.Signature): Either.Either<ledger.UnprovenTransaction, WalletError>;
|
|
24
29
|
}
|
|
25
30
|
export type DefaultTransactingConfiguration = {
|
|
@@ -30,16 +35,56 @@ export type DefaultTransactingContext = {
|
|
|
30
35
|
coinsAndBalancesCapability: CoinsAndBalancesCapability<CoreWallet>;
|
|
31
36
|
keysCapability: KeysCapability<CoreWallet>;
|
|
32
37
|
};
|
|
33
|
-
export declare const makeDefaultTransactingCapability: (config: DefaultTransactingConfiguration, getContext: () => DefaultTransactingContext) => TransactingCapability<
|
|
34
|
-
export declare class TransactingCapabilityImplementation
|
|
38
|
+
export declare const makeDefaultTransactingCapability: (config: DefaultTransactingConfiguration, getContext: () => DefaultTransactingContext) => TransactingCapability<CoreWallet>;
|
|
39
|
+
export declare class TransactingCapabilityImplementation implements TransactingCapability<CoreWallet> {
|
|
40
|
+
#private;
|
|
35
41
|
readonly networkId: NetworkId.NetworkId;
|
|
36
42
|
readonly getCoinSelection: () => CoinSelection<ledger.Utxo>;
|
|
37
|
-
readonly
|
|
43
|
+
readonly txOps: TransactionOps;
|
|
38
44
|
readonly getCoins: () => CoinsAndBalancesCapability<CoreWallet>;
|
|
39
45
|
readonly getKeys: () => KeysCapability<CoreWallet>;
|
|
40
|
-
constructor(networkId: NetworkId.NetworkId, getCoinSelection: () => CoinSelection<ledger.Utxo>, getCoins: () => CoinsAndBalancesCapability<CoreWallet>, getKeys: () => KeysCapability<CoreWallet>,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
constructor(networkId: NetworkId.NetworkId, getCoinSelection: () => CoinSelection<ledger.Utxo>, getCoins: () => CoinsAndBalancesCapability<CoreWallet>, getKeys: () => KeysCapability<CoreWallet>, txOps: TransactionOps);
|
|
47
|
+
/**
|
|
48
|
+
* Balances an unbound transaction
|
|
49
|
+
* Note: Unbound transactions are balanced in place and returned
|
|
50
|
+
* @param wallet - The wallet to balance the transaction with
|
|
51
|
+
* @param transaction - The transaction to balance
|
|
52
|
+
* @returns The balanced transaction and the new wallet state if successful, otherwise an error
|
|
53
|
+
*/
|
|
54
|
+
balanceUnboundTransaction(wallet: CoreWallet, transaction: UnboundTransaction): Either.Either<[UnboundTransactionBalanceResult, CoreWallet], WalletError>;
|
|
55
|
+
/**
|
|
56
|
+
* Balances an unproven transaction
|
|
57
|
+
* Note: This method does the same thing as balanceUnboundTransaction but is provided for convenience and type safety
|
|
58
|
+
* @param wallet - The wallet to balance the transaction with
|
|
59
|
+
* @param transaction - The transaction to balance
|
|
60
|
+
* @returns The balanced transaction and the new wallet state if successful, otherwise an error
|
|
61
|
+
*/
|
|
62
|
+
balanceUnprovenTransaction(wallet: CoreWallet, transaction: ledger.UnprovenTransaction): Either.Either<[UnprovenTransactionBalanceResult, CoreWallet], WalletError>;
|
|
63
|
+
/**
|
|
64
|
+
* Balances a bound transaction
|
|
65
|
+
* Note: In bound transactions we can only balance the guaranteed section in intents
|
|
66
|
+
* @param wallet - The wallet to balance the transaction with
|
|
67
|
+
* @param transaction - The transaction to balance
|
|
68
|
+
* @returns A balancing counterpart transaction (which should be merged with the original transaction )
|
|
69
|
+
* and the new wallet state if successful, otherwise an error
|
|
70
|
+
*/
|
|
71
|
+
balanceFinalizedTransaction(wallet: CoreWallet, transaction: ledger.FinalizedTransaction): Either.Either<[FinalizedTransactionBalanceResult, CoreWallet], WalletError>;
|
|
72
|
+
/**
|
|
73
|
+
* Makes a transfer transaction
|
|
74
|
+
* @param wallet - The wallet to make the transfer with
|
|
75
|
+
* @param outputs - The outputs for the transfer
|
|
76
|
+
* @param ttl - The TTL for the transaction
|
|
77
|
+
* @returns The balanced transfer transaction and the new wallet state if successful, otherwise an error
|
|
78
|
+
*/
|
|
79
|
+
makeTransfer(wallet: CoreWallet, outputs: ReadonlyArray<TokenTransfer>, ttl: Date): Either.Either<TransactingResult<ledger.UnprovenTransaction, CoreWallet>, WalletError>;
|
|
80
|
+
/**
|
|
81
|
+
* Initializes a swap transaction
|
|
82
|
+
* @param wallet - The wallet to initialize the swap for
|
|
83
|
+
* @param desiredInputs - The desired inputs for the swap
|
|
84
|
+
* @param desiredOutputs - The desired outputs for the swap
|
|
85
|
+
* @param ttl - The TTL for the swap
|
|
86
|
+
* @returns The initialized swap transaction and the new wallet state if successful, otherwise an error
|
|
87
|
+
*/
|
|
88
|
+
initSwap(wallet: CoreWallet, desiredInputs: Record<ledger.RawTokenType, bigint>, desiredOutputs: ReadonlyArray<TokenTransfer>, ttl: Date): Either.Either<TransactingResult<ledger.UnprovenTransaction, CoreWallet>, WalletError>;
|
|
44
89
|
signTransaction(transaction: ledger.UnprovenTransaction, signSegment: (data: Uint8Array) => ledger.Signature): Either.Either<ledger.UnprovenTransaction, WalletError>;
|
|
45
90
|
}
|
package/dist/v1/Transacting.js
CHANGED
|
@@ -13,182 +13,140 @@
|
|
|
13
13
|
import * as ledger from '@midnight-ntwrk/ledger-v7';
|
|
14
14
|
import { Either, Option, pipe } from 'effect';
|
|
15
15
|
import { CoreWallet } from './CoreWallet.js';
|
|
16
|
-
import { SignError, TransactingError } from './WalletError.js';
|
|
17
|
-
import { getBalanceRecipe, Imbalances } from '@midnight-ntwrk/wallet-sdk-capabilities';
|
|
18
|
-
import {
|
|
16
|
+
import { InsufficientFundsError, OtherWalletError, SignError, TransactingError } from './WalletError.js';
|
|
17
|
+
import { getBalanceRecipe, Imbalances, InsufficientFundsError as BalancingInsufficientFundsError, } from '@midnight-ntwrk/wallet-sdk-capabilities';
|
|
18
|
+
import { TransactionOps } from './TransactionOps.js';
|
|
19
19
|
import { MidnightBech32m, UnshieldedAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
|
|
20
20
|
const GUARANTEED_SEGMENT = 0;
|
|
21
|
-
const mergeCounterOffer = (counterOffer, currentOffer) => pipe(Option.fromNullable(currentOffer), Option.match({
|
|
22
|
-
onNone: () => Either.right(counterOffer),
|
|
23
|
-
onSome: (currentOffer) => Either.try({
|
|
24
|
-
try: () => ledger.UnshieldedOffer.new([...currentOffer.inputs, ...counterOffer.inputs], [...currentOffer.outputs, ...counterOffer.outputs], [...currentOffer.signatures, ...counterOffer.signatures]),
|
|
25
|
-
catch: (error) => new TransactingError({ message: 'Failed to merge counter offers', cause: error }),
|
|
26
|
-
}),
|
|
27
|
-
}));
|
|
28
21
|
export const makeDefaultTransactingCapability = (config, getContext) => {
|
|
29
|
-
return new TransactingCapabilityImplementation(config.networkId, () => getContext().coinSelection, () => getContext().coinsAndBalancesCapability, () => getContext().keysCapability,
|
|
22
|
+
return new TransactingCapabilityImplementation(config.networkId, () => getContext().coinSelection, () => getContext().coinsAndBalancesCapability, () => getContext().keysCapability, TransactionOps);
|
|
30
23
|
};
|
|
31
24
|
export class TransactingCapabilityImplementation {
|
|
32
25
|
networkId;
|
|
33
26
|
getCoinSelection;
|
|
34
|
-
|
|
27
|
+
txOps;
|
|
35
28
|
getCoins;
|
|
36
29
|
getKeys;
|
|
37
|
-
constructor(networkId, getCoinSelection, getCoins, getKeys,
|
|
30
|
+
constructor(networkId, getCoinSelection, getCoins, getKeys, txOps) {
|
|
38
31
|
this.getCoins = getCoins;
|
|
39
32
|
this.networkId = networkId;
|
|
40
33
|
this.getCoinSelection = getCoinSelection;
|
|
41
34
|
this.getKeys = getKeys;
|
|
42
|
-
this.
|
|
35
|
+
this.txOps = txOps;
|
|
43
36
|
}
|
|
44
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Balances an unbound transaction
|
|
39
|
+
* Note: Unbound transactions are balanced in place and returned
|
|
40
|
+
* @param wallet - The wallet to balance the transaction with
|
|
41
|
+
* @param transaction - The transaction to balance
|
|
42
|
+
* @returns The balanced transaction and the new wallet state if successful, otherwise an error
|
|
43
|
+
*/
|
|
44
|
+
balanceUnboundTransaction(wallet, transaction) {
|
|
45
|
+
return this.#balanceUnboundishTransaction(wallet, transaction);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Balances an unproven transaction
|
|
49
|
+
* Note: This method does the same thing as balanceUnboundTransaction but is provided for convenience and type safety
|
|
50
|
+
* @param wallet - The wallet to balance the transaction with
|
|
51
|
+
* @param transaction - The transaction to balance
|
|
52
|
+
* @returns The balanced transaction and the new wallet state if successful, otherwise an error
|
|
53
|
+
*/
|
|
54
|
+
balanceUnprovenTransaction(wallet, transaction) {
|
|
55
|
+
return this.#balanceUnboundishTransaction(wallet, transaction);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Balances a bound transaction
|
|
59
|
+
* Note: In bound transactions we can only balance the guaranteed section in intents
|
|
60
|
+
* @param wallet - The wallet to balance the transaction with
|
|
61
|
+
* @param transaction - The transaction to balance
|
|
62
|
+
* @returns A balancing counterpart transaction (which should be merged with the original transaction )
|
|
63
|
+
* and the new wallet state if successful, otherwise an error
|
|
64
|
+
*/
|
|
65
|
+
balanceFinalizedTransaction(wallet, transaction) {
|
|
45
66
|
return Either.gen(this, function* () {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const { addressHex, publicKey } = wallet.publicKey;
|
|
54
|
-
for (const segment of [...segments, GUARANTEED_SEGMENT]) {
|
|
55
|
-
const allIntentImbalances = yield* Either.try({
|
|
56
|
-
try: () => transaction.imbalances(segment),
|
|
57
|
-
catch: (error) => new TransactingError({ message: 'Failed to get intent imbalances', cause: error }),
|
|
58
|
-
});
|
|
59
|
-
const imbalances = allIntentImbalances
|
|
60
|
-
.entries()
|
|
61
|
-
.filter(([token, value]) => token.tag === 'unshielded' && value !== 0n)
|
|
62
|
-
.map(([token, value]) => [token, value])
|
|
63
|
-
.map(([token, value]) => {
|
|
64
|
-
return [token.raw, value];
|
|
65
|
-
})
|
|
66
|
-
.toArray();
|
|
67
|
-
// // intent is balanced
|
|
68
|
-
if (!imbalances.length)
|
|
69
|
-
continue;
|
|
70
|
-
const availableCoins = this.getCoins().getAvailableCoins(wallet);
|
|
71
|
-
if (!availableCoins.length) {
|
|
72
|
-
return yield* Either.left(new TransactingError({ message: 'No available coins to spend' }));
|
|
73
|
-
}
|
|
74
|
-
// select inputs, receive the change outputs
|
|
75
|
-
const { inputs, outputs: changeOutputs } = yield* Either.try({
|
|
76
|
-
try: () => getBalanceRecipe({
|
|
77
|
-
coins: availableCoins.map(({ utxo }) => utxo),
|
|
78
|
-
initialImbalances: Imbalances.fromEntries(imbalances),
|
|
79
|
-
feeTokenType: '',
|
|
80
|
-
transactionCostModel: {
|
|
81
|
-
inputFeeOverhead: 0n,
|
|
82
|
-
outputFeeOverhead: 0n,
|
|
83
|
-
},
|
|
84
|
-
createOutput: (coin) => ({
|
|
85
|
-
...coin,
|
|
86
|
-
owner: addressHex,
|
|
87
|
-
}),
|
|
88
|
-
isCoinEqual: (a, b) => a.intentHash === b.intentHash && a.outputNo === b.outputNo,
|
|
89
|
-
}),
|
|
90
|
-
catch: (error) => {
|
|
91
|
-
const message = error instanceof Error ? error.message : error?.toString() || '';
|
|
92
|
-
return new TransactingError({ message });
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
// mark the coins as spent
|
|
96
|
-
const [spentInputs] = yield* CoreWallet.spendUtxos(wallet, inputs);
|
|
97
|
-
const ledgerInputs = spentInputs.map((input) => ({
|
|
98
|
-
...input,
|
|
99
|
-
intentHash: input.intentHash,
|
|
100
|
-
owner: publicKey,
|
|
101
|
-
}));
|
|
102
|
-
const counterOffer = yield* Either.try({
|
|
103
|
-
try: () => ledger.UnshieldedOffer.new(ledgerInputs, changeOutputs, []),
|
|
104
|
-
catch: (error) => new TransactingError({ message: 'Failed to create counter offer', cause: error }),
|
|
105
|
-
});
|
|
106
|
-
// NOTE: for the segment === 0 we insert the counter-offer into any intent's guaranteed section
|
|
107
|
-
if (segment !== GUARANTEED_SEGMENT) {
|
|
108
|
-
const intent = transaction.intents.get(segment);
|
|
109
|
-
const isBound = isIntentBound(intent);
|
|
110
|
-
if (!isBound && intent.fallibleUnshieldedOffer) {
|
|
111
|
-
const mergedOffer = yield* mergeCounterOffer(counterOffer, intent.fallibleUnshieldedOffer);
|
|
112
|
-
intent.fallibleUnshieldedOffer = mergedOffer;
|
|
113
|
-
transaction.intents = transaction.intents.set(segment, intent);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
// create a new offer if the intent is bound
|
|
117
|
-
const nextSegment = Math.max(...TransactionTrait.default.getSegments(transaction)) + 1;
|
|
118
|
-
const newIntent = ledger.Intent.new(intent.ttl);
|
|
119
|
-
newIntent.fallibleUnshieldedOffer = counterOffer;
|
|
120
|
-
transaction.intents = transaction.intents.set(nextSegment, newIntent);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
let ttl = new Date();
|
|
125
|
-
let updated = false;
|
|
126
|
-
// try to find and modify any unbound intent first
|
|
127
|
-
const segments = TransactionTrait.default.getSegments(transaction);
|
|
128
|
-
for (const segment of segments) {
|
|
129
|
-
const intent = transaction.intents.get(segment);
|
|
130
|
-
ttl = intent.ttl;
|
|
131
|
-
const isBound = isIntentBound(intent);
|
|
132
|
-
if (!isBound) {
|
|
133
|
-
const mergedOffer = yield* mergeCounterOffer(counterOffer, intent.guaranteedUnshieldedOffer);
|
|
134
|
-
intent.guaranteedUnshieldedOffer = mergedOffer;
|
|
135
|
-
transaction.intents = transaction.intents.set(segment, intent);
|
|
136
|
-
updated = true;
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// no unbound intents found, insert a new one
|
|
141
|
-
if (!updated) {
|
|
142
|
-
const nextSegment = Math.max(...segments) + 1;
|
|
143
|
-
const newIntent = ledger.Intent.new(ttl);
|
|
144
|
-
newIntent.guaranteedUnshieldedOffer = counterOffer;
|
|
145
|
-
transaction.intents = transaction.intents.set(nextSegment, newIntent);
|
|
146
|
-
}
|
|
67
|
+
// Ensure all intents are bound
|
|
68
|
+
const segments = this.txOps.getSegments(transaction);
|
|
69
|
+
for (const segment of segments) {
|
|
70
|
+
const intent = transaction.intents?.get(segment);
|
|
71
|
+
const isBound = this.txOps.isIntentBound(intent);
|
|
72
|
+
if (!isBound) {
|
|
73
|
+
return yield* Either.left(new TransactingError({ message: `Intent with id ${segment} is not bound` }));
|
|
147
74
|
}
|
|
148
75
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
76
|
+
// get the first intent so we can use its ttl to create the balancing intent
|
|
77
|
+
const intent = transaction.intents?.get(segments[0]);
|
|
78
|
+
const imbalances = this.txOps.getImbalances(transaction, GUARANTEED_SEGMENT);
|
|
79
|
+
// guaranteed section is balanced
|
|
80
|
+
if (imbalances.size === 0) {
|
|
81
|
+
return [undefined, wallet];
|
|
82
|
+
}
|
|
83
|
+
const recipe = yield* this.#balanceSegment(wallet, imbalances, Imbalances.empty(), this.getCoinSelection());
|
|
84
|
+
const { newState, offer } = yield* this.#prepareOffer(wallet, recipe);
|
|
85
|
+
const balancingIntent = ledger.Intent.new(intent.ttl);
|
|
86
|
+
balancingIntent.guaranteedUnshieldedOffer = offer;
|
|
87
|
+
return [ledger.Transaction.fromPartsRandomized(this.networkId, undefined, undefined, balancingIntent), newState];
|
|
153
88
|
});
|
|
154
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Makes a transfer transaction
|
|
92
|
+
* @param wallet - The wallet to make the transfer with
|
|
93
|
+
* @param outputs - The outputs for the transfer
|
|
94
|
+
* @param ttl - The TTL for the transaction
|
|
95
|
+
* @returns The balanced transfer transaction and the new wallet state if successful, otherwise an error
|
|
96
|
+
*/
|
|
155
97
|
makeTransfer(wallet, outputs, ttl) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
value: output.amount,
|
|
164
|
-
owner: UnshieldedAddress.codec
|
|
165
|
-
.decode(networkId, MidnightBech32m.parse(output.receiverAddress))
|
|
166
|
-
.data.toString('hex'),
|
|
167
|
-
type: output.type,
|
|
168
|
-
};
|
|
169
|
-
});
|
|
170
|
-
return Either.try({
|
|
171
|
-
try: () => {
|
|
172
|
-
const intent = ledger.Intent.new(ttl);
|
|
173
|
-
intent.guaranteedUnshieldedOffer = ledger.UnshieldedOffer.new([], ledgerOutputs, []);
|
|
98
|
+
return Either.gen(this, function* () {
|
|
99
|
+
const { networkId } = this;
|
|
100
|
+
const isValid = outputs.every((output) => output.amount > 0n);
|
|
101
|
+
if (!isValid) {
|
|
102
|
+
return yield* Either.left(new TransactingError({ message: 'The amount of all inputs needs to be positive' }));
|
|
103
|
+
}
|
|
104
|
+
const ledgerOutputs = outputs.map((output) => {
|
|
174
105
|
return {
|
|
175
|
-
|
|
176
|
-
|
|
106
|
+
value: output.amount,
|
|
107
|
+
owner: UnshieldedAddress.codec
|
|
108
|
+
.decode(networkId, MidnightBech32m.parse(output.receiverAddress))
|
|
109
|
+
.data.toString('hex'),
|
|
110
|
+
type: output.type,
|
|
177
111
|
};
|
|
178
|
-
}
|
|
179
|
-
|
|
112
|
+
});
|
|
113
|
+
const recipe = yield* this.#balanceSegment(wallet, Imbalances.empty(), Imbalances.fromEntries(ledgerOutputs.map((output) => [output.type, output.value])), this.getCoinSelection());
|
|
114
|
+
const { newState, offer } = yield* this.#prepareOffer(wallet, {
|
|
115
|
+
inputs: recipe.inputs,
|
|
116
|
+
outputs: [...recipe.outputs, ...ledgerOutputs],
|
|
117
|
+
});
|
|
118
|
+
const intent = ledger.Intent.new(ttl);
|
|
119
|
+
const hasNightOutput = ledgerOutputs.some((output) => output.type === ledger.nativeToken().raw);
|
|
120
|
+
if (hasNightOutput) {
|
|
121
|
+
intent.fallibleUnshieldedOffer = offer;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
intent.guaranteedUnshieldedOffer = offer;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
newState,
|
|
128
|
+
transaction: ledger.Transaction.fromParts(networkId, undefined, undefined, intent),
|
|
129
|
+
};
|
|
180
130
|
});
|
|
181
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Initializes a swap transaction
|
|
134
|
+
* @param wallet - The wallet to initialize the swap for
|
|
135
|
+
* @param desiredInputs - The desired inputs for the swap
|
|
136
|
+
* @param desiredOutputs - The desired outputs for the swap
|
|
137
|
+
* @param ttl - The TTL for the swap
|
|
138
|
+
* @returns The initialized swap transaction and the new wallet state if successful, otherwise an error
|
|
139
|
+
*/
|
|
182
140
|
initSwap(wallet, desiredInputs, desiredOutputs, ttl) {
|
|
183
141
|
return Either.gen(this, function* () {
|
|
184
|
-
const networkId = this
|
|
142
|
+
const { networkId } = this;
|
|
185
143
|
const outputsValid = desiredOutputs.every((output) => output.amount > 0n);
|
|
186
144
|
if (!outputsValid) {
|
|
187
|
-
return yield* Either.left(new TransactingError({ message: 'The amount needs to be positive' }));
|
|
145
|
+
return yield* Either.left(new TransactingError({ message: 'The amount of all outputs needs to be positive' }));
|
|
188
146
|
}
|
|
189
147
|
const inputsValid = Object.entries(desiredInputs).every(([, amount]) => amount > 0n);
|
|
190
148
|
if (!inputsValid) {
|
|
191
|
-
return yield* Either.left(new TransactingError({ message: 'The
|
|
149
|
+
return yield* Either.left(new TransactingError({ message: 'The amount of all inputs needs to be positive' }));
|
|
192
150
|
}
|
|
193
151
|
const ledgerOutputs = desiredOutputs.map((output) => ({
|
|
194
152
|
value: output.amount,
|
|
@@ -198,55 +156,155 @@ export class TransactingCapabilityImplementation {
|
|
|
198
156
|
type: output.type,
|
|
199
157
|
}));
|
|
200
158
|
const targetImbalances = Imbalances.fromEntries(Object.entries(desiredInputs));
|
|
201
|
-
const
|
|
202
|
-
const {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
initialImbalances: Imbalances.empty(),
|
|
206
|
-
feeTokenType: '',
|
|
207
|
-
transactionCostModel: {
|
|
208
|
-
inputFeeOverhead: 0n,
|
|
209
|
-
outputFeeOverhead: 0n,
|
|
210
|
-
},
|
|
211
|
-
createOutput: (coin) => ({
|
|
212
|
-
...coin,
|
|
213
|
-
owner: wallet.publicKey.addressHex,
|
|
214
|
-
}),
|
|
215
|
-
isCoinEqual: (a, b) => a.intentHash === b.intentHash && a.outputNo === b.outputNo,
|
|
216
|
-
targetImbalances,
|
|
217
|
-
}),
|
|
218
|
-
catch: (error) => {
|
|
219
|
-
const message = error instanceof Error ? error.message : error?.toString() || '';
|
|
220
|
-
return new TransactingError({ message });
|
|
221
|
-
},
|
|
159
|
+
const recipe = yield* this.#balanceSegment(wallet, Imbalances.empty(), targetImbalances, this.getCoinSelection());
|
|
160
|
+
const { newState, offer } = yield* this.#prepareOffer(wallet, {
|
|
161
|
+
inputs: recipe.inputs,
|
|
162
|
+
outputs: [...recipe.outputs, ...ledgerOutputs],
|
|
222
163
|
});
|
|
223
|
-
const [spentInputs, updatedWallet] = yield* CoreWallet.spendUtxos(wallet, inputs);
|
|
224
|
-
const ledgerInputs = spentInputs.map((input) => ({
|
|
225
|
-
...input,
|
|
226
|
-
owner: wallet.publicKey.publicKey,
|
|
227
|
-
}));
|
|
228
|
-
const offer = ledger.UnshieldedOffer.new(ledgerInputs, [...changeOutputs, ...ledgerOutputs], []);
|
|
229
164
|
const intent = ledger.Intent.new(ttl);
|
|
230
165
|
intent.guaranteedUnshieldedOffer = offer;
|
|
231
166
|
const tx = ledger.Transaction.fromParts(networkId, undefined, undefined, intent);
|
|
232
167
|
return {
|
|
233
|
-
newState
|
|
168
|
+
newState,
|
|
234
169
|
transaction: tx,
|
|
235
170
|
};
|
|
236
171
|
});
|
|
237
172
|
}
|
|
238
173
|
signTransaction(transaction, signSegment) {
|
|
239
|
-
return Either.gen(function* () {
|
|
240
|
-
const segments =
|
|
174
|
+
return Either.gen(this, function* () {
|
|
175
|
+
const segments = this.txOps.getSegments(transaction);
|
|
241
176
|
if (!segments.length) {
|
|
242
177
|
throw new SignError({ message: 'No segments found in the provided transaction' });
|
|
243
178
|
}
|
|
244
179
|
for (const segment of segments) {
|
|
245
|
-
const signedData = yield*
|
|
180
|
+
const signedData = yield* this.txOps.getSignatureData(transaction, segment);
|
|
246
181
|
const signature = signSegment(signedData);
|
|
247
|
-
transaction = yield*
|
|
182
|
+
transaction = yield* this.txOps.addSignature(transaction, signature, segment);
|
|
248
183
|
}
|
|
249
184
|
return transaction;
|
|
250
185
|
});
|
|
251
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Balances a segment of a transaction
|
|
189
|
+
* @param wallet - The wallet to balance the segment for
|
|
190
|
+
* @param imbalances - The imbalances to balance the segment for
|
|
191
|
+
* @param targetImbalances - The target imbalances to balance the segment for
|
|
192
|
+
* @param coinSelection - The coin selection to use for the balance recipe
|
|
193
|
+
* @returns The balance recipe if successful, otherwise an error
|
|
194
|
+
*/
|
|
195
|
+
#balanceSegment(wallet, imbalances, targetImbalances, coinSelection) {
|
|
196
|
+
return Either.try({
|
|
197
|
+
try: () => getBalanceRecipe({
|
|
198
|
+
coins: this.getCoins()
|
|
199
|
+
.getAvailableCoins(wallet)
|
|
200
|
+
.map(({ utxo }) => utxo),
|
|
201
|
+
initialImbalances: imbalances,
|
|
202
|
+
feeTokenType: '',
|
|
203
|
+
transactionCostModel: {
|
|
204
|
+
inputFeeOverhead: 0n,
|
|
205
|
+
outputFeeOverhead: 0n,
|
|
206
|
+
},
|
|
207
|
+
coinSelection,
|
|
208
|
+
createOutput: (coin) => ({
|
|
209
|
+
...coin,
|
|
210
|
+
owner: wallet.publicKey.addressHex,
|
|
211
|
+
}),
|
|
212
|
+
isCoinEqual: (a, b) => a.intentHash === b.intentHash && a.outputNo === b.outputNo,
|
|
213
|
+
targetImbalances,
|
|
214
|
+
}),
|
|
215
|
+
catch: (err) => {
|
|
216
|
+
if (err instanceof BalancingInsufficientFundsError) {
|
|
217
|
+
return new InsufficientFundsError({
|
|
218
|
+
message: 'Insufficient funds',
|
|
219
|
+
tokenType: err.tokenType,
|
|
220
|
+
amount: imbalances.get(err.tokenType) ?? 0n,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
return new OtherWalletError({
|
|
225
|
+
message: 'Balancing unshielded segment failed',
|
|
226
|
+
cause: err,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Prepares an offer for a given balance recipe
|
|
234
|
+
* @param wallet - The wallet to prepare the offer for
|
|
235
|
+
* @param balanceRecipe - The balance recipe to prepare the offer for
|
|
236
|
+
* @returns The prepared offer and the new wallet state if successful, otherwise an error
|
|
237
|
+
*/
|
|
238
|
+
#prepareOffer(wallet, balanceRecipe) {
|
|
239
|
+
return Either.gen(function* () {
|
|
240
|
+
const [spentInputs, updatedWallet] = yield* CoreWallet.spendUtxos(wallet, balanceRecipe.inputs);
|
|
241
|
+
const { publicKey } = wallet.publicKey;
|
|
242
|
+
const ledgerInputs = spentInputs.map((input) => ({
|
|
243
|
+
...input,
|
|
244
|
+
intentHash: input.intentHash,
|
|
245
|
+
owner: publicKey,
|
|
246
|
+
}));
|
|
247
|
+
const counterOffer = yield* Either.try({
|
|
248
|
+
try: () => ledger.UnshieldedOffer.new(ledgerInputs, [...balanceRecipe.outputs], []),
|
|
249
|
+
catch: (error) => new TransactingError({ message: 'Failed to create counter offer', cause: error }),
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
newState: updatedWallet,
|
|
253
|
+
offer: counterOffer,
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
#mergeOffers(offerA, offerB) {
|
|
258
|
+
return pipe(Option.fromNullable(offerB), Option.match({
|
|
259
|
+
onNone: () => Either.right(offerA),
|
|
260
|
+
onSome: (offerB) => Either.try({
|
|
261
|
+
try: () => ledger.UnshieldedOffer.new([...offerB.inputs, ...offerA.inputs], [...offerB.outputs, ...offerA.outputs], [...offerB.signatures, ...offerA.signatures]),
|
|
262
|
+
catch: (error) => new TransactingError({ message: 'Failed to merge offers', cause: error }),
|
|
263
|
+
}),
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Balances an unboundish (unproven or unbound) transaction
|
|
268
|
+
* @param wallet - The wallet to balance the transaction with
|
|
269
|
+
* @param transaction - The transaction to balance
|
|
270
|
+
* @returns The balanced transaction and the new wallet state if successful, otherwise an error
|
|
271
|
+
*/
|
|
272
|
+
#balanceUnboundishTransaction(wallet, transaction) {
|
|
273
|
+
return Either.gen(this, function* () {
|
|
274
|
+
const segments = this.txOps.getSegments(transaction);
|
|
275
|
+
// no segments to balance
|
|
276
|
+
if (segments.length === 0) {
|
|
277
|
+
return [undefined, wallet];
|
|
278
|
+
}
|
|
279
|
+
for (const segment of [...segments, GUARANTEED_SEGMENT]) {
|
|
280
|
+
const imbalances = this.txOps.getImbalances(transaction, segment);
|
|
281
|
+
// intent is balanced
|
|
282
|
+
if (imbalances.size === 0) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
// if segment is GUARANTEED_SEGMENT, use the first intent to place the balancing offer in the guaranteed section
|
|
286
|
+
const intentSegment = segment === GUARANTEED_SEGMENT ? segments[0] : segment;
|
|
287
|
+
const intent = transaction.intents?.get(intentSegment);
|
|
288
|
+
if (!intent) {
|
|
289
|
+
return yield* Either.left(new TransactingError({ message: `Intent with id ${segment} was not found` }));
|
|
290
|
+
}
|
|
291
|
+
const isBound = this.txOps.isIntentBound(intent);
|
|
292
|
+
if (isBound) {
|
|
293
|
+
return yield* Either.left(new TransactingError({ message: `Intent with id ${segment} is already bound` }));
|
|
294
|
+
}
|
|
295
|
+
const recipe = yield* this.#balanceSegment(wallet, imbalances, Imbalances.empty(), this.getCoinSelection());
|
|
296
|
+
const { offer } = yield* this.#prepareOffer(wallet, recipe);
|
|
297
|
+
const targetOffer = segment !== GUARANTEED_SEGMENT ? intent.fallibleUnshieldedOffer : intent.guaranteedUnshieldedOffer;
|
|
298
|
+
const mergedOffer = yield* this.#mergeOffers(offer, targetOffer);
|
|
299
|
+
if (segment !== GUARANTEED_SEGMENT) {
|
|
300
|
+
intent.fallibleUnshieldedOffer = mergedOffer;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
intent.guaranteedUnshieldedOffer = mergedOffer;
|
|
304
|
+
}
|
|
305
|
+
transaction.intents = transaction.intents.set(intentSegment, intent);
|
|
306
|
+
}
|
|
307
|
+
return [transaction, wallet];
|
|
308
|
+
});
|
|
309
|
+
}
|
|
252
310
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Either } from 'effect';
|
|
2
|
+
import { Imbalances } from '@midnight-ntwrk/wallet-sdk-capabilities';
|
|
3
|
+
import * as ledger from '@midnight-ntwrk/ledger-v7';
|
|
4
|
+
import { WalletError } from './WalletError.js';
|
|
5
|
+
/**
|
|
6
|
+
* Unbound transaction type. This is a transaction that has no signatures and is not bound yet.
|
|
7
|
+
*/
|
|
8
|
+
export type UnboundTransaction = ledger.Transaction<ledger.SignatureEnabled, ledger.Proof, ledger.PreBinding>;
|
|
9
|
+
/**
|
|
10
|
+
* Utility type to extract the Intent type from a Transaction type.
|
|
11
|
+
* Maps Transaction<S, P, B> to Intent<S, P, B>.
|
|
12
|
+
*/
|
|
13
|
+
export type IntentOf<T> = T extends ledger.Transaction<infer S, infer P, infer B> ? ledger.Intent<S, P, B> : never;
|
|
14
|
+
export type TransactionOps = {
|
|
15
|
+
getSignatureData: (transaction: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.PreBinding>, segment: number) => Either.Either<Uint8Array, WalletError>;
|
|
16
|
+
getSegments(transaction: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>): number[];
|
|
17
|
+
addSignature(transaction: ledger.UnprovenTransaction | UnboundTransaction, signature: ledger.Signature, segment: number): Either.Either<ledger.UnprovenTransaction, WalletError>;
|
|
18
|
+
getImbalances(transaction: ledger.FinalizedTransaction | UnboundTransaction | ledger.UnprovenTransaction, segment: number): Imbalances;
|
|
19
|
+
addSignaturesToOffer(offer: ledger.UnshieldedOffer<ledger.SignatureEnabled>, signature: ledger.Signature, segment: number, offerType: 'guaranteed' | 'fallible'): Either.Either<ledger.UnshieldedOffer<ledger.SignatureEnabled>, WalletError>;
|
|
20
|
+
isIntentBound(intent: ledger.Intent<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>): boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare const TransactionOps: TransactionOps;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// This file is part of MIDNIGHT-WALLET-SDK.
|
|
2
|
+
// Copyright (C) 2025 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 { Either, pipe, Array as Arr } from 'effect';
|
|
14
|
+
import { Imbalances } from '@midnight-ntwrk/wallet-sdk-capabilities';
|
|
15
|
+
import * as ledger from '@midnight-ntwrk/ledger-v7';
|
|
16
|
+
import { TransactingError } from './WalletError.js';
|
|
17
|
+
export const TransactionOps = {
|
|
18
|
+
getSignatureData(tx, segment) {
|
|
19
|
+
if (!tx.intents) {
|
|
20
|
+
return Either.left(new TransactingError({ message: 'Transaction has no intents' }));
|
|
21
|
+
}
|
|
22
|
+
const intent = tx.intents.get(segment);
|
|
23
|
+
if (!intent) {
|
|
24
|
+
return Either.left(new TransactingError({ message: `Intent with segment ${segment} was not found` }));
|
|
25
|
+
}
|
|
26
|
+
return Either.try({
|
|
27
|
+
try: () => intent.signatureData(segment),
|
|
28
|
+
catch: (error) => new TransactingError({ message: 'Failed to get offer signature data', cause: error }),
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
getSegments(transaction) {
|
|
32
|
+
return transaction.intents?.keys().toArray() ?? [];
|
|
33
|
+
},
|
|
34
|
+
addSignature(transaction, signature, segment) {
|
|
35
|
+
return Either.gen(function* () {
|
|
36
|
+
if (!transaction.intents || transaction.intents.size === 0) {
|
|
37
|
+
return yield* Either.left(new TransactingError({ message: 'No intents found in the transaction' }));
|
|
38
|
+
}
|
|
39
|
+
const originalIntent = yield* Either.fromNullable(transaction.intents?.get(segment), () => new TransactingError({ message: 'Intent with a given segment was not found' }));
|
|
40
|
+
if (TransactionOps.isIntentBound(originalIntent)) {
|
|
41
|
+
return yield* Either.left(new TransactingError({ message: `Intent at segment ${segment} is already bound` }));
|
|
42
|
+
}
|
|
43
|
+
const clonedIntent = yield* Either.try({
|
|
44
|
+
try: () => ledger.Intent.deserialize('signature', 'pre-proof', 'pre-binding', originalIntent.serialize()),
|
|
45
|
+
catch: (error) => new TransactingError({ message: 'Failed to clone intent', cause: error }),
|
|
46
|
+
});
|
|
47
|
+
if (clonedIntent.fallibleUnshieldedOffer) {
|
|
48
|
+
clonedIntent.fallibleUnshieldedOffer = yield* TransactionOps.addSignaturesToOffer(clonedIntent.fallibleUnshieldedOffer, signature, segment, 'fallible');
|
|
49
|
+
}
|
|
50
|
+
if (clonedIntent.guaranteedUnshieldedOffer) {
|
|
51
|
+
clonedIntent.guaranteedUnshieldedOffer = yield* TransactionOps.addSignaturesToOffer(clonedIntent.guaranteedUnshieldedOffer, signature, segment, 'guaranteed');
|
|
52
|
+
}
|
|
53
|
+
transaction.intents = transaction.intents?.set(segment, clonedIntent);
|
|
54
|
+
return transaction;
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
getImbalances(transaction, segment) {
|
|
58
|
+
const imbalances = transaction
|
|
59
|
+
.imbalances(segment)
|
|
60
|
+
.entries()
|
|
61
|
+
.filter(([token, value]) => token.tag === 'unshielded' && value !== 0n)
|
|
62
|
+
.map(([token, value]) => [token.raw.toString(), value])
|
|
63
|
+
.toArray();
|
|
64
|
+
return Imbalances.fromEntries(imbalances);
|
|
65
|
+
},
|
|
66
|
+
addSignaturesToOffer(offer, signature, segment, offerType) {
|
|
67
|
+
return pipe(offer.inputs, Arr.map((_, i) => offer.signatures.at(i) ?? signature), (signatures) => Either.try({
|
|
68
|
+
try: () => offer.addSignatures(signatures),
|
|
69
|
+
catch: (error) => new TransactingError({
|
|
70
|
+
message: `Failed to add ${offerType} signature at segment ${segment}`,
|
|
71
|
+
cause: error,
|
|
72
|
+
}),
|
|
73
|
+
}));
|
|
74
|
+
},
|
|
75
|
+
isIntentBound(intent) {
|
|
76
|
+
return intent.binding.instance === 'binding';
|
|
77
|
+
},
|
|
78
|
+
};
|
package/dist/v1/V1Builder.d.ts
CHANGED
|
@@ -20,39 +20,37 @@ export type DefaultV1Configuration = BaseV1Configuration & DefaultSyncConfigurat
|
|
|
20
20
|
declare const V1BuilderSymbol: {
|
|
21
21
|
readonly typeId: unique symbol;
|
|
22
22
|
};
|
|
23
|
-
export type V1Variant<TSerialized, TSyncUpdate
|
|
23
|
+
export type V1Variant<TSerialized, TSyncUpdate> = Variant.Variant<typeof V1Tag, CoreWallet, null, RunningV1Variant<TSerialized, TSyncUpdate>> & {
|
|
24
24
|
deserializeState: (serialized: TSerialized) => Either.Either<CoreWallet, WalletError>;
|
|
25
25
|
coinsAndBalances: CoinsAndBalancesCapability<CoreWallet>;
|
|
26
26
|
keys: KeysCapability<CoreWallet>;
|
|
27
27
|
serialization: SerializationCapability<CoreWallet, TSerialized>;
|
|
28
28
|
transactionHistory: TransactionHistoryService<UnshieldedUpdate>;
|
|
29
29
|
};
|
|
30
|
-
export type AnyV1Variant = V1Variant<any, any
|
|
31
|
-
export type DefaultV1Variant = V1Variant<string, WalletSyncUpdate
|
|
32
|
-
export type TransactionOf<T extends AnyV1Variant> = T extends V1Variant<any, any,
|
|
33
|
-
export type SerializedStateOf<T extends AnyV1Variant> = T extends V1Variant<infer TSerialized, any
|
|
34
|
-
export type DefaultV1Builder = V1Builder<DefaultV1Configuration, RunningV1Variant.Context<string, WalletSyncUpdate
|
|
35
|
-
export declare class V1Builder<TConfig extends BaseV1Configuration = BaseV1Configuration, TContext extends Partial<RunningV1Variant.AnyContext> = object, TSerialized = never, TSyncUpdate = never
|
|
30
|
+
export type AnyV1Variant = V1Variant<any, any>;
|
|
31
|
+
export type DefaultV1Variant = V1Variant<string, WalletSyncUpdate>;
|
|
32
|
+
export type TransactionOf<T extends AnyV1Variant> = T extends V1Variant<any, any> ? ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish> : never;
|
|
33
|
+
export type SerializedStateOf<T extends AnyV1Variant> = T extends V1Variant<infer TSerialized, any> ? TSerialized : never;
|
|
34
|
+
export type DefaultV1Builder = V1Builder<DefaultV1Configuration, RunningV1Variant.Context<string, WalletSyncUpdate>, string, WalletSyncUpdate>;
|
|
35
|
+
export declare class V1Builder<TConfig extends BaseV1Configuration = BaseV1Configuration, TContext extends Partial<RunningV1Variant.AnyContext> = object, TSerialized = never, TSyncUpdate = never> implements VariantBuilder.VariantBuilder<V1Variant<TSerialized, TSyncUpdate>, TConfig> {
|
|
36
36
|
#private;
|
|
37
|
-
constructor(buildState?: V1Builder.PartialBuildState<TConfig, TContext, TSerialized, TSyncUpdate
|
|
37
|
+
constructor(buildState?: V1Builder.PartialBuildState<TConfig, TContext, TSerialized, TSyncUpdate>);
|
|
38
38
|
withDefaults(): DefaultV1Builder;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
withKeys<TKeysConfig, TKeysContext extends Partial<RunningV1Variant.AnyContext>>(keysCapability: (configuration: TKeysConfig, getContext: () => TKeysContext) => KeysCapability<CoreWallet>): V1Builder<TConfig & TKeysConfig, TContext & TKeysContext, TSerialized, TSyncUpdate, TTransaction>;
|
|
55
|
-
build(this: V1Builder<TConfig, RunningV1Variant.Context<TSerialized, TSyncUpdate, TTransaction>, TSerialized, TSyncUpdate, TTransaction>, configuration: TConfig): V1Variant<TSerialized, TSyncUpdate, TTransaction>;
|
|
39
|
+
withSyncDefaults(): V1Builder<TConfig & DefaultSyncConfiguration, TContext & DefaultSyncContext, TSerialized, WalletSyncUpdate>;
|
|
40
|
+
withSync<TSyncConfig, TSyncContext extends Partial<RunningV1Variant.AnyContext>, TSyncUpdate>(syncService: (configuration: TSyncConfig, getContext: () => TSyncContext) => SyncService<CoreWallet, TSyncUpdate>, syncCapability: (configuration: TSyncConfig, getContext: () => TSyncContext) => SyncCapability<CoreWallet, TSyncUpdate>): V1Builder<TConfig & TSyncConfig, TContext & TSyncContext, TSerialized, TSyncUpdate>;
|
|
41
|
+
withSerializationDefaults(): V1Builder<TConfig, TContext, string, TSyncUpdate>;
|
|
42
|
+
withSerialization<TSerializationConfig, TSerializationContext extends Partial<RunningV1Variant.AnyContext>, TSerialized>(serializationCapability: (configuration: TSerializationConfig, getContext: () => TSerializationContext) => SerializationCapability<CoreWallet, TSerialized>): V1Builder<TConfig & TSerializationConfig, TContext & TSerializationContext, TSerialized, TSyncUpdate>;
|
|
43
|
+
withTransactingDefaults(this: V1Builder<TConfig, TContext, TSerialized, TSyncUpdate>): V1Builder<TConfig & DefaultTransactingConfiguration, TContext & DefaultTransactingContext, TSerialized, TSyncUpdate>;
|
|
44
|
+
withTransacting<TTransactingConfig, TTransactingContext extends Partial<RunningV1Variant.AnyContext>>(transactingCapability: (config: TTransactingConfig, getContext: () => TTransactingContext) => TransactingCapability<CoreWallet>): V1Builder<TConfig & TTransactingConfig, TContext & TTransactingContext, TSerialized, TSyncUpdate>;
|
|
45
|
+
withCoinSelection<TCoinSelectionConfig, TCoinSelectionContext extends Partial<RunningV1Variant.AnyContext>>(coinSelection: (config: TCoinSelectionConfig, getContext: () => TCoinSelectionContext) => CoinSelection<ledger.Utxo>): V1Builder<TConfig & TCoinSelectionConfig, TContext & TCoinSelectionContext, TSerialized, TSyncUpdate>;
|
|
46
|
+
withCoinSelectionDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate>;
|
|
47
|
+
withCoinsAndBalancesDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate>;
|
|
48
|
+
withCoinsAndBalances<TBalancesConfig, TBalancesContext extends Partial<RunningV1Variant.AnyContext>>(coinsAndBalancesCapability: (configuration: TBalancesConfig, getContext: () => TBalancesContext) => CoinsAndBalancesCapability<CoreWallet>): V1Builder<TConfig & TBalancesConfig, TContext & TBalancesContext, TSerialized, TSyncUpdate>;
|
|
49
|
+
withTransactionHistoryDefaults(this: V1Builder<TConfig, TContext, TSerialized, TSyncUpdate>): V1Builder<TConfig & DefaultTransactionHistoryConfiguration, TContext, TSerialized, TSyncUpdate>;
|
|
50
|
+
withTransactionHistory<TTransactionHistoryConfig, TTransactionHistoryContext extends Partial<RunningV1Variant.AnyContext>>(transactionHistoryService: (configuration: TTransactionHistoryConfig, getContext: () => TTransactionHistoryContext) => TransactionHistoryService<UnshieldedUpdate>): V1Builder<TConfig & TTransactionHistoryConfig, TContext & TTransactionHistoryContext, TSerialized, TSyncUpdate>;
|
|
51
|
+
withKeysDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate>;
|
|
52
|
+
withKeys<TKeysConfig, TKeysContext extends Partial<RunningV1Variant.AnyContext>>(keysCapability: (configuration: TKeysConfig, getContext: () => TKeysContext) => KeysCapability<CoreWallet>): V1Builder<TConfig & TKeysConfig, TContext & TKeysContext, TSerialized, TSyncUpdate>;
|
|
53
|
+
build(this: V1Builder<TConfig, RunningV1Variant.Context<TSerialized, TSyncUpdate>, TSerialized, TSyncUpdate>, configuration: TConfig): V1Variant<TSerialized, TSyncUpdate>;
|
|
56
54
|
}
|
|
57
55
|
/** @internal */
|
|
58
56
|
declare namespace V1Builder {
|
|
@@ -60,8 +58,8 @@ declare namespace V1Builder {
|
|
|
60
58
|
readonly syncService: (configuration: TConfig, getContext: () => TContext) => SyncService<CoreWallet, TSyncUpdate>;
|
|
61
59
|
readonly syncCapability: (configuration: TConfig, getContext: () => TContext) => SyncCapability<CoreWallet, TSyncUpdate>;
|
|
62
60
|
};
|
|
63
|
-
type HasTransacting<TConfig, TContext
|
|
64
|
-
readonly transactingCapability: (configuration: TConfig, getContext: () => TContext) => TransactingCapability<
|
|
61
|
+
type HasTransacting<TConfig, TContext> = {
|
|
62
|
+
readonly transactingCapability: (configuration: TConfig, getContext: () => TContext) => TransactingCapability<CoreWallet>;
|
|
65
63
|
};
|
|
66
64
|
type HasCoinSelection<TConfig, TContext> = {
|
|
67
65
|
readonly coinSelection: (configuration: TConfig, getContext: () => TContext) => CoinSelection<ledger.Utxo>;
|
|
@@ -81,9 +79,9 @@ declare namespace V1Builder {
|
|
|
81
79
|
/**
|
|
82
80
|
* The internal build state of {@link V1Builder}.
|
|
83
81
|
*/
|
|
84
|
-
type FullBuildState<TConfig, TContext, TSerialized, TSyncUpdate
|
|
85
|
-
type PartialBuildState<TConfig = object, TContext = object, TSerialized = never, TSyncUpdate = never
|
|
86
|
-
[K in keyof FullBuildState<never, never, never, never
|
|
82
|
+
type FullBuildState<TConfig, TContext, TSerialized, TSyncUpdate> = Types.Simplify<HasSync<TConfig, TContext, TSyncUpdate> & HasSerialization<TConfig, TContext, TSerialized> & HasTransacting<TConfig, TContext> & HasCoinSelection<TConfig, TContext> & HasCoinsAndBalances<TConfig, TContext> & HasKeys<TConfig, TContext> & HasTransactionHistory<TConfig, TContext>>;
|
|
83
|
+
type PartialBuildState<TConfig = object, TContext = object, TSerialized = never, TSyncUpdate = never> = {
|
|
84
|
+
[K in keyof FullBuildState<never, never, never, never>]?: FullBuildState<TConfig, TContext, TSerialized, TSyncUpdate>[K] | undefined;
|
|
87
85
|
};
|
|
88
86
|
/**
|
|
89
87
|
* Utility interface that manages the type variance of {@link V1Builder}.
|
package/dist/v1/V1Builder.js
CHANGED
|
@@ -19,8 +19,7 @@ export class V1Builder {
|
|
|
19
19
|
this.#buildState = buildState;
|
|
20
20
|
}
|
|
21
21
|
withDefaults() {
|
|
22
|
-
return this.
|
|
23
|
-
.withSyncDefaults()
|
|
22
|
+
return this.withSyncDefaults()
|
|
24
23
|
.withSerializationDefaults()
|
|
25
24
|
.withTransactingDefaults()
|
|
26
25
|
.withCoinsAndBalancesDefaults()
|
|
@@ -28,16 +27,6 @@ export class V1Builder {
|
|
|
28
27
|
.withKeysDefaults()
|
|
29
28
|
.withCoinSelectionDefaults();
|
|
30
29
|
}
|
|
31
|
-
withTransactionType() {
|
|
32
|
-
return new V1Builder({
|
|
33
|
-
...this.#buildState,
|
|
34
|
-
transactingCapability: undefined,
|
|
35
|
-
transactionHistoryService: undefined,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
withDefaultTransactionType() {
|
|
39
|
-
return this.withTransactionType();
|
|
40
|
-
}
|
|
41
30
|
withSyncDefaults() {
|
|
42
31
|
return this.withSync(makeDefaultSyncService, makeDefaultSyncCapability);
|
|
43
32
|
}
|
package/dist/v1/index.d.ts
CHANGED
|
@@ -10,6 +10,6 @@ export * from './RunningV1Variant.js';
|
|
|
10
10
|
export * as Simulator from './Simulator.js';
|
|
11
11
|
export * as WalletError from './WalletError.js';
|
|
12
12
|
export * from './CoreWallet.js';
|
|
13
|
-
export * from './
|
|
13
|
+
export * from './TransactionOps.js';
|
|
14
14
|
export * as TransactionHistoryStorage from '../storage/index.js';
|
|
15
15
|
export * as UnshieldedState from './UnshieldedState.js';
|
package/dist/v1/index.js
CHANGED
|
@@ -22,6 +22,6 @@ export * from './RunningV1Variant.js';
|
|
|
22
22
|
export * as Simulator from './Simulator.js';
|
|
23
23
|
export * as WalletError from './WalletError.js';
|
|
24
24
|
export * from './CoreWallet.js';
|
|
25
|
-
export * from './
|
|
25
|
+
export * from './TransactionOps.js';
|
|
26
26
|
export * as TransactionHistoryStorage from '../storage/index.js';
|
|
27
27
|
export * as UnshieldedState from './UnshieldedState.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midnight-ntwrk/wallet-sdk-unshielded-wallet",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@midnight-ntwrk/ledger-v7": "7.0.0-
|
|
26
|
+
"@midnight-ntwrk/ledger-v7": "7.0.0-rc.1",
|
|
27
27
|
"@midnight-ntwrk/wallet-sdk-abstractions": "1.0.0-beta.10",
|
|
28
|
-
"@midnight-ntwrk/wallet-sdk-address-format": "3.0.0-beta.
|
|
29
|
-
"@midnight-ntwrk/wallet-sdk-capabilities": "3.0.0-beta.
|
|
28
|
+
"@midnight-ntwrk/wallet-sdk-address-format": "3.0.0-beta.11",
|
|
29
|
+
"@midnight-ntwrk/wallet-sdk-capabilities": "3.0.0-beta.11",
|
|
30
30
|
"@midnight-ntwrk/wallet-sdk-hd": "3.0.0-beta.8",
|
|
31
|
-
"@midnight-ntwrk/wallet-sdk-indexer-client": "1.0.0-beta.
|
|
32
|
-
"@midnight-ntwrk/wallet-sdk-utilities": "1.0.0-beta.
|
|
33
|
-
"effect": "^3.
|
|
31
|
+
"@midnight-ntwrk/wallet-sdk-indexer-client": "1.0.0-beta.16",
|
|
32
|
+
"@midnight-ntwrk/wallet-sdk-utilities": "1.0.0-beta.10",
|
|
33
|
+
"effect": "^3.19.14",
|
|
34
34
|
"rxjs": "^7.5"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"eslint": "^9.37.0",
|
|
49
|
+
"fast-check": "^4.5.3",
|
|
49
50
|
"prettier": "^3.7.0",
|
|
50
51
|
"publint": "~0.3.14",
|
|
51
52
|
"rimraf": "^6.0.1",
|
package/dist/v1/Transaction.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Either } from 'effect';
|
|
2
|
-
import { Imbalances } from '@midnight-ntwrk/wallet-sdk-capabilities';
|
|
3
|
-
import { TransactionImbalances } from './TransactionImbalances.js';
|
|
4
|
-
import * as ledger from '@midnight-ntwrk/ledger-v7';
|
|
5
|
-
import { TransactingError, WalletError } from './WalletError.js';
|
|
6
|
-
export declare const isIntentBound: (intent: ledger.Intent<ledger.Signaturish, ledger.Proofish, ledger.Bindingish>) => boolean;
|
|
7
|
-
export type TransactionTrait<Tx> = {
|
|
8
|
-
id(tx: Tx): string;
|
|
9
|
-
getOfferSignatureData: (transaction: Tx, segment: number) => Either.Either<Uint8Array, WalletError>;
|
|
10
|
-
getSegments(transaction: Tx): number[];
|
|
11
|
-
addOfferSignature(transaction: Tx, signature: ledger.Signature, segment: number): Either.Either<Tx, WalletError>;
|
|
12
|
-
bindTransaction(transaction: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.PreBinding>): Either.Either<ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Binding>, WalletError>;
|
|
13
|
-
};
|
|
14
|
-
export declare const TransactionTrait: {
|
|
15
|
-
default: TransactionTrait<ledger.UnprovenTransaction>;
|
|
16
|
-
shared: {
|
|
17
|
-
getOfferSignatureData(tx: ledger.UnprovenTransaction, segment: number): Either.Either<Uint8Array<ArrayBufferLike>, WalletError>;
|
|
18
|
-
addOfferSignature(transaction: ledger.UnprovenTransaction, signature: ledger.Signature, segment?: number): Either.Either<ledger.UnprovenTransaction, WalletError>;
|
|
19
|
-
getSegments(transaction: ledger.UnprovenTransaction): number[];
|
|
20
|
-
bindTransaction(transaction: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.PreBinding>): Either.Either<ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Binding>, TransactingError>;
|
|
21
|
-
getImbalances(tx: ledger.FinalizedTransaction | ledger.UnprovenTransaction | ledger.ProofErasedTransaction): TransactionImbalances;
|
|
22
|
-
getGuaranteedImbalances: (tx: ledger.FinalizedTransaction | ledger.UnprovenTransaction | ledger.ProofErasedTransaction) => Imbalances;
|
|
23
|
-
getFallibleImbalances: (tx: ledger.FinalizedTransaction | ledger.UnprovenTransaction | ledger.ProofErasedTransaction) => Imbalances;
|
|
24
|
-
};
|
|
25
|
-
};
|
package/dist/v1/Transaction.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
// This file is part of MIDNIGHT-WALLET-SDK.
|
|
2
|
-
// Copyright (C) 2025 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 { Either, pipe } from 'effect';
|
|
14
|
-
import { Imbalances } from '@midnight-ntwrk/wallet-sdk-capabilities';
|
|
15
|
-
import * as ledger from '@midnight-ntwrk/ledger-v7';
|
|
16
|
-
import { TransactingError } from './WalletError.js';
|
|
17
|
-
export const isIntentBound = (intent) => {
|
|
18
|
-
return intent.binding instanceof ledger.Binding;
|
|
19
|
-
};
|
|
20
|
-
export const TransactionTrait = new (class {
|
|
21
|
-
default = {
|
|
22
|
-
getOfferSignatureData(tx, segment) {
|
|
23
|
-
return TransactionTrait.shared.getOfferSignatureData(tx, segment);
|
|
24
|
-
},
|
|
25
|
-
getSegments(tx) {
|
|
26
|
-
return TransactionTrait.shared.getSegments(tx);
|
|
27
|
-
},
|
|
28
|
-
addOfferSignature(transaction, signature, segment) {
|
|
29
|
-
return TransactionTrait.shared.addOfferSignature(transaction, signature, segment);
|
|
30
|
-
},
|
|
31
|
-
bindTransaction(transaction) {
|
|
32
|
-
return TransactionTrait.shared.bindTransaction(transaction);
|
|
33
|
-
},
|
|
34
|
-
id(tx) {
|
|
35
|
-
return tx.identifiers().at(0);
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
shared = {
|
|
39
|
-
getOfferSignatureData(tx, segment) {
|
|
40
|
-
return Either.try({
|
|
41
|
-
try: () => {
|
|
42
|
-
if (!tx.intents) {
|
|
43
|
-
throw new TransactingError({ message: 'Transaction has no intents' });
|
|
44
|
-
}
|
|
45
|
-
const intent = tx.intents.get(segment);
|
|
46
|
-
if (!intent) {
|
|
47
|
-
throw new TransactingError({ message: `Intent with segment ${segment} was not found` });
|
|
48
|
-
}
|
|
49
|
-
return pipe(isIntentBound(intent) ? intent : intent.bind(segment), (boundIntent) => boundIntent.signatureData(segment));
|
|
50
|
-
},
|
|
51
|
-
catch: (error) => error instanceof TransactingError
|
|
52
|
-
? error
|
|
53
|
-
: new TransactingError({ message: 'Failed to get offer signature data', cause: error }),
|
|
54
|
-
});
|
|
55
|
-
},
|
|
56
|
-
addOfferSignature(transaction, signature, segment = 1) {
|
|
57
|
-
return Either.gen(function* () {
|
|
58
|
-
if (!transaction.intents || !transaction.intents.size) {
|
|
59
|
-
throw new TransactingError({ message: 'No intents found in the provided transaction' });
|
|
60
|
-
}
|
|
61
|
-
const intent = transaction.intents.get(segment);
|
|
62
|
-
if (!intent) {
|
|
63
|
-
throw new TransactingError({ message: 'Intent with a given segment was not found' });
|
|
64
|
-
}
|
|
65
|
-
const isBound = isIntentBound(intent);
|
|
66
|
-
if (isBound)
|
|
67
|
-
return transaction;
|
|
68
|
-
let updatedIntent = intent;
|
|
69
|
-
if (intent.guaranteedUnshieldedOffer) {
|
|
70
|
-
const offer = intent.guaranteedUnshieldedOffer;
|
|
71
|
-
const inputsLen = offer.inputs.length;
|
|
72
|
-
const signatures = [];
|
|
73
|
-
for (let i = 0; i < inputsLen; ++i) {
|
|
74
|
-
signatures.push(offer.signatures.at(i) ?? signature);
|
|
75
|
-
}
|
|
76
|
-
updatedIntent = yield* Either.try({
|
|
77
|
-
try: () => {
|
|
78
|
-
const offerWithSignatures = offer.addSignatures(signatures);
|
|
79
|
-
updatedIntent.guaranteedUnshieldedOffer = offerWithSignatures;
|
|
80
|
-
return updatedIntent;
|
|
81
|
-
},
|
|
82
|
-
catch: (error) => new TransactingError({
|
|
83
|
-
message: `Failed to add guaranteed signature at segment ${segment}`,
|
|
84
|
-
cause: error,
|
|
85
|
-
}),
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
if (intent.fallibleUnshieldedOffer) {
|
|
89
|
-
const offer = intent.fallibleUnshieldedOffer;
|
|
90
|
-
const inputsLen = offer.inputs.length;
|
|
91
|
-
const signatures = [];
|
|
92
|
-
for (let i = 0; i < inputsLen; ++i) {
|
|
93
|
-
signatures.push(offer.signatures.at(i) ?? signature);
|
|
94
|
-
}
|
|
95
|
-
updatedIntent = yield* Either.try({
|
|
96
|
-
try: () => {
|
|
97
|
-
const offerWithSignatures = offer.addSignatures(signatures);
|
|
98
|
-
updatedIntent.fallibleUnshieldedOffer = offerWithSignatures;
|
|
99
|
-
return updatedIntent;
|
|
100
|
-
},
|
|
101
|
-
catch: (error) => new TransactingError({
|
|
102
|
-
message: `Failed to add fallible signature at segment ${segment}`,
|
|
103
|
-
cause: error,
|
|
104
|
-
}),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
transaction.intents = transaction.intents.set(segment, updatedIntent);
|
|
108
|
-
return transaction;
|
|
109
|
-
});
|
|
110
|
-
},
|
|
111
|
-
getSegments(transaction) {
|
|
112
|
-
return transaction.intents && transaction.intents.size > 0 ? transaction.intents.keys().toArray() : [];
|
|
113
|
-
},
|
|
114
|
-
bindTransaction(transaction) {
|
|
115
|
-
return Either.try({
|
|
116
|
-
try: () => transaction.bind(),
|
|
117
|
-
catch: (error) => new TransactingError({ message: 'Failed to bind transaction', cause: error }),
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
getImbalances(tx) {
|
|
121
|
-
const guaranteedImbalances = TransactionTrait.shared.getGuaranteedImbalances(tx);
|
|
122
|
-
const fallibleImbalances = TransactionTrait.shared.getFallibleImbalances(tx);
|
|
123
|
-
return pipe({
|
|
124
|
-
guaranteed: guaranteedImbalances,
|
|
125
|
-
fallible: fallibleImbalances,
|
|
126
|
-
fees: 0n,
|
|
127
|
-
});
|
|
128
|
-
},
|
|
129
|
-
getGuaranteedImbalances: (tx) => {
|
|
130
|
-
const rawGuaranteedImbalances = tx
|
|
131
|
-
.imbalances(0)
|
|
132
|
-
.entries()
|
|
133
|
-
.filter(([token]) => token.tag === 'shielded')
|
|
134
|
-
.map(([token, value]) => {
|
|
135
|
-
return [token.raw.toString(), value];
|
|
136
|
-
});
|
|
137
|
-
return Imbalances.fromEntries(rawGuaranteedImbalances);
|
|
138
|
-
},
|
|
139
|
-
getFallibleImbalances: (tx) => {
|
|
140
|
-
try {
|
|
141
|
-
const rawFallibleImbalances = tx
|
|
142
|
-
.imbalances(1)
|
|
143
|
-
.entries()
|
|
144
|
-
.filter(([token]) => token.tag === 'shielded')
|
|
145
|
-
.map(([token, value]) => {
|
|
146
|
-
return [token.raw.toString(), value];
|
|
147
|
-
});
|
|
148
|
-
return Imbalances.fromEntries(rawFallibleImbalances);
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return Imbalances.empty();
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
})();
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// This file is part of MIDNIGHT-WALLET-SDK.
|
|
2
|
-
// Copyright (C) 2025 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 '@midnight-ntwrk/wallet-sdk-capabilities';
|
|
14
|
-
export const TransactionImbalances = new (class {
|
|
15
|
-
empty = () => {
|
|
16
|
-
return {
|
|
17
|
-
guaranteed: Imbalances.empty(),
|
|
18
|
-
fallible: Imbalances.empty(),
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
})();
|