@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.
@@ -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<ledger.FinalizedTransaction, WalletSyncUpdate, string>;
39
- export type UnshieldedWalletClass = CustomizedUnshieldedWalletClass<ledger.FinalizedTransaction, WalletSyncUpdate, string>;
40
- export interface CustomizedUnshieldedWallet<TTransaction = ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>, TSyncUpdate = WalletSyncUpdate, TSerialized = string> extends WalletLike.WalletLike<[Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate, TTransaction>>]> {
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
- balanceTransaction(tx: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>): Promise<ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>>;
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<TTransaction = ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>, TSyncUpdate = WalletSyncUpdate, TSerialized = string, TConfig extends BaseV1Configuration = DefaultV1Configuration> extends WalletLike.BaseWalletClass<[Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate, TTransaction>>]> {
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<TTransaction, TSyncUpdate, TSerialized>;
54
- restore(serializedState: TSerialized): CustomizedUnshieldedWallet<TTransaction, TSyncUpdate, TSerialized>;
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, TTransaction = ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>, TSyncUpdate = WalletSyncUpdate, TSerialized = string>(configuration: TConfig, builder: VariantBuilder.VariantBuilder<V1Variant<TSerialized, TSyncUpdate, TTransaction>, TConfig>): CustomizedUnshieldedWalletClass<TTransaction, TSyncUpdate, TSerialized, TConfig>;
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>;
@@ -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
- balanceTransaction(tx) {
80
+ balanceFinalizedTransaction(tx) {
81
81
  return this.runtime
82
82
  .dispatch({
83
- [V1Tag]: (v1) => v1.balanceTransaction(tx),
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, TTransaction> = {
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<TTransaction, CoreWallet>;
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, any>;
26
+ type AnyContext = Context<any, any>;
26
27
  }
27
28
  export declare const V1Tag: unique symbol;
28
- export type DefaultRunningV1 = RunningV1Variant<string, WalletSyncUpdate, ledger.FinalizedTransaction>;
29
- export declare class RunningV1Variant<TSerialized, TSyncUpdate, TTransaction> implements Variant.RunningVariant<typeof V1Tag, CoreWallet> {
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, TTransaction>);
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
- balanceTransaction(tx: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>): Effect.Effect<ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>, WalletError>;
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
- balanceTransaction(tx) {
70
+ balanceFinalizedTransaction(tx) {
71
71
  return SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => {
72
- return pipe(this.#v1Context.transactingCapability.balanceTransaction(state, tx), EitherOps.toEffect, Effect.map(({ transaction, newState }) => [transaction, newState]));
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.flatMap(({ transaction, newState }) => pipe(this.#v1Context.transactingCapability.balanceTransaction(newState, transaction), EitherOps.toEffect, Effect.map(({ transaction, newState }) => [transaction, newState]))));
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) {
@@ -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 { TransactionTrait } from './Transaction.js';
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<_TTransaction, TState> {
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
- balanceTransaction(wallet: CoreWallet, transaction: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>): Either.Either<TransactingResult<ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>, TState>, WalletError>;
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<ledger.UnprovenTransaction, CoreWallet>;
34
- export declare class TransactingCapabilityImplementation<TTransaction extends ledger.UnprovenTransaction> implements TransactingCapability<ledger.UnprovenTransaction, CoreWallet> {
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 txTrait: TransactionTrait<TTransaction>;
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>, txTrait: TransactionTrait<TTransaction>);
41
- balanceTransaction(wallet: CoreWallet, transaction: TTransaction): Either.Either<TransactingResult<TTransaction, CoreWallet>, WalletError>;
42
- makeTransfer(wallet: CoreWallet, outputs: ReadonlyArray<TokenTransfer>, ttl: Date): Either.Either<TransactingResult<TTransaction, CoreWallet>, WalletError>;
43
- initSwap(wallet: CoreWallet, desiredInputs: Record<ledger.RawTokenType, bigint>, desiredOutputs: ReadonlyArray<TokenTransfer>, ttl: Date): Either.Either<TransactingResult<TTransaction, CoreWallet>, WalletError>;
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
  }
@@ -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 { isIntentBound, TransactionTrait } from './Transaction.js';
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, TransactionTrait.default);
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
- txTrait;
27
+ txOps;
35
28
  getCoins;
36
29
  getKeys;
37
- constructor(networkId, getCoinSelection, getCoins, getKeys, txTrait) {
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.txTrait = txTrait;
35
+ this.txOps = txOps;
43
36
  }
44
- balanceTransaction(wallet, transaction) {
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
- const segments = TransactionTrait.default.getSegments(transaction);
47
- if (!transaction.intents || !transaction.intents.size || !segments.length) {
48
- return {
49
- newState: wallet,
50
- transaction,
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
- return {
150
- newState: wallet,
151
- transaction: transaction,
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
- const networkId = this.networkId;
157
- const isValid = outputs.every((output) => output.amount > 0n);
158
- if (!isValid) {
159
- throw new TransactingError({ message: 'The amount needs to be positive' });
160
- }
161
- const ledgerOutputs = outputs.map((output) => {
162
- return {
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
- newState: wallet,
176
- transaction: ledger.Transaction.fromParts(networkId, undefined, undefined, intent),
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
- catch: (error) => new TransactingError({ message: 'Failed to create transaction', cause: error }),
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.networkId;
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 input amounts need to be positive' }));
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 availableCoins = this.getCoins().getAvailableCoins(wallet);
202
- const { inputs, outputs: changeOutputs } = yield* Either.try({
203
- try: () => getBalanceRecipe({
204
- coins: availableCoins.map(({ utxo }) => utxo),
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: updatedWallet,
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 = TransactionTrait.default.getSegments(transaction);
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* TransactionTrait.default.getOfferSignatureData(transaction, segment);
180
+ const signedData = yield* this.txOps.getSignatureData(transaction, segment);
246
181
  const signature = signSegment(signedData);
247
- transaction = yield* TransactionTrait.default.addOfferSignature(transaction, signature, segment);
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
+ };
@@ -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, TTransaction> = Variant.Variant<typeof V1Tag, CoreWallet, null, RunningV1Variant<TSerialized, TSyncUpdate, TTransaction>> & {
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, any>;
31
- export type DefaultV1Variant = V1Variant<string, WalletSyncUpdate, ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>>;
32
- export type TransactionOf<T extends AnyV1Variant> = T extends V1Variant<any, any, infer TTransaction> ? TTransaction : never;
33
- export type SerializedStateOf<T extends AnyV1Variant> = T extends V1Variant<infer TSerialized, any, any> ? TSerialized : never;
34
- export type DefaultV1Builder = V1Builder<DefaultV1Configuration, RunningV1Variant.Context<string, WalletSyncUpdate, ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>>, string, WalletSyncUpdate, ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>>;
35
- export declare class V1Builder<TConfig extends BaseV1Configuration = BaseV1Configuration, TContext extends Partial<RunningV1Variant.AnyContext> = object, TSerialized = never, TSyncUpdate = never, TTransaction = never> implements VariantBuilder.VariantBuilder<V1Variant<TSerialized, TSyncUpdate, TTransaction>, TConfig> {
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, TTransaction>);
37
+ constructor(buildState?: V1Builder.PartialBuildState<TConfig, TContext, TSerialized, TSyncUpdate>);
38
38
  withDefaults(): DefaultV1Builder;
39
- withTransactionType<Transaction>(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, Transaction>;
40
- withDefaultTransactionType(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>>;
41
- withSyncDefaults(): V1Builder<TConfig & DefaultSyncConfiguration, TContext & DefaultSyncContext, TSerialized, WalletSyncUpdate, TTransaction>;
42
- 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, TTransaction>;
43
- withSerializationDefaults(): V1Builder<TConfig, TContext, string, TSyncUpdate, TTransaction>;
44
- withSerialization<TSerializationConfig, TSerializationContext extends Partial<RunningV1Variant.AnyContext>, TSerialized>(serializationCapability: (configuration: TSerializationConfig, getContext: () => TSerializationContext) => SerializationCapability<CoreWallet, TSerialized>): V1Builder<TConfig & TSerializationConfig, TContext & TSerializationContext, TSerialized, TSyncUpdate, TTransaction>;
45
- withTransactingDefaults(this: V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>>): V1Builder<TConfig & DefaultTransactingConfiguration, TContext & DefaultTransactingContext, TSerialized, TSyncUpdate, ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>>;
46
- withTransacting<TTransactingConfig, TTransactingContext extends Partial<RunningV1Variant.AnyContext>>(transactingCapability: (config: TTransactingConfig, getContext: () => TTransactingContext) => TransactingCapability<TTransaction, CoreWallet>): V1Builder<TConfig & TTransactingConfig, TContext & TTransactingContext, TSerialized, TSyncUpdate, TTransaction>;
47
- withCoinSelection<TCoinSelectionConfig, TCoinSelectionContext extends Partial<RunningV1Variant.AnyContext>>(coinSelection: (config: TCoinSelectionConfig, getContext: () => TCoinSelectionContext) => CoinSelection<ledger.Utxo>): V1Builder<TConfig & TCoinSelectionConfig, TContext & TCoinSelectionContext, TSerialized, TSyncUpdate, TTransaction>;
48
- withCoinSelectionDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, TTransaction>;
49
- withCoinsAndBalancesDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, TTransaction>;
50
- withCoinsAndBalances<TBalancesConfig, TBalancesContext extends Partial<RunningV1Variant.AnyContext>>(coinsAndBalancesCapability: (configuration: TBalancesConfig, getContext: () => TBalancesContext) => CoinsAndBalancesCapability<CoreWallet>): V1Builder<TConfig & TBalancesConfig, TContext & TBalancesContext, TSerialized, TSyncUpdate, TTransaction>;
51
- withTransactionHistoryDefaults(this: V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, ledger.FinalizedTransaction>): V1Builder<TConfig & DefaultTransactionHistoryConfiguration, TContext, TSerialized, TSyncUpdate, ledger.FinalizedTransaction>;
52
- withTransactionHistory<TTransactionHistoryConfig, TTransactionHistoryContext extends Partial<RunningV1Variant.AnyContext>>(transactionHistoryService: (configuration: TTransactionHistoryConfig, getContext: () => TTransactionHistoryContext) => TransactionHistoryService<UnshieldedUpdate>): V1Builder<TConfig & TTransactionHistoryConfig, TContext & TTransactionHistoryContext, TSerialized, TSyncUpdate, TTransaction>;
53
- withKeysDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, TTransaction>;
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, TTransaction> = {
64
- readonly transactingCapability: (configuration: TConfig, getContext: () => TContext) => TransactingCapability<TTransaction, CoreWallet>;
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, TTransaction> = Types.Simplify<HasSync<TConfig, TContext, TSyncUpdate> & HasSerialization<TConfig, TContext, TSerialized> & HasTransacting<TConfig, TContext, TTransaction> & HasCoinSelection<TConfig, TContext> & HasCoinsAndBalances<TConfig, TContext> & HasKeys<TConfig, TContext> & HasTransactionHistory<TConfig, TContext>>;
85
- type PartialBuildState<TConfig = object, TContext = object, TSerialized = never, TSyncUpdate = never, TTransaction = never> = {
86
- [K in keyof FullBuildState<never, never, never, never, never>]?: FullBuildState<TConfig, TContext, TSerialized, TSyncUpdate, TTransaction>[K] | undefined;
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}.
@@ -19,8 +19,7 @@ export class V1Builder {
19
19
  this.#buildState = buildState;
20
20
  }
21
21
  withDefaults() {
22
- return this.withDefaultTransactionType()
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
  }
@@ -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 './Transaction.js';
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 './Transaction.js';
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.16",
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-alpha.1",
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.10",
29
- "@midnight-ntwrk/wallet-sdk-capabilities": "3.0.0-beta.10",
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.15",
32
- "@midnight-ntwrk/wallet-sdk-utilities": "1.0.0-beta.9",
33
- "effect": "^3.17.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",
@@ -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
- };
@@ -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,8 +0,0 @@
1
- import { Imbalances } from '@midnight-ntwrk/wallet-sdk-capabilities';
2
- export type TransactionImbalances = Readonly<{
3
- guaranteed: Imbalances;
4
- fallible: Imbalances;
5
- }>;
6
- export declare const TransactionImbalances: {
7
- empty: () => TransactionImbalances;
8
- };
@@ -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
- })();