@swapkit/wallet-hardware 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/chunk-3h1f3hyp.js +4 -0
  2. package/dist/chunk-3h1f3hyp.js.map +10 -0
  3. package/dist/chunk-8m2sn76p.js +4 -0
  4. package/dist/chunk-8m2sn76p.js.map +10 -0
  5. package/dist/chunk-qjq7gvyx.js +4 -0
  6. package/dist/chunk-qjq7gvyx.js.map +10 -0
  7. package/dist/chunk-wfbg89d3.js +4 -0
  8. package/dist/chunk-wfbg89d3.js.map +10 -0
  9. package/dist/chunk-xn0fnag5.js +4 -0
  10. package/dist/chunk-xn0fnag5.js.map +10 -0
  11. package/dist/trezor/index.cjs +2 -2
  12. package/dist/trezor/index.cjs.map +9 -4
  13. package/dist/trezor/index.js +2 -2
  14. package/dist/trezor/index.js.map +3 -3
  15. package/dist/types/keepkey/index.d.ts +1 -1
  16. package/dist/types/ledger/index.d.ts +1 -1
  17. package/dist/types/trezor/cardanoSigner.d.ts +21 -0
  18. package/dist/types/trezor/cardanoSigner.d.ts.map +1 -0
  19. package/dist/types/trezor/index.d.ts +3 -3
  20. package/dist/types/trezor/index.d.ts.map +1 -1
  21. package/dist/types/trezor/rippleSigner.d.ts +23 -0
  22. package/dist/types/trezor/rippleSigner.d.ts.map +1 -0
  23. package/dist/types/trezor/solanaSigner.d.ts +17 -0
  24. package/dist/types/trezor/solanaSigner.d.ts.map +1 -0
  25. package/dist/types/trezor/stellarSigner.d.ts +99 -0
  26. package/dist/types/trezor/stellarSigner.d.ts.map +1 -0
  27. package/dist/types/trezor/tronSigner.d.ts +30 -0
  28. package/dist/types/trezor/tronSigner.d.ts.map +1 -0
  29. package/package.json +7 -3
  30. package/src/trezor/cardanoSigner.ts +226 -0
  31. package/src/trezor/index.ts +136 -43
  32. package/src/trezor/rippleSigner.ts +100 -0
  33. package/src/trezor/solanaSigner.ts +129 -0
  34. package/src/trezor/stellarSigner.ts +186 -0
  35. package/src/trezor/tronSigner.ts +148 -0
