@rango-dev/provider-trezor 0.29.0 → 0.29.1-next.1

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 (45) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/actions/utxo.d.ts +12 -0
  3. package/dist/chunk-FTGT4J2G.js +2 -0
  4. package/dist/chunk-FTGT4J2G.js.map +7 -0
  5. package/dist/chunk-HUUVMU7F.js +2 -0
  6. package/dist/chunk-HUUVMU7F.js.map +7 -0
  7. package/dist/{ethereum-AWO7VVVT.js → ethereum-WJFMSBHD.js} +2 -2
  8. package/dist/ethereum-WJFMSBHD.js.map +7 -0
  9. package/dist/init.d.ts +1 -0
  10. package/dist/mod.d.ts +1 -1
  11. package/dist/mod.js +1 -1
  12. package/dist/mod.js.map +4 -4
  13. package/dist/namespaces/utxo.d.ts +3 -0
  14. package/dist/provider.d.ts +1 -1
  15. package/dist/signers/utxo.d.ts +8 -0
  16. package/dist/state.d.ts +2 -0
  17. package/dist/{legacy/helpers.d.ts → utils.d.ts} +1 -2
  18. package/dist/utxo/config.d.ts +17 -0
  19. package/dist/utxo/psbt.d.ts +29 -0
  20. package/dist/utxo/psbt.test.d.ts +1 -0
  21. package/dist/utxo-MZNQ2QK4.js +2 -0
  22. package/dist/utxo-MZNQ2QK4.js.map +7 -0
  23. package/package.json +9 -4
  24. package/readme.md +15 -0
  25. package/src/actions/utxo.ts +56 -0
  26. package/src/constants.ts +23 -1
  27. package/src/init.ts +20 -0
  28. package/src/mod.ts +1 -5
  29. package/src/namespaces/evm.ts +4 -14
  30. package/src/namespaces/utxo.ts +24 -0
  31. package/src/provider.ts +2 -0
  32. package/src/signer.ts +4 -0
  33. package/src/signers/ethereum.ts +1 -1
  34. package/src/signers/utxo.ts +90 -0
  35. package/src/state.ts +15 -0
  36. package/src/{legacy/helpers.ts → utils.ts} +1 -14
  37. package/src/utxo/config.ts +67 -0
  38. package/src/utxo/psbt.test.ts +162 -0
  39. package/src/utxo/psbt.ts +96 -0
  40. package/dist/chunk-DRNBRMLU.js +0 -2
  41. package/dist/chunk-DRNBRMLU.js.map +0 -7
  42. package/dist/ethereum-AWO7VVVT.js.map +0 -7
  43. package/dist/legacy/index.d.ts +0 -18
  44. package/dist/provider-trezor.build.json +0 -1
  45. package/src/legacy/index.ts +0 -151
package/src/mod.ts CHANGED
@@ -1,12 +1,8 @@
1
1
  import { defineVersions } from '@hub3js/core/utils';
2
2
 
3
- import { buildLegacyProvider } from './legacy/index.js';
4
3
  import { buildProvider } from './provider.js';
5
4
 
6
5
  const versions = () =>
7
- defineVersions()
8
- .version('0.0.0', buildLegacyProvider())
9
- .version('1.0.0', buildProvider())
10
- .build();
6
+ defineVersions().version('1.0.0', buildProvider()).build();
11
7
 
12
8
  export { versions };
@@ -7,15 +7,12 @@ import { standardizeAndThrowError } from '@hub3js/std/operators';
7
7
  import { ETHEREUM_CHAIN_ID } from '@rango-dev/wallets-shared';
8
8
 
9
9
  import { WALLET_ID } from '../constants.js';
10
+ import { initTrezor } from '../init.js';
11
+ import { setDerivationPath } from '../state.js';
10
12
  import {
11
13
  getEthereumAccounts,
12
- getTrezorModule,
13
14
  getTrezorNormalizedDerivationPath,
14
- } from '../legacy/helpers.js';
15
- import { getTrezorManifest } from '../provider.js';
16
- import { setDerivationPath } from '../state.js';
17
-
18
- let isTrezorInitialized = false;
15
+ } from '../utils.js';
19
16
 
20
17
  const connect = builders
21
18
  .connect()
@@ -27,14 +24,7 @@ const connect = builders
27
24
  getTrezorNormalizedDerivationPath(options.derivationPath)
28
25
  );
29
26
 
30
- if (!isTrezorInitialized) {
31
- const TrezorConnect = await getTrezorModule();
32
- await TrezorConnect.init({
33
- lazyLoad: true, // this param will prevent iframe injection until TrezorConnect.method will be called
34
- manifest: getTrezorManifest(),
35
- });
36
- isTrezorInitialized = true;
37
- }
27
+ await initTrezor();
38
28
 
39
29
  const result = await getEthereumAccounts();
40
30
 
@@ -0,0 +1,24 @@
1
+ import type { UtxoActions } from '@rango-dev/wallets-core/namespaces/utxo';
2
+
3
+ import { NamespaceBuilder } from '@hub3js/core';
4
+ import * as commonBuilders from '@hub3js/std/builders';
5
+ import { standardizeAndThrowError } from '@hub3js/std/operators';
6
+ import { builders } from '@rango-dev/wallets-core/namespaces/utxo';
7
+
8
+ import { utxoActions } from '../actions/utxo.js';
9
+ import { WALLET_ID } from '../constants.js';
10
+
11
+ const connect = builders
12
+ .connect()
13
+ .action(utxoActions.connect())
14
+ .or(standardizeAndThrowError)
15
+ .build();
16
+
17
+ const disconnect = commonBuilders.disconnect<UtxoActions>().build();
18
+
19
+ const utxo = new NamespaceBuilder<UtxoActions>('UTXO', WALLET_ID)
20
+ .action(connect)
21
+ .action(disconnect)
22
+ .build();
23
+
24
+ export { utxo };
package/src/provider.ts CHANGED
@@ -4,6 +4,7 @@ import { ProviderBuilder } from '@hub3js/core';
4
4
 
