@rango-dev/provider-phantom 0.0.0-experimental-936229e8-20251208

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.
@@ -0,0 +1,27 @@
1
+ import { ProviderBuilder } from '@rango-dev/wallets-core';
2
+
3
+ import { metadata, WALLET_ID } from './constants.js';
4
+ import { evm } from './namespaces/evm.js';
5
+ import { solana } from './namespaces/solana.js';
6
+ import { sui } from './namespaces/sui.js';
7
+ import { utxo } from './namespaces/utxo.js';
8
+ import { phantom as phantomInstance } from './utils.js';
9
+
10
+ const buildProvider = () =>
11
+ new ProviderBuilder(WALLET_ID)
12
+ .init(function (context) {
13
+ const [, setState] = context.state();
14
+
15
+ if (phantomInstance()) {
16
+ setState('installed', true);
17
+ console.debug('[phantom] instance detected.', context);
18
+ }
19
+ })
20
+ .config('metadata', metadata)
21
+ .add('solana', solana)
22
+ .add('evm', evm)
23
+ .add('utxo', utxo)
24
+ .add('sui', sui)
25
+ .build();
26
+
27
+ export { buildProvider };
package/src/signer.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { Provider } from './utils.js';
2
+ import type { SignerFactory } from 'rango-types';
3
+
4
+ import { LegacyNetworks as Networks } from '@rango-dev/wallets-core/legacy';
5
+ import { getInstance as getSuiInstance } from '@rango-dev/wallets-core/namespaces/sui';
6
+ import {
7
+ dynamicImportWithRefinedError,
8
+ getNetworkInstance,
9
+ } from '@rango-dev/wallets-shared';
10
+ import { DefaultSignerFactory, TransactionType as TxType } from 'rango-types';
11
+
12
+ import { WALLET_NAME_IN_WALLET_STANDARD } from './constants.js';
13
+
14
+ export default async function getSigners(
15
+ provider: Provider
16
+ ): Promise<SignerFactory> {
17
+ const solProvider = getNetworkInstance(provider, Networks.SOLANA);
18
+ const evmProvider = getNetworkInstance(provider, Networks.ETHEREUM);
19
+ const bitcoinInstance = getNetworkInstance(provider, Networks.BTC);
20
+
21
+ const suiProvider = getSuiInstance(WALLET_NAME_IN_WALLET_STANDARD);
22
+
23
+ const { DefaultEvmSigner } = await dynamicImportWithRefinedError(
24
+ async () => await import('@rango-dev/signer-evm')
25
+ );
26
+ const { DefaultSolanaSigner } = await dynamicImportWithRefinedError(
27
+ async () => await import('@rango-dev/signer-solana')
28
+ );
29
+ const { BTCSigner } = await dynamicImportWithRefinedError(
30
+ async () => await import('./signers/utxoSigner.js')
31
+ );
32
+ const { DefaultSuiSigner } = await dynamicImportWithRefinedError(
33
+ async () => await import('@rango-dev/signer-sui')
34
+ );
35
+ const signers = new DefaultSignerFactory();
36
+ signers.registerSigner(TxType.SOLANA, new DefaultSolanaSigner(solProvider));
37
+ signers.registerSigner(TxType.EVM, new DefaultEvmSigner(evmProvider));
38
+ signers.registerSigner(TxType.TRANSFER, new BTCSigner(bitcoinInstance));
39
+ if (!!suiProvider) {
40
+ signers.registerSigner(TxType.SUI, new DefaultSuiSigner(suiProvider));
41
+ }
42
+ return signers;
43
+ }
@@ -0,0 +1,92 @@
1
+ import type { GenericSigner, Transfer } from 'rango-types';
2
+
3
+ import * as secp256k1 from '@bitcoinerlab/secp256k1';
4
+ import { Networks } from '@rango-dev/wallets-shared';
5
+ import * as bitcoin from 'bitcoinjs-lib';
6
+ import { SignerError } from 'rango-types';
7
+
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ type TransferExternalProvider = any;
10
+
11
+ const BTC_RPC_URL = 'https://go.getblock.io/f37bad28a991436483c0a3679a3acbee';
12
+
13
+ // TODO: use Uint8Array.fromBase64() static method and use this function as a polyfill after updating TypeScript DOM lib
14
+ function base64ToUint8Array(base64String: string) {
15
+ const binaryString = atob(base64String);
16
+ const length = binaryString.length;
17
+ const uint8Array = new Uint8Array(length);
18
+
19
+ for (let i = 0; i < length; i++) {
20
+ uint8Array[i] = binaryString.charCodeAt(i);
21
+ }
22
+
23
+ return uint8Array;
24
+ }
25
+
26
+ export class BTCSigner implements GenericSigner<Transfer> {
27
+ private provider: TransferExternalProvider;
28
+ constructor(provider: TransferExternalProvider) {
29
+ this.provider = provider;
30
+ }
31
+
32
+ async signMessage(): Promise<string> {
33
+ throw SignerError.UnimplementedError('signMessage');
34
+ }
35
+
36
+ async signAndSendTx(tx: Transfer): Promise<{ hash: string }> {
37
+ const { asset, psbt } = tx;
38
+
39
+ if (!psbt) {
40
+ throw new Error(
41
+ 'No PSBT found to sign. Ensure a valid PSBT is provided.'
42
+ );
43
+ }
44
+
45
+ if (asset.blockchain !== Networks.BTC) {
46
+ throw new Error(
47
+ `Signing ${asset.blockchain} transaction is not implemented by the signer.`
48
+ );
49
+ }
50
+ // Initialize ECC library
51
+ bitcoin.initEccLib(secp256k1);
52
+
53
+ const signedPSBTBytes = await this.provider.signPSBT(
54
+ base64ToUint8Array(psbt.unsignedPsbtBase64),
55
+ {
56
+ inputsToSign: psbt.inputsToSign,
57
+ }
58
+ );
59
+
60
+ // Finalize PSBT
61
+ const finalPsbt = bitcoin.Psbt.fromBuffer(Buffer.from(signedPSBTBytes));
62
+ finalPsbt.finalizeAllInputs();
63
+
64
+ const finalPsbtBaseHex = finalPsbt.extractTransaction().toHex();
65
+
66
+ // Broadcast PSBT to rpc node
67
+ const response = await fetch(BTC_RPC_URL, {
68
+ method: 'POST',
69
+ body: JSON.stringify({
70
+ method: 'sendrawtransaction',
71
+ params: [finalPsbtBaseHex],
72
+ }),
73
+ });
74
+
75
+ if (!response.ok) {
76
+ // Handle network and fetch errors
77
+ const errorText = await response.text();
78
+ throw new Error(`Error broadcasting transaction: ${errorText}`);
79
+ }
80
+
81
+ const data = await response.json();
82
+
83
+ if (!data.result) {
84
+ // Handle Bitcoin specific errors
85
+ throw new Error(
86
+ `Error broadcasting transaction. Error Code ${data.error.code}: ${data.error.message}`
87
+ );
88
+ }
89
+
90
+ return { hash: data.result };
91
+ }
92
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,98 @@
1
+ import type { ProviderAPI as EvmProviderApi } from '@rango-dev/wallets-core/namespaces/evm';
2
+ import type { ProviderAPI as SolanaProviderApi } from '@rango-dev/wallets-core/namespaces/solana';
3
+ import type { ProviderAPI as SuiProviderApi } from '@rango-dev/wallets-core/namespaces/sui';
4
+
5
+ import { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
6
+
7
+ export type Provider = Map<string, unknown>;
8
+
9
+ export function phantom(): Provider | null {
10
+ const { phantom } = window;
11
+
12
+ if (!phantom) {
13
+ return null;
14
+ }
15
+
16
+ const { solana, ethereum, bitcoin, sui } = phantom;
17
+
18
+ const instances: Provider = new Map();
19
+
20
+ if (ethereum && ethereum.isPhantom) {
21
+ instances.set(LegacyNetworks.ETHEREUM, ethereum);
22
+ }
23
+
24
+ if (solana && solana.isPhantom) {
25
+ instances.set(LegacyNetworks.SOLANA, solana);
26
+ }
27
+
28
+ if (bitcoin && bitcoin.isPhantom) {
29
+ instances.set(LegacyNetworks.BTC, bitcoin);
30
+ }
31
+ if (sui && sui.isPhantom) {
32
+ instances.set(LegacyNetworks.SUI, sui);
33
+ }
34
+
35
+ return instances;
36
+ }
37
+
38
+ export function getInstanceOrThrow(): Provider {
39
+ const instances = phantom();
40
+
41
+ if (!instances) {
42
+ throw new Error('Phantom is not injected. Please check your wallet.');
43
+ }
44
+
45
+ return instances;
46
+ }
47
+
48
+ export function evmPhantom(): EvmProviderApi {
49
+ const instances = phantom();
50
+
51
+ const evmInstance = instances?.get(LegacyNetworks.ETHEREUM);
52
+
53
+ if (!evmInstance) {
54
+ throw new Error(
55
+ 'Phantom not injected or EVM not enabled. Please check your wallet.'
56
+ );
57
+ }
58
+
59
+ return evmInstance as EvmProviderApi;
60
+ }
61
+
62
+ export function solanaPhantom(): SolanaProviderApi {
63
+ const instance = phantom();
64
+ const solanaInstance = instance?.get(LegacyNetworks.SOLANA);
65
+
66
+ if (!solanaInstance) {
67
+ throw new Error(
68
+ 'Phantom not injected or Solana not enabled. Please check your wallet.'
69
+ );
70
+ }
71
+
72
+ return solanaInstance;
73
+ }
74
+
75
+ export function bitcoinPhantom(): SolanaProviderApi {
76
+ const instance = phantom();
77
+ const bitcoinInstance = instance?.get(LegacyNetworks.BTC);
78
+
79
+ if (!bitcoinInstance) {
80
+ throw new Error(
81
+ 'Phantom not injected or Bitcoin not enabled. Please check your wallet.'
82
+ );
83
+ }
84
+
85
+ return bitcoinInstance;
86
+ }
87
+ export function suiPhantom(): SuiProviderApi {
88
+ const instance = phantom();
89
+ const suiInstance = instance?.get(LegacyNetworks.SUI);
90
+
91
+ if (!suiInstance) {
92
+ throw new Error(
93
+ 'Phantom not injected or Sui not enabled. Please check your wallet.'
94
+ );
95
+ }
96
+
97
+ return suiInstance as SuiProviderApi;
98
+ }