Skip to content

Commit 2413c10

Browse files
feat!: inputResolver resolveInput function now takes an additional parameter options
BREAKING CHANGE: inputResolver resolveInput function now takes an additional parameter options
1 parent c90750b commit 2413c10

File tree

3 files changed

+183
-21
lines changed

3 files changed

+183
-21
lines changed

packages/core/src/Cardano/Address/PaymentAddress.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
assertIsBech32WithPrefix,
88
assertIsHexString
99
} from '@cardano-sdk/util';
10-
import { HydratedTx, HydratedTxIn, TxIn, TxOut } from '../types';
10+
import { HydratedTx, HydratedTxIn, Tx, TxIn, TxOut } from '../types';
1111
import { NetworkId } from '../ChainId';
1212
import { RewardAccount } from './RewardAccount';
1313

@@ -68,11 +68,15 @@ export const isAddressWithin =
6868
export const inputsWithAddresses = (tx: HydratedTx, ownAddresses: PaymentAddress[]): HydratedTxIn[] =>
6969
tx.body.inputs.filter(isAddressWithin(ownAddresses));
7070

71+
export type ResolveOptions = {
72+
hints: Tx[];
73+
};
74+
7175
/**
7276
* @param txIn transaction input to resolve associated txOut from
7377
* @returns txOut
7478
*/
75-
export type ResolveInput = (txIn: TxIn) => Promise<TxOut | null>;
79+
export type ResolveInput = (txIn: TxIn, options?: ResolveOptions) => Promise<TxOut | null>;
7680