5
5
  import { metadata, WALLET_ID } from './constants.js';
6
6
  import { evm } from './namespaces/evm.js';
7
+ import { utxo } from './namespaces/utxo.js';
7
8
 
8
9
  let trezorManifest: Environments['manifest'];
9
10
 
@@ -24,6 +25,7 @@ const buildProvider = () =>
24
25
  })
25
26
  .config('metadata', metadata)
26
27
  .add('evm', evm)
28
+ .add('utxo', utxo)
27
29
  .build();
28
30
 
29
31
  export { buildProvider };
package/src/signer.ts CHANGED
@@ -8,6 +8,10 @@ export default async function getSigners(): Promise<SignerFactory> {
8
8
  const { EthereumSigner } = await dynamicImportWithRefinedError(
9
9
  async () => await import('./signers/ethereum.js')
10
10
  );
11
+ const { BTCSigner } = await dynamicImportWithRefinedError(
12
+ async () => await import('./signers/utxo.js')
13
+ );
11
14
  signers.registerSigner(TxType.EVM, new EthereumSigner());
15
+ signers.registerSigner(TxType.TRANSFER, new BTCSigner());
12
16
  return signers;
13
17
  }
@@ -5,8 +5,8 @@ import { DEFAULT_ETHEREUM_RPC_URL } from '@rango-dev/wallets-shared';
5
5
  import { JsonRpcProvider, Transaction } from 'ethers';
6
6
  import { type GenericSigner } from 'rango-types';
7
7
 
8
- import { getTrezorModule, trezorErrorMessages } from '../legacy/helpers.js';
9
8
  import { getDerivationPath } from '../state.js';
9
+ import { getTrezorModule, trezorErrorMessages } from '../utils.js';
10
10
 
