Skip to content

Commit

Permalink
feat!: add context to txSubmit
Browse files Browse the repository at this point in the history
- add cbor to Signedtx type
- implement new SignedTx in PersonalWallet and fix ObservableWallet to work with the type

BREAKING CHANGE: SignedTx.ctx now renamed to context
  • Loading branch information
VanessaPC committed Jun 8, 2023
1 parent bfe46c8 commit 57589ec
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 31 deletions.
3 changes: 2 additions & 1 deletion packages/core/src/Provider/TxSubmitProvider/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import Cardano, { CardanoNodeErrors, Provider } from '../..';
import Cardano, { CardanoNodeErrors, HandleResolution, Provider } from '../..';

type SerializedTransaction = Cardano.util.HexBlob;

export interface SubmitTxArgs {
signedTransaction: SerializedTransaction;
context?: { handles: HandleResolution[] };
}

export interface TxSubmitProvider extends Provider {
Expand Down
10 changes: 5 additions & 5 deletions packages/tx-construction/src/tx-builder/OutputBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,6 @@ export class TxOutputBuilder implements OutputBuilder {
async build(): Promise<OutputBuilderTxOut> {
const txOut = this.toTxOut();

const outputValidation = toOutputValidationError(txOut, await this.#outputValidator.validateOutput(txOut));
if (outputValidation) {
throw outputValidation;
}

if (this.#partialOutput.handle && this.#handleProvider) {
const resolution = await this.#handleProvider.resolveHandles({ handles: [this.#partialOutput.handle] });

Expand All @@ -158,6 +153,11 @@ export class TxOutputBuilder implements OutputBuilder {
}
}

const outputValidation = toOutputValidationError(txOut, await this.#outputValidator.validateOutput(txOut));
if (outputValidation) {
throw outputValidation;
}

return txOut;
}
}
27 changes: 15 additions & 12 deletions packages/tx-construction/src/tx-builder/finalizeTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
TransactionSigner,
util as keyManagementUtil
} from '@cardano-sdk/key-management';
import { Cardano } from '@cardano-sdk/core';
import { Cardano, TxCBOR } from '@cardano-sdk/core';
import { FinalizeTxDependencies, SignedTx, TxContext } from './types';

const getSignatures = async (
Expand Down Expand Up @@ -41,19 +41,22 @@ export const finalizeTx = async (
)
: await getSignatures(keyAgent, tx, witness?.extraSigners, signingOptions);

const transaction = {
auxiliaryData,
body: tx.body,
id: tx.hash,
isValid,
witness: {
...witness,
signatures: new Map([...signatures.entries(), ...(witness?.signatures?.entries() || [])])
}
};

return {
ctx: {
cbor: TxCBOR.serialize(transaction),
context: {
handles: handles ?? []
},
tx: {
auxiliaryData,
body: tx.body,
id: tx.hash,
isValid,
witness: {
...witness,
signatures: new Map([...signatures.entries(), ...(witness?.signatures?.entries() || [])])
}
}
tx: transaction
};
};
5 changes: 3 additions & 2 deletions packages/tx-construction/src/tx-builder/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Cardano, Handle, HandleProvider, HandleResolution } from '@cardano-sdk/core';
import { Cardano, Handle, HandleProvider, HandleResolution, TxCBOR } from '@cardano-sdk/core';
import { CustomError } from 'ts-custom-error';

import { InputSelectionError, InputSelector, SelectionSkeleton } from '@cardano-sdk/input-selection';
Expand Down Expand Up @@ -124,8 +124,9 @@ export type TxInspection = Cardano.TxBodyWithHash &
};

export interface SignedTx {
cbor: TxCBOR;
tx: Cardano.Tx;
ctx: {
context: {
handles: HandleResolution[];
};
}
Expand Down
8 changes: 4 additions & 4 deletions packages/tx-construction/test/tx-builder/TxBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ describe('GenericTxBuilder', () => {
});

it('can set extraSigners for signing', async () => {
const { tx } = await txBuilder.addOutput(mocks.utxo[0][1]).extraSigners(signers).build().sign();
expect(tx.witness.signatures.get(pubKey)).toEqual(signature);
const { tx: signedTx } = await txBuilder.addOutput(mocks.utxo[0][1]).extraSigners(signers).build().sign();
expect(signedTx.witness.signatures.get(pubKey)).toEqual(signature);
});
});

Expand Down Expand Up @@ -583,8 +583,8 @@ describe('GenericTxBuilder', () => {
.build();
const { handles } = await tx.inspect();
expect(handles).toEqual([resolvedHandle]);
const { ctx } = await tx.sign();
expect(ctx.handles).toEqual([resolvedHandle]);
const { context } = await tx.sign();
expect(context.handles).toEqual([resolvedHandle]);
});

