Skip to content

Commit bbc894c

Browse files
Merge pull request #1082 from input-output-hk/feat/lw-9629-create-inputresolver-that-can-resolve-both-own-and-foreign-inputs
feat(wallet): added a new input resolver decorator that also fetches from the backend
2 parents 2cd1e01 + 14c486d commit bbc894c

File tree

3 files changed

+459
-7
lines changed

3 files changed

+459
-7
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

+72-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-bitwise */
22
import * as Crypto from '@cardano-sdk/crypto';
3-
import { Cardano } from '@cardano-sdk/core';
3+
import { Cardano, ChainHistoryProvider } from '@cardano-sdk/core';
44
import { GroupedAddress, util as KeyManagementUtil } from '@cardano-sdk/key-management';
55
import { Observable, firstValueFrom } from 'rxjs';
66
import { ObservableWallet, ScriptAddress, isScriptAddress } from '../types';
@@ -23,11 +23,79 @@ 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;
40+
}
41+
});
42+
43+
/**
44+
* Creates an input resolver that fetch transaction inputs from the backend.
45+
*
46+
* This function tries to fetch the transaction from the backend using a `ChainHistoryProvider`. It
47+
* also caches fetched transactions to optimize subsequent input resolutions.
48+
*
49+
* @param provider The backend provider used to fetch transactions by their hashes if
50+
* they are not found by the inputResolver.
51+
* @returns An input resolver that can fetch unresolved inputs from the backend.
52+
*/
53+
export const createBackendInputResolver = (provider: ChainHistoryProvider): Cardano.InputResolver => {
54+
const txCache = new Map<Cardano.TransactionId, Cardano.Tx>();
55+
56+
const fetchAndCacheTransaction = async (txId: Cardano.TransactionId): Promise<Cardano.Tx | null> => {
57+
if (txCache.has(txId)) {
58+
return txCache.get(txId)!;
59+
}
60+
61+
const txs = await provider.transactionsByHashes({ ids: [txId] });
62+
if (txs.length > 0) {
63+
txCache.set(txId, txs[0]);
64+
return txs[0];
65+
}
66+
67+
return null;
68+
};
69+
70+
return {
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+
79+
const tx = await fetchAndCacheTransaction(input.txId);
80+
if (!tx) return null;
81+
82+
return tx.body.outputs.length > input.index ? tx.body.outputs[input.index] : null;
83+
}
84+
};
85+
};
86+
87+
/**
88+
* Combines multiple input resolvers into a single resolver.
89+
*
90+
* @param resolvers The input resolvers to combine.
91+
*/
92+
export const combineInputResolvers = (...resolvers: Cardano.InputResolver[]): Cardano.InputResolver => ({
93+
async resolveInput(txIn: Cardano.TxIn, options?: Cardano.ResolveOptions) {
94+
for (const resolver of resolvers) {
95+
const resolved = await resolver.resolveInput(txIn, options);
96+
if (resolved) return resolved;
97+
}
98+
return null;
3199
}
32100
});
33101

0 commit comments

Comments
 (0)