11
11
  export function getTrezorErrorMessage(error: unknown) {
12
12
  if (
@@ -0,0 +1,90 @@
1
+ import type { GenericSigner, Transfer } from 'rango-types';
2
+
3
+ import { Networks } from '@rango-dev/wallets-shared';
4
+ import { SignerError } from 'rango-types';
5
+
6
+ import { getBitcoinDerivationPath } from '../state.js';
7
+ import { getTrezorModule } from '../utils.js';
8
+ import { BITCOIN_COIN_NAME } from '../utxo/config.js';
9
+ import { buildTrezorBitcoinTransaction } from '../utxo/psbt.js';
10
+
11
+ function getTrezorErrorMessage(payload: { error: string; code?: string }) {
12
+ return new Error(
13
+ payload.code ? `${payload.error} (${payload.code})` : payload.error
14
+ );
15
+ }
16
+
17
+ export class BTCSigner implements GenericSigner<Transfer> {
18
+ async signMessage(): Promise<string> {
19
+ throw SignerError.UnimplementedError('signMessage');
20
+ }
21
+
22
+ async signAndSendTx(tx: Transfer): Promise<{ hash: string }> {
23
+ const { blockchain } = tx.asset;
24
+ if (blockchain !== Networks.BTC) {
25
+ throw new Error(
26
+ `Signing ${blockchain} transactions is not supported by Trezor.`
27
+ );
28
+ }
29
+
30
+ const { psbt } = tx;
31
+ if (!psbt) {
32
+ throw new Error(
33
+ 'No PSBT found to sign. Ensure a valid PSBT is provided.'
34
+ );
35
+ }
36
+
37
+ const path = getBitcoinDerivationPath();
38
+ if (!path) {
39
+ throw new Error(
40
+ 'No connected Bitcoin account found. Please connect the wallet first.'
41
+ );
42
+ }
43
+
44
+ const TrezorConnect = await getTrezorModule();
45
+ const { inputs, outputs, version, locktime } =
46
+ buildTrezorBitcoinTransaction(psbt.unsignedPsbtBase64, path);
47
+
48
+ // `push: true` signs and broadcasts via Trezor's Blockbook backend.
49
+ const result = await TrezorConnect.signTransaction({
50
+ coin: BITCOIN_COIN_NAME,
51
+ inputs,
52
+ outputs,
53
+ version,
54
+ locktime,
55
+ push: true,
56
+ });
57
+
58
+ if (!result.success) {
59
+ throw getTrezorErrorMessage(result.payload);
60
+ }
61
+
62
+ return { hash: await this.#resolveHash(result.payload) };
63
+ }
64
+
65
+ /**
66
+ * `push: true` usually returns the txid directly. If it isn't present (older
67
+ * firmware/connect), push the serialized transaction explicitly as a fallback.
68
+ */
69
+ async #resolveHash(payload: {
70
+ txid?: string;
71
+ serializedTx?: string;
72
+ }): Promise<string> {
73
+ if (payload.txid) {
74
+ return payload.txid;
75
+ }
76
+ if (!payload.serializedTx) {
77
+ throw new Error('Trezor did not return a transaction id.');
78
+ }
79
+
80
+ const TrezorConnect = await getTrezorModule();
81
+ const pushResult = await TrezorConnect.pushTransaction({
82
+ tx: payload.serializedTx,
83
+ coin: BITCOIN_COIN_NAME,
84
+ });
85
+ if (!pushResult.success) {
86
+ throw getTrezorErrorMessage(pushResult.payload);
87
+ }
88
+ return pushResult.payload.txid;
89
+ }
90
+ }
package/src/state.ts CHANGED
@@ -8,3 +8,18 @@ export function setDerivationPath(path: string) {
8
8
  export function getDerivationPath() {
9
9
  return derivationPath;
10
10
  }
11
+
12
+ /*
13
+ * Bitcoin's connect-time path, kept for the same reason as the EVM one: Rango's PSBT
14
+ * carries no derivation data, so the signer must remember which path to sign with. Kept
15
+ * separate from the EVM path so the two namespaces never collide.
16
+ */
17
+ let bitcoinDerivationPath = '';
18
+
19
+ export function setBitcoinDerivationPath(path: string) {
20
+ bitcoinDerivationPath = path;
21
+ }
22
+
23
+ export function getBitcoinDerivationPath() {
24
+ return bitcoinDerivationPath;
25
+ }
@@ -3,11 +3,10 @@ import type { TrezorConnect } from '@trezor/connect-web';
3
3
  import {
4
4
  dynamicImportWithRefinedError,
5
5
  ETHEREUM_CHAIN_ID,
6
- Networks,
7
6
  type ProviderConnectResult,
8
7
  } from '@rango-dev/wallets-shared';
9
8
 
10
- import { getDerivationPath } from '../state';
9
+ import { getDerivationPath } from './state.js';
11
10
 
12
11
  export const trezorErrorMessages: { [statusCode: string]: string } = {
13
12
  Failure_ActionCancelled: 'User rejected the transaction.',
@@ -29,18 +28,6 @@ export async function getTrezorModule() {
29
28
  return mod.default;
30
29
  }
31
30
 
32
- export function getTrezorInstance() {
33
- /*
34
- * Instances have a required property which is `chainId` and is using in swap execution.
35
- * Here we are setting it as Ethereum always since we are supporting only eth for now.
36
- */
37
- const instances = new Map();
38
-
39
- instances.set(Networks.ETHEREUM, { chainId: ETHEREUM_CHAIN_ID });
40
-
41
- return instances;
42
- }
43
-
44
31
  export async function getEthereumAccounts(): Promise<ProviderConnectResult> {
45
32
  const TrezorConnect = await getTrezorModule();
46
33
  const derivationPath = getDerivationPath();
@@ -0,0 +1,67 @@
1
+ /*
2
+ * Trezor needs the input `script_type` to sign a Bitcoin input. It is determined by the
3
+ * BIP-43 `purpose` of the derivation path the address was derived from, so we expose the
4
+ * four common address types and let the user connect whichever one they hold funds on.
5
+ */
6
+ export type TrezorInputScriptType =
7
+ | 'SPENDADDRESS'
8
+ | 'SPENDP2SHWITNESS'
9
+ | 'SPENDWITNESS'
10
+ | 'SPENDTAPROOT';
11
+
12
+ export interface BitcoinAddressType {
13
+ /** Stable id used in the derivationPath metadata entries. */
14
+ id: 'legacy' | 'nested-segwit' | 'native-segwit' | 'taproot';
15
+ label: string;
16
+ /** BIP-43 purpose: 44 legacy, 49 nested segwit, 84 native segwit, 86 taproot. */
17
+ purpose: number;
18
+ inputScriptType: TrezorInputScriptType;
19
+ }
20
+
21
+ export const BITCOIN_COIN_NAME = 'btc';
22
+
23
+ export const BITCOIN_ADDRESS_TYPES: readonly BitcoinAddressType[] = [
24
+ {
25
+ id: 'native-segwit',
26
+ label: 'Native SegWit',
27
+ purpose: 84,
28
+ inputScriptType: 'SPENDWITNESS',
29
+ },
30
+ {
31
+ id: 'nested-segwit',
32
+ label: 'Nested SegWit',
33
+ purpose: 49,
34
+ inputScriptType: 'SPENDP2SHWITNESS',
35
+ },
36
+ {
37
+ id: 'legacy',
38
+ label: 'Legacy',
39
+ purpose: 44,
40
+ inputScriptType: 'SPENDADDRESS',
41
+ },
42
+ {
43
+ id: 'taproot',
44
+ label: 'Taproot',
45
+ purpose: 86,
46
+ inputScriptType: 'SPENDTAPROOT',
47
+ },
48
+ ] as const;
49
+
50
+ /**
51
+ * Resolve the Trezor input script type for a derivation path by reading its BIP-43
52
+ * purpose (the first hardened level). The path always comes from one of our own
53
+ * derivation templates, so an unknown purpose is a programming error.
54
+ */
55
+ export function resolveBitcoinScriptType(path: string): TrezorInputScriptType {
56
+ const purpose = Number.parseInt(
57
+ path.replace(/^m\//, '').split('/')[0]?.replace(/['h]$/, '') ?? '',
58
+ 10
59
+ );
60
+ const addressType = BITCOIN_ADDRESS_TYPES.find(
61
+ (type) => type.purpose === purpose
62
+ );
63
+ if (!addressType) {
64
+ throw new Error(`Unsupported Bitcoin derivation path: ${path}`);
65
+ }
66
+ return addressType.inputScriptType;
67
+ }
@@ -0,0 +1,162 @@
1
+ /* eslint-disable @typescript-eslint/no-magic-numbers */
2
+ import * as ecc from '@bitcoinerlab/secp256k1';
3
+ import * as bitcoin from 'bitcoinjs-lib';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ import { buildTrezorBitcoinTransaction } from './psbt.js';
7
+
8
+ bitcoin.initEccLib(ecc);
9
+ const NETWORK = bitcoin.networks.bitcoin;
10
+
11
+ const PRIVATE_KEY = Buffer.alloc(32, 7);
12
+ const PUBKEY = Buffer.from(
13
+ ecc.pointFromScalar(PRIVATE_KEY, true) as Uint8Array
14
+ );
15
+ const { address: OWN_ADDRESS } = bitcoin.payments.p2wpkh({
16
+ pubkey: PUBKEY,
17
+ network: NETWORK,
18
+ });
19
+
20
+ const NATIVE_SEGWIT_PATH = "m/84'/0'/0'/0/0";
21
+
22
+ // Non-palindromic on purpose, so the txid byte-reversal is actually exercised.
23
+ const FAKE_PREV_TXID =
24
+ '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
25
+ const RECIPIENT = 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq';
26
+
27
+ function nativeSegwitInput() {
28
+ const witnessScript = bitcoin.payments.p2wpkh({
29
+ pubkey: PUBKEY,
30
+ network: NETWORK,
31
+ }).output as Buffer;
32
+ return {
33
+ hash: FAKE_PREV_TXID,
34
+ index: 0,
35
+ witnessUtxo: { script: witnessScript, value: 100_000 },
36
+ };
37
+ }
38
+
39
+ describe('buildTrezorBitcoinTransaction', () => {
40
+ it('maps each input to the connected path and its script type', () => {
41
+ const psbt = new bitcoin.Psbt({ network: NETWORK });
42
+ psbt.addInput(nativeSegwitInput());
43
+ psbt.addOutput({ address: RECIPIENT, value: 90_000 });
44
+
45
+ const { inputs } = buildTrezorBitcoinTransaction(
46
+ psbt.toBase64(),
47
+ NATIVE_SEGWIT_PATH,
48
+ NETWORK
49
+ );
50
+
51
+ expect(inputs).toHaveLength(1);
52
+ expect(inputs[0].address_n).toBe(NATIVE_SEGWIT_PATH);
53
+ expect(inputs[0].script_type).toBe('SPENDWITNESS');
54
+ expect(inputs[0].prev_hash).toBe(FAKE_PREV_TXID);
55
+ expect(inputs[0].prev_index).toBe(0);
56
+ expect(inputs[0].amount).toBe('100000');
57
+ });
58
+
59
+ it('preserves version, locktime and per-input sequence from the PSBT', () => {
60
+ const psbt = new bitcoin.Psbt({ network: NETWORK });
61
+ psbt.setVersion(2);
62
+ psbt.setLocktime(800_000);
63
+ // RBF-signaling sequence.
64
+ psbt.addInput({ ...nativeSegwitInput(), sequence: 0xfffffffd });
65
+ psbt.addOutput({ address: RECIPIENT, value: 90_000 });
66
+
67
+ const { inputs, version, locktime } = buildTrezorBitcoinTransaction(
68
+ psbt.toBase64(),
69
+ NATIVE_SEGWIT_PATH,
70
+ NETWORK
71
+ );
72
+
73
+ expect(version).toBe(2);
74
+ expect(locktime).toBe(800_000);
75
+ expect(inputs[0].sequence).toBe(0xfffffffd);
76
+ });
77
+
78
+ it('rejects an input with a non-SIGHASH_ALL sighash type', () => {
79
+ const psbt = new bitcoin.Psbt({ network: NETWORK });
80
+ psbt.addInput({
81
+ ...nativeSegwitInput(),
82
+ sighashType: bitcoin.Transaction.SIGHASH_SINGLE,
83
+ });
84
+ psbt.addOutput({ address: RECIPIENT, value: 90_000 });
85
+
86
+ expect(() =>
87
+ buildTrezorBitcoinTransaction(
88
+ psbt.toBase64(),
89
+ NATIVE_SEGWIT_PATH,
90
+ NETWORK
91
+ )
92
+ ).toThrow(/only SIGHASH_ALL is supported/);
93
+ });
94
+
95
+ it('reconstructs outputs as address payments', () => {
96
+ const psbt = new bitcoin.Psbt({ network: NETWORK });
97
+ psbt.addInput(nativeSegwitInput());
98
+ psbt.addOutput({ address: RECIPIENT, value: 60_000 });
99
+ psbt.addOutput({ address: OWN_ADDRESS as string, value: 39_000 });
100
+
101
+ const { outputs } = buildTrezorBitcoinTransaction(
102
+ psbt.toBase64(),
103
+ NATIVE_SEGWIT_PATH,
104
+ NETWORK
105
+ );
106
+
107
+ expect(outputs).toEqual([
108
+ { script_type: 'PAYTOADDRESS', address: RECIPIENT, amount: '60000' },
109
+ { script_type: 'PAYTOADDRESS', address: OWN_ADDRESS, amount: '39000' },
110
+ ]);
111
+ });
112
+
113
+ it('derives the script type from the path purpose (legacy P2PKH)', () => {
114
+ const p2pkh = bitcoin.payments.p2pkh({ pubkey: PUBKEY, network: NETWORK });
115
+
116
+ // A previous transaction that funds the legacy address at vout 0.
117
+ const prevTx = new bitcoin.Transaction();
118
+ prevTx.version = 2;
119
+ prevTx.addInput(Buffer.alloc(32, 1), 0);
120
+ prevTx.addOutput(p2pkh.output as Buffer, 100_000);
121
+
122
+ const psbt = new bitcoin.Psbt({ network: NETWORK });
123
+ psbt.addInput({
124
+ hash: prevTx.getId(),
125
+ index: 0,
126
+ nonWitnessUtxo: prevTx.toBuffer(),
127
+ });
128
+ psbt.addOutput({ address: RECIPIENT, value: 90_000 });
129
+
130
+ const { inputs } = buildTrezorBitcoinTransaction(
131
+ psbt.toBase64(),
132
+ "m/44'/0'/0'/0/0",
133
+ NETWORK
134
+ );
135
+
136
+ expect(inputs[0].script_type).toBe('SPENDADDRESS');
137
+ expect(inputs[0].amount).toBe('100000');
138
+ });
139
+
140
+ it('encodes an OP_RETURN output as PAYTOOPRETURN', () => {
141
+ const memo = Buffer.from('rango', 'utf8');
142
+ const psbt = new bitcoin.Psbt({ network: NETWORK });
143
+ psbt.addInput(nativeSegwitInput());
144
+ psbt.addOutput({ address: RECIPIENT, value: 90_000 });
145
+ psbt.addOutput({
146
+ script: bitcoin.payments.embed({ data: [memo] }).output as Buffer,
147
+ value: 0,
148
+ });
149
+
150
+ const { outputs } = buildTrezorBitcoinTransaction(
151
+ psbt.toBase64(),
152
+ NATIVE_SEGWIT_PATH,
153
+ NETWORK
154
+ );
155
+
156
+ expect(outputs[1]).toEqual({
157
+ script_type: 'PAYTOOPRETURN',
158
+ amount: '0',
159
+ op_return_data: memo.toString('hex'),
160
+ });
161
+ });
162
+ });
@@ -0,0 +1,96 @@
1
+ import type { SignTransaction } from '@trezor/connect-web';
2
+
3
+ import * as ecc from '@bitcoinerlab/secp256k1';
4
+ import * as bitcoin from 'bitcoinjs-lib';
5
+
6
+ import { resolveBitcoinScriptType } from './config.js';
7
+
8
+ // Lets bitcoinjs-lib decode taproot (witness v1) output addresses.
9
+ bitcoin.initEccLib(ecc);
10
+
11
+ type TrezorInput = SignTransaction['inputs'][number];
12
+ type TrezorOutput = SignTransaction['outputs'][number];
13
+
14
+ export interface TrezorBitcoinTransaction {
15
+ inputs: TrezorInput[];
16
+ outputs: TrezorOutput[];
17
+ /** Transaction version from the PSBT — preserved so the signed tx matches Rango's. */
18
+ version: number;
19
+ /** Transaction locktime from the PSBT (e.g. for CLTV) — preserved as-is. */
20
+ locktime: number;
21
+ }
22
+
23
+ const reverseTxid = (hash: Buffer) =>
24
+ Buffer.from(hash).reverse().toString('hex');
25
+
26
+ /**
27
+ * Translate Rango's unsigned PSBT into the transaction Trezor's `signTransaction` expects
28
+ * (Trezor has no "sign this PSBT" call). Every field that affects the resulting bytes is
29
+ * preserved: `version`, `locktime`, and per-input `sequence` (RBF / locktime signaling),
30
+ * alongside each input's amount and script.
31
+ *
32
+ * Rango's PSBT carries no derivation data, so the connected `path` (and the script type
33
+ * from its BIP-43 purpose) is applied to every input — Rango funds the swap from that
34
+ * single address. `refTxs` are omitted: Trezor Connect fetches previous transactions from
35
+ * its Blockbook backend. Pure function -> unit testable.
36
+ *
37
+ * Only SIGHASH_ALL is supported: a non-default per-input sighash can't be honored through
38
+ * Trezor's signTransaction, so we reject it rather than sign the wrong thing. Multisig /
39
+ * script-path spends are out of scope (Rango funds from a single-key address).
40
+ */
41
+ export function buildTrezorBitcoinTransaction(
42
+ unsignedPsbtBase64: string,
43
+ path: string,
44
+ network: bitcoin.Network = bitcoin.networks.bitcoin
45
+ ): TrezorBitcoinTransaction {
46
+ const psbt = bitcoin.Psbt.fromBase64(unsignedPsbtBase64, { network });
47
+ const scriptType = resolveBitcoinScriptType(path);
48
+
49
+ const inputs = psbt.txInputs.map((input, index): TrezorInput => {
50
+ const data = psbt.data.inputs[index];
51
+
52
+ if (
53
+ data.sighashType != null &&
54
+ data.sighashType !== bitcoin.Transaction.SIGHASH_ALL
55
+ ) {
56
+ throw new Error(
57
+ `PSBT input #${index} uses sighash type ${data.sighashType}; only SIGHASH_ALL is supported.`
58
+ );
59
+ }
60
+
61
+ const utxo =
62
+ data.witnessUtxo ??
63
+ bitcoin.Transaction.fromBuffer(data.nonWitnessUtxo as Buffer).outs[
64
+ input.index
65
+ ];
66
+ return {
67
+ // Trezor Connect accepts the path string directly (like our EVM signer).
68
+ address_n: path,
69
+ prev_hash: reverseTxid(input.hash),
70
+ prev_index: input.index,
71
+ amount: utxo.value.toString(),
72
+ script_type: scriptType,
73
+ sequence: input.sequence,
74
+ };
75
+ });
76
+
77
+ const outputs = psbt.txOutputs.map(
78
+ (output): TrezorOutput =>
79
+ output.address
80
+ ? {
81
+ script_type: 'PAYTOADDRESS',
82
+ address: output.address,
83
+ amount: output.value.toString(),
84
+ }
85
+ : {
86
+ script_type: 'PAYTOOPRETURN',
87
+ amount: '0',
88
+ op_return_data: (
89
+ bitcoin.script.decompile(output.script)?.find(Buffer.isBuffer) ??
90
+ Buffer.alloc(0)
91
+ ).toString('hex'),
92
+ }
93
+ );
94
+
95
+ return { inputs, outputs, version: psbt.version, locktime: psbt.locktime };
96
+ }
@@ -1,2 +0,0 @@
1
- var s=Object.defineProperty;var e=(t,r)=>s(t,"name",{value:r,configurable:!0});var n="";function l(t){n=t}e(l,"setDerivationPath");function a(){return n}e(a,"getDerivationPath");import{dynamicImportWithRefinedError as c,ETHEREUM_CHAIN_ID as i,Networks as u}from"@rango-dev/wallets-shared";var w={Failure_ActionCancelled:"User rejected the transaction."};async function d(){let t=await c(async()=>await import("@trezor/connect-web"));return t.default.default?t.default.default:t.default}e(d,"getTrezorModule");function E(){let t=new Map;return t.set(u.ETHEREUM,{chainId:i}),t}e(E,"getTrezorInstance");async function z(){let t=await d(),r=a(),o=await t.ethereumGetAddress({path:r});if(!o.success)throw new Error(o.payload.error);return{accounts:[o.payload.address],chainId:i,derivationPath:r}}e(z,"getEthereumAccounts");var P=e(t=>t&&!t.startsWith("m/")?"m/"+t:t,"getTrezorNormalizedDerivationPath");export{e as a,l as b,a as c,w as d,d as e,E as f,z as g,P as h};
2
- //# sourceMappingURL=chunk-DRNBRMLU.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/state.ts", "../src/legacy/helpers.ts"],
4
- "sourcesContent": ["// We keep derivationPath here because we need to maintain it for signing transactions after it is set in connect method\nlet derivationPath = '';\n\nexport function setDerivationPath(path: string) {\n derivationPath = path;\n}\n\nexport function getDerivationPath() {\n return derivationPath;\n}\n", "import type { TrezorConnect } from '@trezor/connect-web';\n\nimport {\n dynamicImportWithRefinedError,\n ETHEREUM_CHAIN_ID,\n Networks,\n type ProviderConnectResult,\n} from '@rango-dev/wallets-shared';\n\nimport { getDerivationPath } from '../state';\n\nexport const trezorErrorMessages: { [statusCode: string]: string } = {\n Failure_ActionCancelled: 'User rejected the transaction.',\n};\n\n// `@trezor/connect-web` is commonjs, when we are importing it dynamically, it has some differences in different tooling. for example vite (you can check widget-examples), goes throw error. this is a workaround for solving this interop issue.\nexport async function getTrezorModule() {\n const mod = await dynamicImportWithRefinedError(\n async () => await import('@trezor/connect-web')\n );\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n if (mod.default.default) {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n return mod.default.default as unknown as TrezorConnect;\n }\n\n return mod.default;\n}\n\nexport function getTrezorInstance() {\n /*\n * Instances have a required property which is `chainId` and is using in swap execution.\n * Here we are setting it as Ethereum always since we are supporting only eth for now.\n */\n const instances = new Map();\n\n instances.set(Networks.ETHEREUM, { chainId: ETHEREUM_CHAIN_ID });\n\n return instances;\n}\n\nexport async function getEthereumAccounts(): Promise<ProviderConnectResult> {\n const TrezorConnect = await getTrezorModule();\n const derivationPath = getDerivationPath();\n const result = await TrezorConnect.ethereumGetAddress({\n path: derivationPath,\n });\n\n if (!result.success) {\n throw new Error(result.payload.error);\n }\n\n return {\n accounts: [result.payload.address],\n chainId: ETHEREUM_CHAIN_ID,\n derivationPath,\n };\n}\n\nexport const getTrezorNormalizedDerivationPath = (\n path: string // TrezorConnect needs master node to be added to derivation path\n) => (path && !path.startsWith('m/') ? 'm/' + path : path);\n"],
5
- "mappings": "+EACA,IAAIA,EAAiB,GAEd,SAASC,EAAkBC,EAAc,CAC9CF,EAAiBE,CACnB,CAFgBC,EAAAF,EAAA,qBAIT,SAASG,GAAoB,CAClC,OAAOJ,CACT,CAFgBG,EAAAC,EAAA,qBCLhB,OACE,iCAAAC,EACA,qBAAAC,EACA,YAAAC,MAEK,4BAIA,IAAMC,EAAwD,CACnE,wBAAyB,gCAC3B,EAGA,eAAsBC,GAAkB,CACtC,IAAMC,EAAM,MAAMC,EAChB,SAAY,KAAM,QAAO,qBAAqB,CAChD,EAGA,OAAID,EAAI,QAAQ,QAGPA,EAAI,QAAQ,QAGdA,EAAI,OACb,CAbsBE,EAAAH,EAAA,mBAef,SAASI,GAAoB,CAKlC,IAAMC,EAAY,IAAI,IAEtB,OAAAA,EAAU,IAAIC,EAAS,SAAU,CAAE,QAASC,CAAkB,CAAC,EAExDF,CACT,CAVgBF,EAAAC,EAAA,qBAYhB,eAAsBI,GAAsD,CAC1E,IAAMC,EAAgB,MAAMT,EAAgB,EACtCU,EAAiBC,EAAkB,EACnCC,EAAS,MAAMH,EAAc,mBAAmB,CACpD,KAAMC,CACR,CAAC,EAED,GAAI,CAACE,EAAO,QACV,MAAM,IAAI,MAAMA,EAAO,QAAQ,KAAK,EAGtC,MAAO,CACL,SAAU,CAACA,EAAO,QAAQ,OAAO,EACjC,QAASL,EACT,eAAAG,CACF,CACF,CAhBsBP,EAAAK,EAAA,uBAkBf,IAAMK,EAAoCV,EAC/CW,GACIA,GAAQ,CAACA,EAAK,WAAW,IAAI,EAAI,KAAOA,EAAOA,EAFJ",
6
- "names": ["derivationPath", "setDerivationPath", "path", "__name", "getDerivationPath", "dynamicImportWithRefinedError", "ETHEREUM_CHAIN_ID", "Networks", "trezorErrorMessages", "getTrezorModule", "mod", "dynamicImportWithRefinedError", "__name", "getTrezorInstance", "instances", "Networks", "ETHEREUM_CHAIN_ID", "getEthereumAccounts", "TrezorConnect", "derivationPath", "getDerivationPath", "result", "getTrezorNormalizedDerivationPath", "path"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/signers/ethereum.ts"],
4
- "sourcesContent": ["import type { EvmTransaction } from 'rango-types/mainApi';\n\nimport { cleanEvmError, toHexQuantity } from '@rango-dev/signer-evm';\nimport { DEFAULT_ETHEREUM_RPC_URL } from '@rango-dev/wallets-shared';\nimport { JsonRpcProvider, Transaction } from 'ethers';\nimport { type GenericSigner } from 'rango-types';\n\nimport { getTrezorModule, trezorErrorMessages } from '../legacy/helpers.js';\nimport { getDerivationPath } from '../state.js';\n\nexport function getTrezorErrorMessage(error: unknown) {\n if (\n typeof error === 'object' &&\n error !== null &&\n 'shortMessage' in error &&\n typeof error.shortMessage === 'string'\n ) {\n /*\n * Some error signs have lengthy, challenging-to-read messages.\n * shortMessage is used because it is shorter and easier to understand.\n */\n return new Error(error.shortMessage, { cause: error });\n }\n return cleanEvmError(error);\n}\n\nexport class EthereumSigner implements GenericSigner<EvmTransaction> {\n async signMessage(msg: string): Promise<string> {\n const TrezorConnect = await getTrezorModule();\n\n const { success, payload } = await TrezorConnect.ethereumSignMessage({\n message: msg,\n path: getDerivationPath(),\n });\n if (!success) {\n throw new Error(payload.error);\n }\n return payload.signature;\n }\n\n async signAndSendTx(\n tx: EvmTransaction,\n fromAddress: string,\n chainId: string\n ): Promise<{ hash: string }> {\n try {\n const TrezorConnect = await getTrezorModule();\n const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = tx;\n const isEIP1559 = maxFeePerGas && maxPriorityFeePerGas;\n\n if (isEIP1559 && !maxFeePerGas) {\n throw new Error('Missing maxFeePerGas');\n }\n if (isEIP1559 && !maxPriorityFeePerGas) {\n throw new Error('Missing maxPriorityFeePerGas');\n }\n if (!isEIP1559 && !gasPrice) {\n throw new Error('Missing gasPrice');\n }\n const provider = new JsonRpcProvider(DEFAULT_ETHEREUM_RPC_URL); // Provider to broadcast transaction\n const transactionCount = await provider.getTransactionCount(fromAddress); // Get nonce\n const additionalFields = isEIP1559\n ? {\n maxFeePerGas: toHexQuantity(maxFeePerGas || '0'),\n maxPriorityFeePerGas: toHexQuantity(maxPriorityFeePerGas || '0'),\n }\n : {\n gasPrice: toHexQuantity(gasPrice || '0'),\n };\n\n const transaction = {\n to: tx.to,\n data: tx.data || '0x',\n value: toHexQuantity(tx.value?.toString() || '0'),\n gasLimit: toHexQuantity(tx.gasLimit?.toString() || '0'),\n chainId: Number.parseInt(chainId),\n nonce: toHexQuantity(transactionCount.toString()),\n ...additionalFields,\n };\n\n const { success, payload } = await TrezorConnect.ethereumSignTransaction({\n path: getDerivationPath(),\n transaction,\n });\n\n if (!success) {\n const errorMessage =\n trezorErrorMessages[payload?.code || ''] || payload.error;\n throw new Error(errorMessage);\n }\n const { r, s, v } = payload;\n\n const serializedTx = Transaction.from({\n ...transaction,\n nonce: Number.parseInt(transaction.nonce),\n /*\n * Type 0: This refers to the legacy transaction type that has been used since Ethereum's inception.\n * Type 2: This refers to the new transaction type introduced with the EIP-1559 (Ethereum Improvement Proposal 1559) update,\n * which was part of the London hard fork.\n */\n type: isEIP1559 ? 2 : 0,\n signature: { r, s, v: parseInt(v) },\n }).serialized;\n const broadcastResult = await provider.broadcastTransaction(serializedTx);\n\n return { hash: broadcastResult.hash };\n } catch (error) {\n throw getTrezorErrorMessage(error);\n }\n }\n}\n"],
5
- "mappings": "6DAEA,OAAS,iBAAAA,EAAe,iBAAAC,MAAqB,wBAC7C,OAAS,4BAAAC,MAAgC,4BACzC,OAAS,mBAAAC,EAAiB,eAAAC,MAAmB,SAC7C,MAAmC,cAK5B,SAASC,EAAsBC,EAAgB,CACpD,OACE,OAAOA,GAAU,UACjBA,IAAU,MACV,iBAAkBA,GAClB,OAAOA,EAAM,cAAiB,SAMvB,IAAI,MAAMA,EAAM,aAAc,CAAE,MAAOA,CAAM,CAAC,EAEhDC,EAAcD,CAAK,CAC5B,CAdgBE,EAAAH,EAAA,yBAgBT,IAAMI,EAAN,KAA8D,CA1BrE,MA0BqE,CAAAD,EAAA,uBACnE,MAAM,YAAYE,EAA8B,CAC9C,IAAMC,EAAgB,MAAMC,EAAgB,EAEtC,CAAE,QAAAC,EAAS,QAAAC,CAAQ,EAAI,MAAMH,EAAc,oBAAoB,CACnE,QAASD,EACT,KAAMK,EAAkB,CAC1B,CAAC,EACD,GAAI,CAACF,EACH,MAAM,IAAI,MAAMC,EAAQ,KAAK,EAE/B,OAAOA,EAAQ,SACjB,CAEA,MAAM,cACJE,EACAC,EACAC,EAC2B,CAC3B,GAAI,CACF,IAAMP,EAAgB,MAAMC,EAAgB,EACtC,CAAE,SAAAO,EAAU,aAAAC,EAAc,qBAAAC,CAAqB,EAAIL,EACnDM,EAAYF,GAAgBC,EAElC,GAAIC,GAAa,CAACF,EAChB,MAAM,IAAI,MAAM,sBAAsB,EAExC,GAAIE,GAAa,CAACD,EAChB,MAAM,IAAI,MAAM,8BAA8B,EAEhD,GAAI,CAACC,GAAa,CAACH,EACjB,MAAM,IAAI,MAAM,kBAAkB,EAEpC,IAAMI,EAAW,IAAIC,EAAgBC,CAAwB,EACvDC,EAAmB,MAAMH,EAAS,oBAAoBN,CAAW,EACjEU,EAAmBL,EACrB,CACE,aAAcM,EAAcR,GAAgB,GAAG,EAC/C,qBAAsBQ,EAAcP,GAAwB,GAAG,CACjE,EACA,CACE,SAAUO,EAAcT,GAAY,GAAG,CACzC,EAEEU,EAAc,CAClB,GAAIb,EAAG,GACP,KAAMA,EAAG,MAAQ,KACjB,MAAOY,EAAcZ,EAAG,OAAO,SAAS,GAAK,GAAG,EAChD,SAAUY,EAAcZ,EAAG,UAAU,SAAS,GAAK,GAAG,EACtD,QAAS,OAAO,SAASE,CAAO,EAChC,MAAOU,EAAcF,EAAiB,SAAS,CAAC,EAChD,GAAGC,CACL,EAEM,CAAE,QAAAd,EAAS,QAAAC,CAAQ,EAAI,MAAMH,EAAc,wBAAwB,CACvE,KAAMI,EAAkB,EACxB,YAAAc,CACF,CAAC,EAED,GAAI,CAAChB,EAAS,CACZ,IAAMiB,EACJC,EAAoBjB,GAAS,MAAQ,EAAE,GAAKA,EAAQ,MACtD,MAAM,IAAI,MAAMgB,CAAY,CAC9B,CACA,GAAM,CAAE,EAAAE,EAAG,EAAAC,EAAG,CAAE,EAAInB,EAEdoB,EAAeC,EAAY,KAAK,CACpC,GAAGN,EACH,MAAO,OAAO,SAASA,EAAY,KAAK,EAMxC,KAAMP,EAAY,EAAI,EACtB,UAAW,CAAE,EAAAU,EAAG,EAAAC,EAAG,EAAG,SAAS,CAAC,CAAE,CACpC,CAAC,EAAE,WAGH,MAAO,CAAE,MAFe,MAAMV,EAAS,qBAAqBW,CAAY,GAEzC,IAAK,CACtC,OAAS5B,EAAO,CACd,MAAMD,EAAsBC,CAAK,CACnC,CACF,CACF",
6
- "names": ["cleanEvmError", "toHexQuantity", "DEFAULT_ETHEREUM_RPC_URL", "JsonRpcProvider", "Transaction", "getTrezorErrorMessage", "error", "cleanEvmError", "__name", "EthereumSigner", "msg", "TrezorConnect", "getTrezorModule", "success", "payload", "getDerivationPath", "tx", "fromAddress", "chainId", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "isEIP1559", "provider", "JsonRpcProvider", "DEFAULT_ETHEREUM_RPC_URL", "transactionCount", "additionalFields", "toHexQuantity", "transaction", "errorMessage", "trezorErrorMessages", "r", "s", "serializedTx", "Transaction"]
7
- }
@@ -1,18 +0,0 @@
1
- import type { Environments } from '../types.js';
2
- import type { LegacyProviderInterface } from '@rango-dev/wallets-core/legacy';
3
- import type { Connect, WalletInfo } from '@rango-dev/wallets-shared';
4
- import { WalletTypes } from '@rango-dev/wallets-shared';
5
- import { type BlockchainMeta, type SignerFactory } from 'rango-types';
6
- import { getTrezorInstance } from './helpers.js';
7
- export declare const config: {
8
- type: WalletTypes;
9
- };
10
- export type { Environments };
11
- type Provider = any;
12
- export declare const init: (environments: Environments) => void;
13
- export declare const getInstance: typeof getTrezorInstance;
14
- export declare const connect: Connect;
15
- export declare const getSigners: (provider: Provider) => Promise<SignerFactory>;
16
- export declare const getWalletInfo: (allBlockChains: BlockchainMeta[]) => WalletInfo;
17
- declare const buildLegacyProvider: () => LegacyProviderInterface;
18
- export { buildLegacyProvider };