it('can build transactions that are not modified by subsequent builder changes', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/util-dev/src/mockProviders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './mockAssetProvider';
export * from './mockUtxoProvider';
export * from './mockChainHistoryProvider';
export * from './mockRewardsProvider';
export * from './mockHandleProvider';
20 changes: 20 additions & 0 deletions packages/util-dev/src/mockProviders/mockHandleProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Cardano } from '@cardano-sdk/core';

export const resolvedHandle = {
handle: 'alice',
hasDatum: false,
policyId: Cardano.PolicyId('50fdcdbfa3154db86a87e4b5697ae30d272e0bbcfa8122efd3e301cb'),
resolvedAddresses: {
cardano:
'addr_test1qqk4sr4f7vtqzd2w90d5nfu3n59jhhpawyphnek2y7er02nkrezryq3ydtmkg0e7e2jvzg443h0ffzfwd09wpcxy2fuqmcnecd'
},
resolvedAt: {
hash: Cardano.BlockId('10d64cc11e9b20e15b6c46aa7b1fed11246f437e62225655a30ea47bf8cc22d0'),
slot: Cardano.Slot(37_834_496)
}
};

export const mockHandleProvider = () => ({
healthCheck: jest.fn().mockResolvedValue({ ok: true }),
resolveHandles: jest.fn().mockResolvedValue([resolvedHandle])
});
22 changes: 18 additions & 4 deletions packages/wallet/src/PersonalWallet/PersonalWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import {
InitializeTxProps,
InitializeTxResult,
InvalidConfigurationError,
SignedTx,
TxBuilderDependencies,
finalizeTx,
initializeTx
Expand Down Expand Up @@ -145,10 +146,12 @@ export const DEFAULT_LOOK_AHEAD_SEARCH = 20;
// Ideally we should calculate this based on the activeSlotsCoeff and probability of a single block per epoch.
const BLOCK_SLOT_GAP_MULTIPLIER = 2.5;

const isOutgoingTx = (input: Cardano.Tx | TxCBOR | OutgoingTx): input is OutgoingTx =>
const isOutgoingTx = (input: Cardano.Tx | TxCBOR | OutgoingTx | SignedTx): input is OutgoingTx =>
typeof input === 'object' && 'cbor' in input;
const isTxCBOR = (input: Cardano.Tx | TxCBOR | OutgoingTx): input is TxCBOR => typeof input === 'string';
const processOutgoingTx = (input: Cardano.Tx | TxCBOR | OutgoingTx): OutgoingTx => {
const isTxCBOR = (input: Cardano.Tx | TxCBOR | OutgoingTx | SignedTx): input is TxCBOR => typeof input === 'string';
const isSignedTx = (input: Cardano.Tx | TxCBOR | OutgoingTx | SignedTx): input is SignedTx =>
typeof input === 'object' && 'context' in input;
const processOutgoingTx = (input: Cardano.Tx | TxCBOR | OutgoingTx | SignedTx): OutgoingTx => {
// TxCbor
if (isTxCBOR(input)) {
return {
Expand All @@ -158,6 +161,16 @@ const processOutgoingTx = (input: Cardano.Tx | TxCBOR | OutgoingTx): OutgoingTx
id: Cardano.TransactionId.fromTxBodyCbor(TxBodyCBOR.fromTxCBOR(input))
};
}
// SignedTx
if (isSignedTx(input)) {
return {
body: input.tx.body,
cbor: input.cbor,
context: input.context,
// Do not re-serialize transaction body to compute transaction id
id: input.tx.id
};
}
// OutgoingTx (resubmitted)
if (isOutgoingTx(input)) {
return input;
Expand Down Expand Up @@ -509,14 +522,15 @@ export class PersonalWallet implements ObservableWallet {
}

async submitTx(
input: Cardano.Tx | TxCBOR | OutgoingTx,
input: Cardano.Tx | TxCBOR | OutgoingTx | SignedTx,
{ mightBeAlreadySubmitted }: SubmitTxOptions = {}
): Promise<Cardano.TransactionId> {
const outgoingTx = processOutgoingTx(input);
this.#logger.debug(`Submitting transaction ${outgoingTx.id}`);
this.#newTransactions.submitting$.next(outgoingTx);
try {
await this.txSubmitProvider.submitTx({
context: outgoingTx.context,
signedTransaction: outgoingTx.cbor
});
const { slot: submittedAt } = await firstValueFrom(this.tip$);
Expand Down
4 changes: 3 additions & 1 deletion packages/wallet/src/services/TransactionReemitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ export const createTransactionReemitter = ({

return {
failed$,
reemit$: merge(rollbackRetry$, unsubmitted$, reemitUnconfirmed$).pipe(map((tx) => pick(tx, ['cbor', 'body', 'id'])))
reemit$: merge(rollbackRetry$, unsubmitted$, reemitUnconfirmed$).pipe(
map((tx) => pick(tx, ['cbor', 'body', 'id', 'context']))
)
};
};
2 changes: 2 additions & 0 deletions packages/wallet/src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AsyncKeyAgent, GroupedAddress } from '@cardano-sdk/key-management';
import { Cardano, CardanoNodeErrors, EpochRewards, TxCBOR } from '@cardano-sdk/core';
import { Observable } from 'rxjs';
import { Percent } from '@cardano-sdk/util';
import { SignedTx } from '@cardano-sdk/tx-construction';

export enum TransactionFailure {
InvalidTransaction = 'INVALID_TRANSACTION',
Expand Down Expand Up @@ -65,6 +66,7 @@ export interface OutgoingTx {
cbor: TxCBOR;
body: Cardano.TxBody;
id: Cardano.TransactionId;
context?: SignedTx['context'];
}

export interface FailedTx extends OutgoingTx {
Expand Down
4 changes: 2 additions & 2 deletions packages/wallet/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { BalanceTracker, DelegationTracker, TransactionsTracker, UtxoTracker } from './services';
import { Cip30DataSignature } from '@cardano-sdk/dapp-connector';
import { GroupedAddress, cip8 } from '@cardano-sdk/key-management';
import { InitializeTxProps, InitializeTxResult, TxBuilder, TxContext } from '@cardano-sdk/tx-construction';
import { InitializeTxProps, InitializeTxResult, SignedTx, TxBuilder, TxContext } from '@cardano-sdk/tx-construction';
import { Observable } from 'rxjs';
import { Shutdown } from '@cardano-sdk/util';

Expand Down Expand Up @@ -84,7 +84,7 @@ export interface ObservableWallet {
/**
* @throws CardanoNodeErrors.TxSubmissionError
*/
submitTx(tx: Cardano.Tx | TxCBOR): Promise<Cardano.TransactionId>;
submitTx(tx: Cardano.Tx | TxCBOR | SignedTx): Promise<Cardano.TransactionId>;

/**
* Create a TxBuilder from this wallet
Expand Down
21 changes: 21 additions & 0 deletions packages/wallet/test/PersonalWallet/methods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ describe('PersonalWallet methods', () => {
txSubmitProvider = mocks.mockTxSubmitProvider();
networkInfoProvider = mocks.mockNetworkInfoProvider();
utxoProvider = mocks.mockUtxoProvider();

const assetProvider = mocks.mockAssetProvider();
const stakePoolProvider = createStubStakePoolProvider();
const rewardsProvider = mockRewardsProvider();
const chainHistoryProvider = mockChainHistoryProvider();
const handleProvider = mocks.mockHandleProvider();
const groupedAddress: GroupedAddress = {
accountIndex: 0,
address,
Expand All @@ -83,6 +85,7 @@ describe('PersonalWallet methods', () => {
{
assetProvider,
chainHistoryProvider,
handleProvider,
keyAgent,
logger,
networkInfoProvider,
Expand Down Expand Up @@ -268,6 +271,24 @@ describe('PersonalWallet methods', () => {
await expect(wallet.submitTx(tx, { mightBeAlreadySubmitted: true })).resolves.not.toThrow();
expect(await txPending).toEqual(outgoingTx);
});

it('resolves on success when submitting a tx when sending coins to a handle', async () => {
const txBuilder = wallet.createTxBuilder();
const txOutput = await txBuilder.buildOutput().handle('alice').coin(1_000_000n).build();

const txOut = await txBuilder.addOutput(txOutput).build().sign();
const submitTxArgsMock = {
context: { handles: [mocks.resolvedHandle] },
signedTransaction: txOut.cbor
};
const txPending = firstValueFrom(wallet.transactions.outgoing.pending$);
await wallet.submitTx(txOut);

const txPendingResult = await txPending;

expect(txPendingResult.body.outputs[0].address).toEqual(mocks.resolvedHandle.resolvedAddresses.cardano);
expect(txSubmitProvider.submitTx).toHaveBeenCalledWith(submitTxArgsMock);
});
});
});

Expand Down

0 comments on commit 57589ec

Please sign in to comment.