@@ -0,0 +1,186 @@
1
+ import { Chain, type DerivationPathArray, derivationPathToString, SwapKitError } from "@swapkit/helpers";
2
+ import type { StellarSigner, StellarTransaction } from "@swapkit/toolboxes/stellar";
3
+
4
+ type StellarClassicTransaction = Exclude<StellarTransaction, { innerTransaction: unknown }>;
5
+ type StellarOperation = StellarClassicTransaction["operations"][number];
6
+ type StellarAsset = Extract<StellarOperation, { type: "changeTrust" }>["line"];
7
+
8
+ const STELLAR_DECIMALS = 7;
9
+ const MAX_UINT32 = 4294967295n;
10
+ const ASSET_TYPES: Record<string, "NATIVE" | "ALPHANUM4" | "ALPHANUM12"> = {
11
+ credit_alphanum4: "ALPHANUM4",
12
+ credit_alphanum12: "ALPHANUM12",
13
+ native: "NATIVE",
14
+ };
15
+ const MEMO_TYPES = { hash: 3, id: 2, return: 4, text: 1 } as const;
16
+
17
+ function notSupported(reason: string) {
18
+ return new SwapKitError({ errorKey: "wallet_trezor_method_not_supported", info: { chain: Chain.Stellar, reason } });
19
+ }
20
+
21
+ function toStroops(amount: string) {
22
+ const match = /^(\d+)(?:\.(\d{1,7}))?$/.exec(amount);
23
+ if (!match) throw notSupported(`Invalid Stellar amount: ${amount}`);
24
+
25
+ const [, whole = "0", fraction = ""] = match;
26
+ return BigInt(`${whole}${fraction.padEnd(STELLAR_DECIMALS, "0")}`).toString();
27
+ }
28
+
29
+ function toTimeBound(value: string) {
30
+ if (!/^\d+$/.test(value) || BigInt(value) > MAX_UINT32) {
31
+ throw notSupported(`Unsupported Stellar time bound: ${value}`);
32
+ }
33
+ return Number(value);
34
+ }
35
+
36
+ function toTrezorAsset(asset: StellarAsset) {
37
+ const type = ASSET_TYPES[asset.getAssetType()];
38
+ if (!type || !("getCode" in asset)) throw notSupported("Trezor does not support liquidity pool assets");
39
+
40
+ return type === "NATIVE" ? { type } : { code: asset.getCode(), issuer: asset.getIssuer(), type };
41
+ }
42
+
43
+ function toTrezorOperation({ source, ...operation }: StellarOperation) {
44
+ const withSource = source ? { source } : {};
45
+
46
+ switch (operation.type) {
47
+ case "payment":
48
+ return {
49
+ ...withSource,
50
+ amount: toStroops(operation.amount),
51
+ asset: toTrezorAsset(operation.asset),
52
+ destination: operation.destination,
53
+ type: "payment" as const,
54
+ };
55
+ case "createAccount":
56
+ return {
57
+ ...withSource,
58
+ destination: operation.destination,
59
+ startingBalance: toStroops(operation.startingBalance),
60
+ type: "createAccount" as const,
61
+ };
62
+ case "changeTrust":
63
+ return {
64
+ ...withSource,
65
+ limit: toStroops(operation.limit),
66
+ line: toTrezorAsset(operation.line),
67
+ type: "changeTrust" as const,
68
+ };
69
+ default:
70
+ throw notSupported(`Unsupported Stellar operation: ${operation.type}`);
71
+ }
72
+ }
73
+
74
+ function decodeTextMemo(value: StellarClassicTransaction["memo"]["value"]) {
75
+ if (typeof value === "string") return value;
76
+
77
+ try {
78
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(value ?? undefined);
79
+ } catch {
80
+ throw notSupported("Trezor only signs UTF-8 text memos");
81
+ }
82
+ }
83
+
84
+ function toTrezorMemo({ type, value }: StellarClassicTransaction["memo"]) {
85
+ switch (type) {
86
+ case "none":
87
+ return undefined;
88
+ case "text":
89
+ return { text: decodeTextMemo(value), type: MEMO_TYPES.text };
90
+ case "id":
91
+ return { id: String(value), type: MEMO_TYPES.id };
92
+ case "hash":
93
+ case "return":
94
+ return { hash: Buffer.from(value ?? "").toString("hex"), type: MEMO_TYPES[type] };
95
+ default:
96
+ throw notSupported(`Unsupported Stellar memo type: ${type}`);
97
+ }
98
+ }
99
+
100
+ export function toTrezorStellarTransaction(transaction: StellarTransaction) {
101
+ if ("innerTransaction" in transaction) throw notSupported("Trezor cannot sign fee bump transactions");
102
+
103
+ if (!transaction.timeBounds) throw notSupported("Trezor requires transaction time bounds");
104
+ const { extraSigners, ledgerBounds, minAccountSequence, minAccountSequenceAge, minAccountSequenceLedgerGap } =
105
+ transaction;
106
+ const hasPreconditions = [ledgerBounds, minAccountSequence, minAccountSequenceAge, minAccountSequenceLedgerGap].some(
107
+ (precondition) => precondition !== undefined,
108
+ );
109
+ if (hasPreconditions || extraSigners?.length) {
110
+ throw notSupported("Trezor cannot sign Stellar transactions with extra preconditions");
111
+ }
112
+
113
+ return {
114
+ networkPassphrase: transaction.networkPassphrase,
115
+ transaction: {
116
+ fee: Number(transaction.fee),
117
+ memo: toTrezorMemo(transaction.memo),
118
+ operations: transaction.operations.map(toTrezorOperation),
119
+ sequence: transaction.sequence,
120
+ source: transaction.source,
121
+ timebounds: {
122
+ maxTime: toTimeBound(transaction.timeBounds.maxTime),
123
+ minTime: toTimeBound(transaction.timeBounds.minTime),
124
+ },
125
+ },
126
+ };
127
+ }
128
+
129
+ export function getStellarSigner({ derivationPath }: { derivationPath: DerivationPathArray }): StellarSigner {
130
+ if (derivationPath[3] || derivationPath[4]) {
131
+ throw new SwapKitError({
132
+ errorKey: "wallet_trezor_derivation_path_not_supported",
133
+ info: { chain: Chain.Stellar, derivationPath, reason: "Stellar only uses m/44'/148'/x'" },
134
+ });
135
+ }
136
+
137
+ const path = derivationPathToString(derivationPath.slice(0, 3) as [number, number, number]);
138
+ let address = "";
139
+
140
+ async function getAddress() {
141
+ if (address) return address;
142
+
143
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
144
+ const result = await TrezorConnect.stellarGetAddress({ path, showOnTrezor: true });
145
+
146
+ if (!result.success) {
147
+ throw new SwapKitError({
148
+ errorKey: "wallet_trezor_failed_to_get_address",
149
+ info: { chain: Chain.Stellar, derivationPath, error: result.payload.error },
150
+ });
151
+ }
152
+
153
+ address = result.payload.address;
154
+ return address;
155
+ }
156
+
157
+ async function signTransaction(transaction: StellarTransaction) {
158
+ const params = toTrezorStellarTransaction(transaction);
159
+ if (params.transaction.source !== (await getAddress())) {
160
+ throw notSupported("Transaction source does not match the Trezor address");
161
+ }
162
+
163
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
164
+ const result = await TrezorConnect.stellarSignTransaction({ path, ...params });
165
+
166
+ if (!result.success) {
167
+ throw new SwapKitError({
168
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
169
+ info: { chain: Chain.Stellar, error: result.payload.error },
170
+ });
171
+ }
172
+
173
+ try {
174
+ transaction.addSignature(address, Buffer.from(result.payload.signature, "hex").toString("base64"));
175
+ } catch (error) {
176
+ throw new SwapKitError({
177
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
178
+ info: { chain: Chain.Stellar, error },
179
+ });
180
+ }
181
+
182
+ return transaction;
183
+ }
184
+
185
+ return { getAddress, signTransaction };
186
+ }
@@ -0,0 +1,148 @@
1
+ import { Chain, type DerivationPathArray, derivationPathToString, SwapKitError } from "@swapkit/helpers";
2
+ import {
3
+ type TronSignedTransaction,
4
+ type TronSigner,
5
+ type TronTransaction,
6
+ tronAddressToHex,
7
+ } from "@swapkit/toolboxes/tron";
8
+
9
+ type TronFields = Record<string, unknown>;
10
+
11
+ const TRON_HEX_ADDRESS = /^41[0-9a-f]{40}$/i;
12
+ // Fields Trezor rebuilds itself; anything else would change the bytes it signs
13
+ const RAW_DATA_FIELDS = new Set([
14
+ "contract",
15
+ "data",
16
+ "expiration",
17
+ "fee_limit",
18
+ "ref_block_bytes",
19
+ "ref_block_hash",
20
+ "timestamp",
21
+ ]);
22
+ const CONTRACT_FIELDS = new Set(["parameter", "type"]);
23
+ const CONTRACT_VALUE_FIELDS: Record<string, Set<string>> = {
24
+ TransferContract: new Set(["amount", "owner_address", "to_address"]),
25
+ TriggerSmartContract: new Set(["contract_address", "data", "owner_address"]),
26
+ };
27
+ const PROTOBUF_DEFAULTS: unknown[] = [undefined, null, 0, "", false];
28
+
29
+ function notSupported(reason: string) {
30
+ return new SwapKitError({ errorKey: "wallet_trezor_method_not_supported", info: { chain: Chain.Tron, reason } });
31
+ }
32
+
33
+ function assertKnownFields(fields: TronFields, allowed: Set<string>, scope: string) {
34
+ const unknown = Object.keys(fields).filter(
35
+ (field) => !allowed.has(field) && !PROTOBUF_DEFAULTS.includes(fields[field]),
36
+ );
37
+ if (unknown.length > 0) throw notSupported(`Trezor cannot sign Tron ${scope} with ${unknown.join(", ")}`);
38
+ }
39
+
40
+ function getStringField(value: TronFields, field: string) {
41
+ const fieldValue = value[field];
42
+ if (typeof fieldValue !== "string" || !fieldValue) throw notSupported(`Missing ${field} in Tron contract`);
43
+ return fieldValue;
44
+ }
45
+
46
+ function getAmount(value: TronFields) {
47
+ const { amount } = value;
48
+ const isValid =
49
+ (typeof amount === "number" && Number.isSafeInteger(amount) && amount > 0) ||
50
+ (typeof amount === "string" && /^[1-9]\d*$/.test(amount));
51
+
52
+ if (!isValid) throw notSupported("Invalid amount in Tron contract");
53
+ return String(amount);
54
+ }
55
+
56
+ export function toTrezorTronContract(transaction: TronTransaction) {
57
+ const [contract, ...rest] = transaction.raw_data.contract;
58
+ if (!contract || rest.length > 0) throw notSupported("Trezor signs Tron transactions with exactly one contract");
59
+
60
+ const value = contract.parameter.value;
61
+ const allowedValueFields = CONTRACT_VALUE_FIELDS[contract.type];
62
+ if (!allowedValueFields) throw notSupported(`Unsupported Tron contract type: ${contract.type}`);
63
+
64
+ assertKnownFields(transaction.raw_data as unknown as TronFields, RAW_DATA_FIELDS, "transactions");
65
+ assertKnownFields(contract as unknown as TronFields, CONTRACT_FIELDS, "contracts");
66
+ assertKnownFields(value, allowedValueFields, contract.type);
67
+
68
+ // `visible: false` means the node already returned the addresses in hex
69
+ const toHexAddress = (field: string) => {
70
+ const address = getStringField(value, field);
71
+ if (transaction.visible !== false) return tronAddressToHex(address);
72
+ if (TRON_HEX_ADDRESS.test(address)) return address.toLowerCase();
73
+
74
+ throw notSupported(`Invalid hex ${field} in Tron contract`);
75
+ };
76
+
77
+ const owner_address = toHexAddress("owner_address");
78
+
79
+ if (contract.type === "TransferContract") {
80
+ return {
81
+ parameter: { value: { amount: getAmount(value), owner_address, to_address: toHexAddress("to_address") } },
82
+ type: "TransferContract" as const,
83
+ };
84
+ }
85
+
86
+ return {
87
+ parameter: {
88
+ value: { contract_address: toHexAddress("contract_address"), data: getStringField(value, "data"), owner_address },
89
+ },
90
+ type: "TriggerSmartContract" as const,
91
+ };
92
+ }
93
+
94
+ export function getTronSigner({
95
+ address: knownAddress,
96
+ derivationPath,
97
+ }: {
98
+ address?: string;
99
+ derivationPath: DerivationPathArray;
100
+ }): TronSigner {
101
+ const path = derivationPathToString(derivationPath);
102
+ let address = knownAddress ?? "";
103
+
104
+ return {
105
+ getAddress: async () => {
106
+ if (address) return address;
107
+
108
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
109
+ const result = await TrezorConnect.tronGetAddress({ path, showOnTrezor: true });
110
+
111
+ if (!result.success) {
112
+ throw new SwapKitError({
113
+ errorKey: "wallet_trezor_failed_to_get_address",
114
+ info: { chain: Chain.Tron, derivationPath, error: result.payload.error },
115
+ });
116
+ }
117
+
118
+ address = result.payload.address;
119
+ return address;
120
+ },
121
+
122
+ signTransaction: async (transaction: TronTransaction): Promise<TronSignedTransaction> => {
123
+ const contract = toTrezorTronContract(transaction);
124
+ const TrezorConnect = (await import("@trezor/connect-web")).default;
125
+ const { data, expiration, fee_limit, ref_block_bytes, ref_block_hash, timestamp } = transaction.raw_data;
126
+
127
+ const result = await TrezorConnect.tronSignTransaction({
128
+ contract: [contract],
129
+ data,
130
+ expiration,
131
+ fee_limit,
132
+ path,
133
+ ref_block_bytes,
134
+ ref_block_hash,
135
+ timestamp,
136
+ });
137
+
138
+ if (!result.success) {
139
+ throw new SwapKitError({
140
+ errorKey: "wallet_trezor_failed_to_sign_transaction",
141
+ info: { chain: Chain.Tron, error: result.payload.error },
142
+ });
143
+ }
144
+
145
+ return { ...transaction, signature: [result.payload.signature.replace(/^0x/, "")] };
146
+ },
147
+ };
148
+ }