/
CustomObservableWallet.test.ts
167 lines (158 loc) · 7.45 KB
/
CustomObservableWallet.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable sonarjs/no-extra-arguments */
/* eslint-disable unicorn/consistent-function-scoping */
import { Cardano, Transaction } from '@cardano-sdk/core';
import { GroupedAddress } from '@cardano-sdk/key-management';
import { ObservableWallet, PersonalWallet, coldObservableProvider } from '../../src';
import {
OutputValidator,
ProtocolParametersRequiredByOutputValidator,
createOutputValidator
} from '@cardano-sdk/tx-construction';
import { RetryBackoffConfig } from 'backoff-rxjs';
import { createStubStakePoolProvider, mockProviders as mocks } from '@cardano-sdk/util-dev';
import { firstValueFrom, of, timer } from 'rxjs';
import { dummyLogger as logger } from 'ts-log';
import { testAsyncKeyAgent } from '../../../key-management/test/mocks';
import { usingAutoFree } from '@cardano-sdk/util';
describe('CustomObservableWallet', () => {
describe('can create an application-specific subset of ObservableWallet interface', () => {
type LaceBalance = {
// Can use partial types
utxo: Pick<ObservableWallet['balance']['utxo'], 'available$'>;
rewardAccounts: ObservableWallet['balance']['rewardAccounts'];
};
/**
* Wallet interface as used by the application
*/
interface LaceObservableWallet {
// Interface supports multi-addresses for the most part (note plural in addresses$ and delegation.rewardAccounts$)
// Might be missing some data (e.g. balance by address):
// we're not sure what kind of data multi-address wallet wants to present to the users,
// but we're open to extending the ObservableWallet interface to support more use cases.
addresses$: ObservableWallet['addresses$'];
delegation: {
rewardAccounts$: ObservableWallet['delegation']['rewardAccounts$'];
};
balance: LaceBalance;
submitTx: ObservableWallet['submitTx'];
}
it('can use PersonalWallet to satisfy application-specific interface', async () => {
// this compiles
const extensionWallet: LaceObservableWallet = new PersonalWallet(
{ name: 'Extension Wallet' },
{
assetProvider: mocks.mockAssetProvider(),
chainHistoryProvider: mocks.mockChainHistoryProvider(),
keyAgent: await testAsyncKeyAgent(),
logger,
networkInfoProvider: mocks.mockNetworkInfoProvider(),
rewardsProvider: mocks.mockRewardsProvider(),
stakePoolProvider: createStubStakePoolProvider(),
txSubmitProvider: mocks.mockTxSubmitProvider(),
utxoProvider: mocks.mockUtxoProvider()
}
);
extensionWallet;
});
it('does not necessarily have to use PersonalWallet, but can still utilize SDK utils', () => {
// let's say we have an API endpoint to submit transaction as bytes and not as SDK's Cardano.Tx
const submitTxBytesHexString: (tx: string) => Promise<Cardano.TransactionId> = () =>
Promise.resolve(Cardano.TransactionId('0000000000000000000000000000000000000000000000000000000000000000'));
// and another endpoint to get wallet addresses
const getAddresses: () => Promise<GroupedAddress[]> = async () => [];
// and another endpoint to get wallet's utxo balance
const getAvailableUtxoBalance: () => Promise<Cardano.Value> = async () => ({ coins: 10_000_000n });
// and another endpoint to get wallet's total reward accounts deposit
const getAvailableRewardAccountsDeposit: () => Promise<Cardano.Lovelace> = async () => 200_000n;
// and another endpoint to get wallet's reward accounts info (such as stake pool delegated to)
const getRewardAccountsDelegation: () => Promise<Cardano.RewardAccountInfo[]> = async () => [
{
address: Cardano.RewardAccount('stake1u89sasnfyjtmgk8ydqfv3fdl52f36x3djedfnzfc9rkgzrcss5vgr'),
keyStatus: Cardano.StakeKeyStatus.Unregistering,
rewardBalance: 5000n
}
];
// some additional arguments might be needed to utilize SDK utils
const retryBackoffConfig: RetryBackoffConfig = { initialInterval: 1000 };
// for the sake of simplicity, let's say we just want to poll all backend endpoints every 10s
const walletUpdateTrigger$ = timer(0, 10_000);
// this compiles
const desktopWallet: LaceObservableWallet = {
// for the sake of simplicity, let's say we don't care about wallet's persistence/restoration
// and want to just re-fetch data upon every subscription and then update using some interval.
// If we did want more features, there are other SDK utils we could use
addresses$: coldObservableProvider({
provider: getAddresses,
retryBackoffConfig,
trigger$: walletUpdateTrigger$
}),
balance: {
rewardAccounts: {
// can entirely bypass SDK and it's utils, providing custom observables
deposit$: of(200_000n),
rewards$: coldObservableProvider({
provider: getAvailableRewardAccountsDeposit,
retryBackoffConfig,
trigger$: walletUpdateTrigger$
})
},
utxo: {
available$: coldObservableProvider({
provider: getAvailableUtxoBalance,
retryBackoffConfig,
trigger$: walletUpdateTrigger$
})
}
},
delegation: {
rewardAccounts$: coldObservableProvider({
provider: getRewardAccountsDelegation,
retryBackoffConfig,
trigger$: walletUpdateTrigger$
})
},
submitTx(tx: Cardano.Tx) {
// can use utils from SDK, in this case `Transaction.fromCore`
// if you want to submit hex-encoded tx, there is also Transaction.toCore for the reverse
const txBytes = usingAutoFree((scope) => scope.manage(Transaction.fromCore(scope, tx)).toCbor());
return submitTxBytesHexString(txBytes);
}
};
desktopWallet;
});
describe('can use SDK abstractions that are not in scope of ObservableWallet interface', () => {
// Let's say application depends on an OutputValidator. This util is a good example,
// but many other utils depend on core.Cardano or ObservableWallet types,
// which are not necessarily resolved by your custom wallet type.
// Please let us know if you'd like to use some util
// that is too constrained by the types - we're open to refactors
let outputValidator: OutputValidator;
it('can utilize PersonalWallet', () => {
const wallet: PersonalWallet = {} as PersonalWallet;
// this compiles
outputValidator = createOutputValidator({
protocolParameters: () => firstValueFrom(wallet.protocolParameters$)
});
outputValidator;
});
it('can provide an implementation that utilize SDK utils, but doesnt depend on full ObservableWallet', () => {
const protocolParameters: ProtocolParametersRequiredByOutputValidator = {
coinsPerUtxoByte: 34_482,
maxValueSize: 5000
};
// this compiles
outputValidator = createOutputValidator({ protocolParameters: async () => protocolParameters });
});
it('can provide custom implementation', () => {
outputValidator = {
validateOutput: async (_output) => ({
coinMissing: 0n,
minimumCoin: 1_000_000n,
tokenBundleSizeExceedsLimit: false
})
} as OutputValidator;
});
});
});
});