Skip to content

Commit df125cd

Browse files
committed
feat: sign shared wallet tx with ledger
1 parent a78683f commit df125cd

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

packages/hardware-ledger/src/transformers/txOut.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ import { LedgerTxTransformerContext } from '../types';
55
import { mapTokenMap } from './assets';
66
import { util } from '@cardano-sdk/key-management';
77

8+
const isScriptAddress = (address: string): boolean => {
9+
const baseAddress = Cardano.Address.fromBech32(address).asBase();
10+
const paymentCredential = baseAddress?.getPaymentCredential();
11+
const stakeCredential = baseAddress?.getStakeCredential();
12+
return (
13+
paymentCredential?.type === Cardano.CredentialType.ScriptHash &&
14+
stakeCredential?.type === Cardano.CredentialType.ScriptHash
15+
);
16+
};
17+
818
const toInlineDatum: Transform<Cardano.PlutusData, Ledger.Datum> = (datum) => ({
919
datumHex: Serialization.PlutusData.fromCore(datum).toCbor(),
1020
type: Ledger.DatumType.INLINE
@@ -20,8 +30,9 @@ const toDestination: Transform<Cardano.TxOut, Ledger.TxOutputDestination, Ledger
2030
context
2131
) => {
2232
const knownAddress = context?.knownAddresses.find((address) => address.address === txOut.address);
33+
const isScriptWallet = isScriptAddress(txOut.address);
2334

24-
if (knownAddress) {
35+
if (knownAddress && !isScriptWallet) {
2536
const paymentKeyPath = util.paymentKeyPathFromGroupedAddress(knownAddress);
2637
const stakeKeyPath = util.stakeKeyPathFromGroupedAddress(knownAddress);
2738

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import * as Crypto from '@cardano-sdk/crypto';
2+
import { BaseWallet, createSharedWallet } from '../../../src';
3+
import { Cardano } from '@cardano-sdk/core';
4+
import { CommunicationType, KeyPurpose, KeyRole, util } from '@cardano-sdk/key-management';
5+
import { InitializeTxProps, InitializeTxResult } from '@cardano-sdk/tx-construction';
6+
import { LedgerKeyAgent } from '@cardano-sdk/hardware-ledger';
7+
import { dummyLogger as logger } from 'ts-log';
8+
import { mockProviders as mocks } from '@cardano-sdk/util-dev';
9+
10+
describe('LedgerSharedWalletKeyAgent', () => {
11+
let ledgerKeyAgent: LedgerKeyAgent;
12+
let wallet: BaseWallet;
13+
14+
beforeAll(async () => {
15+
ledgerKeyAgent = await LedgerKeyAgent.createWithDevice(
16+
{
17+
chainId: Cardano.ChainIds.Preprod,
18+
communicationType: CommunicationType.Node,
19+
purpose: KeyPurpose.MULTI_SIG
20+
},
21+
{ bip32Ed25519: await Crypto.SodiumBip32Ed25519.create(), logger }
22+
);
23+
});
24+
25+
afterAll(async () => {
26+
await ledgerKeyAgent.deviceConnection?.transport.close();
27+
});
28+
29+
describe('signTransaction', () => {
30+
let txInternals: InitializeTxResult;
31+
32+
beforeAll(async () => {
33+
// Create the wallet key and script components first
34+
const walletPubKey = await ledgerKeyAgent.derivePublicKey({ index: 0, role: KeyRole.External });
35+
const walletKeyHash = ledgerKeyAgent.bip32Ed25519.getPubKeyHash(walletPubKey);
36+
37+
const walletStakePubKey = await ledgerKeyAgent.derivePublicKey({ index: 0, role: KeyRole.Stake });
38+
const walletStakeKeyHash = ledgerKeyAgent.bip32Ed25519.getPubKeyHash(walletStakePubKey);
39+
40+
// Define the payment and staking scripts
41+
const paymentScript: Cardano.NativeScript = {
42+
__type: Cardano.ScriptType.Native,
43+
kind: Cardano.NativeScriptKind.RequireAnyOf,
44+
scripts: [
45+
{
46+
__type: Cardano.ScriptType.Native,
47+
keyHash: walletKeyHash,
48+
kind: Cardano.NativeScriptKind.RequireSignature
49+
},
50+
{
51+
__type: Cardano.ScriptType.Native,
52+
keyHash: Crypto.Ed25519KeyHashHex('b275b08c999097247f7c17e77007c7010cd19f20cc086ad99d398539'),
53+
kind: Cardano.NativeScriptKind.RequireSignature
54+
}
55+
]
56+
};
57+
58+
const stakingScript: Cardano.NativeScript = {
59+
__type: Cardano.ScriptType.Native,
60+
kind: Cardano.NativeScriptKind.RequireAnyOf,
61+
scripts: [
62+
{
63+
__type: Cardano.ScriptType.Native,
64+
keyHash: walletStakeKeyHash,
65+
kind: Cardano.NativeScriptKind.RequireSignature
66+
},
67+
{
68+
__type: Cardano.ScriptType.Native,
69+
keyHash: Crypto.Ed25519KeyHashHex('b275b08c999097247f7c17e77007c7010cd19f20cc086ad99d398539'),
70+
kind: Cardano.NativeScriptKind.RequireSignature
71+
}
72+
]
73+
};
74+
75+
const outputs: Cardano.TxOut[] = [
76+
{
77+
address: Cardano.PaymentAddress(
78+
'addr_test1qpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5ewvxwdrt70qlcpeeagscasafhffqsxy36t90ldv06wqrk2qum8x5w'
79+
),
80+
scriptReference: paymentScript,
81+
value: { coins: 11_111_111n }
82+
}
83+
];
84+
const props: InitializeTxProps = {
85+
outputs: new Set<Cardano.TxOut>(outputs)
86+
};
87+
88+
wallet = createSharedWallet(
89+
{ name: 'Shared HW Wallet' },
90+
{
91+
assetProvider: mocks.mockAssetProvider(),
92+
chainHistoryProvider: mocks.mockChainHistoryProvider(),
93+
logger,
94+
networkInfoProvider: mocks.mockNetworkInfoProvider(),
95+
paymentScript,
96+
rewardAccountInfoProvider: mocks.mockRewardAccountInfoProvider(),
97+
rewardsProvider: mocks.mockRewardsProvider(),
98+
stakingScript,
99+
txSubmitProvider: mocks.mockTxSubmitProvider(),
100+
utxoProvider: mocks.mockUtxoProvider(),
101+
witnesser: util.createBip32Ed25519Witnesser(util.createAsyncKeyAgent(ledgerKeyAgent))
102+
}
103+
);
104+
txInternals = await wallet.initializeTx(props);
105+
});
106+
107+
afterAll(() => wallet.shutdown());
108+
109+
it('successfully signs a transaction', async () => {
110+
const tx = await wallet.finalizeTx({ tx: txInternals });
111+
expect(tx).toBeDefined();
112+
});
113+
});
114+
});

0 commit comments

Comments
 (0)