Skip to content

Commit

Permalink
Merge pull request #1526 from input-output-hk/fix/input-resolver-now-…
Browse files Browse the repository at this point in the history
…looks-in-tx-history

feat(wallet): input resolver now searches TX history if input cant be found in current UTXO set
  • Loading branch information
mkazlauskas authored Nov 12, 2024
2 parents db92c20 + e452da3 commit 6db2449
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 7 deletions.
15 changes: 15 additions & 0 deletions packages/wallet/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export {}

declare global {
interface Array<T> {
findLastIndex(
predicate: (value: T, index: number, obj: T[]) => unknown,
thisArg?: any
): number;

findLast(
predicate: (value: T, index: number, obj: T[]) => unknown,
thisArg?: any
): T | undefined;
}
}
9 changes: 9 additions & 0 deletions packages/wallet/src/services/WalletUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface InputResolverContext {
available$: Observable<Cardano.Utxo[]>;
};
transactions: {
history$: Observable<Cardano.HydratedTx[]>;
outgoing: {
signed$: Observable<WitnessedTx[]>;
};
Expand All @@ -30,6 +31,7 @@ export type WalletUtilContext = WalletOutputValidatorContext &

export const createInputResolver = ({ utxo, transactions }: InputResolverContext): Cardano.InputResolver => ({
async resolveInput(input: Cardano.TxIn, options?: Cardano.ResolveOptions) {
const txHistory = await firstValueFrom(transactions.history$);
const utxoAvailable = await firstValueFrom(utxo.available$, { defaultValue: [] });
const signedTransactions = await firstValueFrom(transactions.outgoing.signed$, { defaultValue: [] });
const utxoFromSigned = signedTransactions.flatMap(({ tx: signedTx }, signedTxIndex) =>
Expand Down Expand Up @@ -63,6 +65,13 @@ export const createInputResolver = ({ utxo, transactions }: InputResolverContext
return tx.body.outputs[input.index];
}
}

const historyTx = txHistory.findLast((entry) => entry.id === input.txId);

if (historyTx && historyTx.body.outputs.length > input.index) {
return historyTx.body.outputs[input.index];
}

return null;
}
});
Expand Down
64 changes: 57 additions & 7 deletions packages/wallet/test/services/WalletUtil.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,45 @@ describe('WalletUtil', () => {
]
];
const resolver = createInputResolver({
transactions: { outgoing: { signed$: of() } },
transactions: { history$: of([]), outgoing: { signed$: of() } },
utxo: { available$: of(utxo) }
});
expect(
await resolver.resolveInput({
index: 0,
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
})
).toEqual({
address: 'addr_test1vr8nl4u0u6fmtfnawx2rxfz95dy7m46t6dhzdftp2uha87syeufdg',
value: { coins: 50_000_000n }
});
expect(
await resolver.resolveInput({
index: 0,
txId: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d4')
})
).toBeNull();
});

it('resolveInput resolves inputs from tx history', async () => {
const utxo: Cardano.Utxo[] = [];
const resolver = createInputResolver({
transactions: {
history$: of([
{
body: {
outputs: [
{
address: Cardano.PaymentAddress('addr_test1vr8nl4u0u6fmtfnawx2rxfz95dy7m46t6dhzdftp2uha87syeufdg'),
value: { coins: 50_000_000n }
}
]
},
id: Cardano.TransactionId('0f3abbc8fc19c2e61bab6059bf8a466e6e754833a08a62a6c56fe0e78f19d9d5')
} as Cardano.HydratedTx
]),
outgoing: { signed$: of() }
},
utxo: { available$: of(utxo) }
});
expect(
Expand Down Expand Up @@ -97,7 +135,7 @@ describe('WalletUtil', () => {
} as Cardano.HydratedTx;

const resolver = createInputResolver({
transactions: { outgoing: { signed$: of() } },
transactions: { history$: of([]), outgoing: { signed$: of() } },
utxo: { available$: of([]) }
});

Expand Down Expand Up @@ -134,7 +172,7 @@ describe('WalletUtil', () => {
const signedTxs = mocks.queryTransactionsResult.pageResults.map(toSignedTx);

const resolver = createInputResolver({
transactions: { outgoing: { signed$: of(signedTxs) } },
transactions: { history$: of([]), outgoing: { signed$: of(signedTxs) } },
utxo: { available$: of() }
});
expect(
Expand Down Expand Up @@ -273,7 +311,10 @@ describe('WalletUtil', () => {
]
];
const resolver = combineInputResolvers(
createInputResolver({ transactions: { outgoing: { signed$: of() } }, utxo: { available$: of(utxo) } }),
createInputResolver({
transactions: { history$: of([]), outgoing: { signed$: of() } },
utxo: { available$: of(utxo) }
}),
createBackendInputResolver(createMockChainHistoryProvider())
);

Expand Down Expand Up @@ -315,7 +356,10 @@ describe('WalletUtil', () => {
} as Cardano.HydratedTx;

const resolver = combineInputResolvers(
createInputResolver({ transactions: { outgoing: { signed$: of() } }, utxo: { available$: of([]) } }),
createInputResolver({
transactions: { history$: of([]), outgoing: { signed$: of() } },
utxo: { available$: of([]) }
}),
createBackendInputResolver(createMockChainHistoryProvider([tx]))
);

Expand Down Expand Up @@ -395,7 +439,10 @@ describe('WalletUtil', () => {
];

const resolver = combineInputResolvers(
createInputResolver({ transactions: { outgoing: { signed$: of() } }, utxo: { available$: of(utxo) } }),
createInputResolver({
transactions: { history$: of([]), outgoing: { signed$: of() } },
utxo: { available$: of(utxo) }
}),
createBackendInputResolver(createMockChainHistoryProvider([tx]))
);

Expand Down Expand Up @@ -456,7 +503,10 @@ describe('WalletUtil', () => {

it('resolveInput resolves to null if the input can not be found', async () => {
const resolver = combineInputResolvers(
createInputResolver({ transactions: { outgoing: { signed$: of() } }, utxo: { available$: of([]) } }),
createInputResolver({
transactions: { history$: of([]), outgoing: { signed$: of() } },
utxo: { available$: of([]) }
}),
createBackendInputResolver(createMockChainHistoryProvider())
);

Expand Down

0 comments on commit 6db2449

Please sign in to comment.