7781
export interface InputResolver {
7882
resolveInput: ResolveInput;

packages/wallet/src/services/WalletUtil.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,20 @@ export interface WalletOutputValidatorContext {
2323
export type WalletUtilContext = WalletOutputValidatorContext & InputResolverContext;
2424

2525
export const createInputResolver = ({ utxo }: InputResolverContext): Cardano.InputResolver => ({
26-
async resolveInput(input: Cardano.TxIn) {
26+
async resolveInput(input: Cardano.TxIn, options?: Cardano.ResolveOptions) {
2727
const utxoAvailable = await firstValueFrom(utxo.available$);
2828
const availableUtxo = utxoAvailable?.find(([txIn]) => txInEquals(txIn, input));
29-
if (!availableUtxo) return null;
30-
return availableUtxo[1];
29+
30+
if (availableUtxo) return availableUtxo[1];
31+
32+
if (options?.hints) {
33+
const tx = options?.hints.find((hint) => hint.id === input.txId);
34+
35+
if (tx && tx.body.outputs.length > input.index) {
36+
return tx.body.outputs[input.index];
37+
}
38+
}
39+
return null;
3140
}
3241
});
3342

@@ -59,7 +68,14 @@ export const createBackendInputResolver = (provider: ChainHistoryProvider): Card
5968
};
6069

6170
return {
62-
async resolveInput(input: Cardano.TxIn) {
71+
async resolveInput(input: Cardano.TxIn, options?: Cardano.ResolveOptions) {
72+
// Add hints to the cache
73+
if (options?.hints) {
74+
for (const hint of options.hints) {
75+
txCache.set(hint.id, hint);
76+
}
77+
}
78+
6379
const tx = await fetchAndCacheTransaction(input.txId);
6480
if (!tx) return null;
6581

@@ -74,9 +90,9 @@ export const createBackendInputResolver = (provider: ChainHistoryProvider): Card
7490
* @param resolvers The input resolvers to combine.
7591
*/
7692
export const combineInputResolvers = (...resolvers: Cardano.InputResolver[]): Cardano.InputResolver => ({
77-
async resolveInput(txIn: Cardano.TxIn) {
93+
async resolveInput(txIn: Cardano.TxIn, options?: Cardano.ResolveOptions) {
7894
for (const resolver of resolvers) {
79-
const resolved = await resolver.resolveInput(txIn);
95+
const resolved = await resolver.resolveInput(txIn, options);
8096
if (resolved) return resolved;
8197
}
8298
return null;

packages/wallet/test/services/WalletUtil.test.ts

+155-13
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,58 @@ describe('WalletUtil', () => {
7070
})
7171
).toBeNull();
7272
});
73+
74+
it('resolveInput resolves inputs from provided hints', async () => {
75+
const tx = {
76+
body: {
77+
outputs: [
78+
{
79+
address: Cardano.PaymentAddress(
80+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz'
81+
),
82+
value: { coins: 50_000_000n }
83+
},
84+
{
85+
address: Cardano.PaymentAddress(
86+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz'
87+
),
88+
value: { coins: 150_000_000n }
89+
}
90+
]
91+
},
92+
id: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
93+
} as Cardano.HydratedTx;
94+
95+
const resolver = createInputResolver({ utxo: { available$: of([]) } });
96+
97+
expect(
98+
await resolver.resolveInput(
99+
{
100+
index: 0,
101+
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
102+
},
103+
{ hints: [tx] }
104+
)
105+
).toEqual({
106+
address:
107+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz',
108+
value: { coins: 50_000_000n }
109+
});
110+
111+
expect(
112+
await resolver.resolveInput(
113+
{
114+
index: 1,
115+
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
116+
},
117+
{ hints: [tx] }
118+
)
119+
).toEqual({
120+
address:
121+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz',
122+
value: { coins: 150_000_000n }
123+
});
124+
});
73125
});
74126

75127
describe('createBackendInputResolver', () => {
@@ -117,6 +169,58 @@ describe('WalletUtil', () => {
117169
value: { coins: 150_000_000n }
118170
});
119171
});
172+
173+
it('resolveInput resolves inputs from provided hints', async () => {
174+
const tx = {
175+
body: {
176+
outputs: [
177+
{
178+
address: Cardano.PaymentAddress(
179+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz'
180+
),
181+
value: { coins: 50_000_000n }
182+
},
183+
{
184+
address: Cardano.PaymentAddress(
185+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz'
186+
),
187+
value: { coins: 150_000_000n }
188+
}
189+
]
190+
},
191+
id: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
192+
} as Cardano.HydratedTx;
193+
194+
const resolver = createBackendInputResolver(createMockChainHistoryProvider([]));
195+
196+
expect(
197+
await resolver.resolveInput(
198+
{
199+
index: 0,
200+
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
201+
},
202+
{ hints: [tx] }
203+
)
204+
).toEqual({
205+
address:
206+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz',
207+
value: { coins: 50_000_000n }
208+
});
209+
210+
expect(
211+
await resolver.resolveInput(
212+
{
213+
index: 1,
214+
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
215+
},
216+
{ hints: [tx] }
217+
)
218+
).toEqual({
219+
address:
220+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz',
221+
value: { coins: 150_000_000n }
222+
});
223+
});
120224
});
121225

122226
describe('combineInputResolvers', () => {
@@ -206,7 +310,23 @@ describe('WalletUtil', () => {
206310
});
207311
});
208312

209-
it('can resolve inputs from own transactions and from chain history provider', async () => {
313+
it('can resolve inputs from own transactions, hints and from chain history provider', async () => {
314+
const hints = [
315+
{
316+
body: {
317+
outputs: [
318+
{
319+
address: Cardano.PaymentAddress(
320+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz'
321+
),
322+
value: { coins: 200_000_000n }
323+
}
324+
]
325+
},
326+
id: Cardano.TransactionId('0000bbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0FFFFFFFFFF')
327+
} as Cardano.HydratedTx
328+
];
329+
210330
const tx = {
211331
body: {
212332
outputs: [
@@ -248,36 +368,58 @@ describe('WalletUtil', () => {
248368
);
249369

250370
expect(
251-
await resolver.resolveInput({
252-
index: 0,
253-
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
254-
})
371+
await resolver.resolveInput(
372+
{
373+
index: 0,
374+
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
375+
},
376+
{ hints }
377+
)
255378
).toEqual({
256379
address: 'addr_test1vr8nl4u0u6fmtfnawx2rxfz95dy7m46t6dhzdftp2uha87syeufdg',
257380
value: { coins: 50_000_000n }
258381
});
259382

260383
expect(
261-
await resolver.resolveInput({
262-
index: 0,
263-
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e7FFFFFFFF')
264-
})
384+
await resolver.resolveInput(
385+
{
386+
index: 0,
387+
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e7FFFFFFFF')
388+
},
389+
{ hints }
390+
)
265391
).toEqual({
266392
address:
267393
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz',
268394
value: { coins: 50_000_000n }
269395
});
270396

271397
expect(
272-
await resolver.resolveInput({
273-
index: 1,
274-
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e7FFFFFFFF')
275-
})
398+
await resolver.resolveInput(
399+
{
400+
index: 1,
401+
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e7FFFFFFFF')
402+
},
403+
{ hints }
404+
)
276405
).toEqual({
277406
address:
278407
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz',
279408
value: { coins: 150_000_000n }
280409
});
410+
expect(
411+
await resolver.resolveInput(
412+
{
413+
index: 0,
414+
txId: Cardano.TransactionId('0000bbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0FFFFFFFFFF')
415+
},
416+
{ hints }
417+
)
418+
).toEqual({
419+
address:
420+
'addr_test1qzs0umu0s2ammmpw0hea0w2crtcymdjvvlqngpgqy76gpfnuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qp3y3vz',
421+
value: { coins: 200_000_000n }
422+
});
281423
});
282424

283425
it('resolveInput resolves to null if the input can not be found', async () => {

0 commit comments

Comments
 (0)