Skip to content

Commit d0bdffa

Browse files
feat!: replace updateWitness with addSignatures in observable wallet
BREAKING CHANGE: remove updateWitness method from observable wallet - add addSignatures method to observable wallet
1 parent 308b003 commit d0bdffa

File tree

7 files changed

+158
-48
lines changed

7 files changed

+158
-48
lines changed

packages/e2e/test/long-running/shared-wallet-delegation-rewards.test.ts

+22-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BaseWallet } from '@cardano-sdk/wallet';
2-
import { Cardano, StakePoolProvider } from '@cardano-sdk/core';
2+
import { Cardano, Serialization, StakePoolProvider } from '@cardano-sdk/core';
33
import { buildSharedWallets } from '../wallet_epoch_0/SharedWallet/utils';
44
import { filter, firstValueFrom, map, take } from 'rxjs';
55
import {
@@ -20,11 +20,15 @@ const env = getEnv(walletVariables);
2020

2121
const submitDelegationTx = async (alice: BaseWallet, bob: BaseWallet, charlotte: BaseWallet, pool: Cardano.PoolId) => {
2222
logger.info(`Creating delegation tx at epoch #${(await firstValueFrom(alice.currentEpoch$)).epochNo}`);
23-
let tx = (await alice.createTxBuilder().delegateFirstStakeCredential(pool).build().sign()).tx;
23+
const tx = (await alice.createTxBuilder().delegateFirstStakeCredential(pool).build().sign()).tx;
2424

25-
tx = await bob.updateWitness({ sender: { id: 'e2e' }, tx });
26-
tx = await charlotte.updateWitness({ sender: { id: 'e2e' }, tx });
27-
await alice.submitTx(tx);
25+
// Serialize and transmit TX...
26+
let serializedTx = Serialization.Transaction.fromCore(tx).toCbor();
27+
28+
serializedTx = await bob.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
29+
serializedTx = await charlotte.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
30+
31+
await alice.submitTx(serializedTx);
2832

2933
const { epochNo } = await firstValueFrom(alice.currentEpoch$);
3034
logger.info(`Delegation tx ${tx.id} submitted at epoch #${epochNo}`);
@@ -61,15 +65,18 @@ const buildSpendRewardTx = async (
6165
const { body } = await tx.inspect();
6266
logger.debug('Body of tx before sign');
6367
logger.debug(body);
64-
let signedTx = (await tx.sign()).tx;
68+
const signedTx = (await tx.sign()).tx;
69+
70+
// Serialize and transmit TX...
71+
let serializedTx = Serialization.Transaction.fromCore(signedTx).toCbor();
6572

66-
signedTx = await bob.updateWitness({ sender: { id: 'e2e' }, tx: signedTx });
67-
signedTx = await charlotte.updateWitness({ sender: { id: 'e2e' }, tx: signedTx });
73+
serializedTx = await bob.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
74+
serializedTx = await charlotte.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
6875

6976
logger.debug('Body of tx after sign');
7077
logger.debug(signedTx.body);
7178

72-
return signedTx;
79+
return serializedTx;
7380
};
7481

7582
const getPoolIds = async (stakePoolProvider: StakePoolProvider, count: number) => {
@@ -187,13 +194,11 @@ describe('shared wallet delegation rewards', () => {
187194
logger.info(`Generated rewards: ${rewards} tLovelace`);
188195

189196
// Spend reward
190-
const spendRewardTx = await buildSpendRewardTx(
191-
aliceMultiSigWallet,
192-
bobMultiSigWallet,
193-
charlotteMultiSigWallet,
194-
faucetWallet
195-
);
196-
expect(spendRewardTx.body.withdrawals?.length).toBeGreaterThan(0);
197-
await submitAndConfirm(aliceMultiSigWallet, spendRewardTx);
197+
const spendRewardsTx = Serialization.Transaction.fromCbor(
198+
await buildSpendRewardTx(aliceMultiSigWallet, bobMultiSigWallet, charlotteMultiSigWallet, faucetWallet)
199+
).toCore();
200+
201+
expect(spendRewardsTx.body.withdrawals?.length).toBeGreaterThan(0);
202+
await submitAndConfirm(aliceMultiSigWallet, spendRewardsTx);
198203
});
199204
});

packages/e2e/test/wallet_epoch_0/SharedWallet/simpleTx.test.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BaseWallet } from '@cardano-sdk/wallet';
2-
import { Cardano } from '@cardano-sdk/core';
2+
import { Cardano, Serialization } from '@cardano-sdk/core';
33
import { buildSharedWallets } from './utils';
44
import { filter, firstValueFrom, map, take } from 'rxjs';
55
import {
@@ -97,14 +97,18 @@ describe('SharedWallet/simpleTx', () => {
9797
// Alice will initiate the transaction.
9898
const txBuilder = aliceMultiSigWallet.createTxBuilder();
9999
const txOut = await txBuilder.buildOutput().address(faucetAddress).coin(1_000_000n).build();
100-
let tx = (await txBuilder.addOutput(txOut).build().sign()).tx;
100+
const tx = (await txBuilder.addOutput(txOut).build().sign()).tx;
101+
102+
// Serialize and transmit TX...
103+
let serializedTx = Serialization.Transaction.fromCore(tx).toCbor();
101104

102105
// Bob updates the transaction with his witness
103-
tx = await bobMultiSigWallet.updateWitness({ sender: { id: 'e2e' }, tx });
106+
serializedTx = await bobMultiSigWallet.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
104107

105108
// Charlotte updates the transaction with her witness
106-
tx = await charlotteMultiSigWallet.updateWitness({ sender: { id: 'e2e' }, tx });
107-
const txId = await charlotteMultiSigWallet.submitTx(tx);
109+
serializedTx = await charlotteMultiSigWallet.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
110+
111+
const txId = await charlotteMultiSigWallet.submitTx(serializedTx);
108112

109113
const finalTxFound = await firstValueFrom(
110114
aliceMultiSigWallet.transactions.history$.pipe(

packages/e2e/test/wallet_epoch_3/SharedWallet/delegation.test.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable max-statements */
22
import { BaseWallet, ObservableWallet } from '@cardano-sdk/wallet';
33
import { BigIntMath, isNotNil } from '@cardano-sdk/util';
4-
import { Cardano, StakePoolProvider } from '@cardano-sdk/core';
4+
import { Cardano, Serialization, StakePoolProvider } from '@cardano-sdk/core';
55
import {
66
TX_TIMEOUT_DEFAULT,
77
firstValueFromTimed,
@@ -172,9 +172,12 @@ describe('SharedWallet/delegation', () => {
172172
.sign()
173173
).tx;
174174

175-
tx = await bobMultiSigWallet.updateWitness({ sender: { id: 'e2e' }, tx });
176-
tx = await charlotteMultiSigWallet.updateWitness({ sender: { id: 'e2e' }, tx });
177-
await aliceMultiSigWallet.submitTx(tx);
175+
// Serialize and transmit TX...
176+
let serializedTx = Serialization.Transaction.fromCore(tx).toCbor();
177+
178+
serializedTx = await bobMultiSigWallet.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
179+
serializedTx = await charlotteMultiSigWallet.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
180+
await aliceMultiSigWallet.submitTx(serializedTx);
178181

179182
// Test it locks available balance after tx is submitted
180183
await firstValueFromTimed(
@@ -224,10 +227,12 @@ describe('SharedWallet/delegation', () => {
224227

225228
// Make a 2nd tx with key de-registration
226229
tx = (await aliceMultiSigWallet.createTxBuilder().delegateFirstStakeCredential(null).build().sign()).tx;
227-
tx = await bobMultiSigWallet.updateWitness({ sender: { id: 'e2e' }, tx });
228-
tx = await charlotteMultiSigWallet.updateWitness({ sender: { id: 'e2e' }, tx });
230+
serializedTx = Serialization.Transaction.fromCore(tx).toCbor();
231+
232+
serializedTx = await bobMultiSigWallet.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
233+
serializedTx = await charlotteMultiSigWallet.addSignatures({ sender: { id: 'e2e' }, tx: serializedTx });
229234

230-
await aliceMultiSigWallet.submitTx(tx);
235+
await aliceMultiSigWallet.submitTx(serializedTx);
231236

232237
await waitForTx(aliceMultiSigWallet, tx.id);
233238
const tx2ConfirmedState = await getWalletStateSnapshot(aliceMultiSigWallet);

packages/wallet/src/Wallets/BaseWallet.ts

+35-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
/* eslint-disable unicorn/no-nested-ternary */
22
// eslint-disable-next-line import/no-extraneous-dependencies
3+
import {
4+
AddSignaturesProps,
5+
Assets,
6+
FinalizeTxProps,
7+
HandleInfo,
8+
ObservableWallet,
9+
SignDataProps,
10+
SyncStatus,
11+
WalletAddress,
12+
WalletNetworkInfoProvider
13+
} from '../types';
314
import {
415
AddressDiscovery,
516
AddressTracker,
@@ -56,17 +67,6 @@ import {
5667
TxSubmitProvider,
5768
UtxoProvider
5869
} from '@cardano-sdk/core';
59-
import {
60-
Assets,
61-
FinalizeTxProps,
62-
HandleInfo,
63-
ObservableWallet,
64-
SignDataProps,
65-
SyncStatus,
66-
UpdateWitnessProps,
67-
WalletAddress,
68-
WalletNetworkInfoProvider
69-
} from '../types';
7070
import { BehaviorObservable, TrackerSubject, coldObservableProvider } from '@cardano-sdk/util-rxjs';
7171
import {
7272
BehaviorSubject,
@@ -878,15 +878,33 @@ export class BaseWallet implements ObservableWallet {
878878
return isEmpty ? [knownAddresses[0]] : [];
879879
}
880880

881-
/** Update the witness of a transaction with witness provided by this wallet */
882-
async updateWitness({ tx, sender }: UpdateWitnessProps): Promise<Cardano.Tx> {
883-
return this.finalizeTx({
884-
auxiliaryData: tx.auxiliaryData,
881+
async addSignatures({ tx, sender }: AddSignaturesProps): Promise<Serialization.TxCBOR> {
882+
const serializableTx = Serialization.Transaction.fromCbor(tx);
883+
const auxiliaryData = serializableTx.auxiliaryData()?.toCore();
884+
const body = serializableTx.body().toCore();
885+
const hash = serializableTx.getId();
886+
const witness = serializableTx.witnessSet().toCore();
887+
const bodyCbor = serializableTx.body().toCbor();
888+
889+
const witnessedTx = await this.finalizeTx({
890+
auxiliaryData,
891+
bodyCbor,
885892
signingContext: {
886893
sender
887894
},
888-
tx: { body: tx.body, hash: tx.id },
889-
witness: tx.witness
895+
tx: { body, hash },
896+
witness
890897
});
898+
899+
const coreWitness = witnessedTx.witness;
900+
const witnessSet = serializableTx.witnessSet();
901+
902+
witnessSet.setVkeys(
903+
Serialization.CborSet.fromCore([...coreWitness.signatures], Serialization.VkeyWitness.fromCore)
904+
);
905+
906+
serializableTx.setWitnessSet(witnessSet);
907+
908+
return serializableTx.toCbor();
891909
}
892910
}

packages/wallet/src/types.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ export type FinalizeTxProps = Omit<TxContext, 'signingContext'> & {
5555
signingContext?: Partial<SignTransactionContext>;
5656
};
5757

58-
export type UpdateWitnessProps = {
59-
tx: Cardano.Tx;
58+
export type AddSignaturesProps = {
59+
tx: Serialization.TxCBOR;
6060
sender?: MessageSender;
6161
};
6262

@@ -144,6 +144,9 @@ export interface ObservableWallet {
144144
*/
145145
getNextUnusedAddress(): Promise<WalletAddress[]>;
146146

147+
/** Updates the transaction witness set with signatures from this wallet. */
148+
addSignatures(props: AddSignaturesProps): Promise<Serialization.TxCBOR>;
149+
147150
shutdown(): void;
148151
}
149152

packages/wallet/test/PersonalWallet/methods.test.ts

+74
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from '@cardano-sdk/core';
2020
import { HexBlob } from '@cardano-sdk/util';
2121
import { InitializeTxProps } from '@cardano-sdk/tx-construction';
22+
import { babbageTx } from '../../../core/test/Serialization/testData';
2223
import { buildDRepIDFromDRepKey, toOutgoingTx, waitForWalletStateSettle } from '../util';
2324
import { getPassphrase, stakeKeyDerivationPath, testAsyncKeyAgent } from '../../../key-management/test/mocks';
2425
import { dummyLogger as logger } from 'ts-log';
@@ -534,6 +535,79 @@ describe('BaseWallet methods', () => {
534535
});
535536
});
536537

538+
describe('addSignatures', () => {
539+
it('adds the signatures and preserves all previous witnesses', async () => {
540+
const mockWitnesser = {
541+
signData: jest.fn(),
542+
witness: jest.fn().mockResolvedValue({
543+
cbor: Serialization.Transaction.fromCore(babbageTx).toCbor(),
544+
context: {
545+
handleResolutions: []
546+
},
547+
tx: {
548+
...babbageTx,
549+
witness: {
550+
...babbageTx.witness,
551+
signatures: new Map([
552+
...babbageTx.witness.signatures.entries(),
553+
[
554+
'0000000000000000000000000000000000000000000000000000000000000000',
555+
'0000000000000000000000000000000000000000000000000000000000000000'
556+
]
557+
])
558+
}
559+
}
560+
})
561+
};
562+
563+
wallet.shutdown();
564+
wallet = createPersonalWallet(
565+
{ name: 'Test Wallet' },
566+
{
567+
addressDiscovery,
568+
assetProvider,
569+
bip32Account,
570+
chainHistoryProvider,
571+
handleProvider,
572+
logger,
573+
networkInfoProvider,
574+
rewardsProvider,
575+
stakePoolProvider,
576+
txSubmitProvider,
577+
utxoProvider,
578+
witnesser: mockWitnesser
579+
}
580+
);
581+
582+
await waitForWalletStateSettle(wallet);
583+
584+
const serializedTx = Serialization.Transaction.fromCore(babbageTx).toCbor();
585+
const tx = await wallet.addSignatures({ tx: serializedTx });
586+
const updatedTx = Serialization.Transaction.fromCbor(tx).toCore();
587+
588+
expect(babbageTx.witness.bootstrap).toEqual(updatedTx.witness.bootstrap);
589+
expect(babbageTx.witness.datums).toEqual(updatedTx.witness.datums);
590+
expect(babbageTx.witness.redeemers).toEqual(updatedTx.witness.redeemers);
591+
expect(babbageTx.witness.scripts).toEqual(updatedTx.witness.scripts);
592+
593+
for (const [key, value] of Object.entries(babbageTx.witness.signatures)) {
594+
expect(value).toEqual(updatedTx.witness.signatures.get(key as Crypto.Ed25519PublicKeyHex));
595+
}
596+
597+
expect(updatedTx.witness.signatures.size).toEqual(babbageTx.witness.signatures.size + 1);
598+
expect(
599+
updatedTx.witness.signatures.get(
600+
'0000000000000000000000000000000000000000000000000000000000000000' as Crypto.Ed25519PublicKeyHex
601+
)
602+
).toEqual('0000000000000000000000000000000000000000000000000000000000000000');
603+
604+
// signed$ emits transaction and all its witnesses
605+
const signedTxs = await firstValueFrom(wallet.transactions.outgoing.signed$);
606+
607+
expect(signedTxs[0].tx).toEqual(updatedTx);
608+
});
609+
});
610+
537611
// eslint-disable-next-line sonarjs/cognitive-complexity
538612
describe('getNextUnusedAddress', () => {
539613
const script: Cardano.NativeScript = {

packages/web-extension/src/observableWallet/util.ts

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export const txBuilderProperties: RemoteApiProperties<Omit<TxBuilder, 'customize
9898
};
9999

100100
export const observableWalletProperties: RemoteApiProperties<ObservableWallet> = {
101+
addSignatures: RemoteApiPropertyType.MethodReturningPromise,
101102
addresses$: RemoteApiPropertyType.HotObservable,
102103
assetInfo$: RemoteApiPropertyType.HotObservable,
103104
balance: {

0 commit comments

Comments
 (0)