@rango-dev/provider-ctrl 0.62.1-next.2

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 +319 -0
  2. package/dist/actions/utxo.d.ts +17 -0
  3. package/dist/actions/utxo.d.ts.map +1 -0
  4. package/dist/builders/utxo.d.ts +21 -0
  5. package/dist/builders/utxo.d.ts.map +1 -0
  6. package/dist/constants.d.ts +23 -0
  7. package/dist/constants.d.ts.map +1 -0
  8. package/dist/mod.d.ts +3 -0
  9. package/dist/mod.d.ts.map +1 -0
  10. package/dist/mod.js +2 -0
  11. package/dist/mod.js.map +7 -0
  12. package/dist/namespaces/evm.d.ts +4 -0
  13. package/dist/namespaces/evm.d.ts.map +1 -0
  14. package/dist/namespaces/solana.d.ts +4 -0
  15. package/dist/namespaces/solana.d.ts.map +1 -0
  16. package/dist/namespaces/utxo.d.ts +4 -0
  17. package/dist/namespaces/utxo.d.ts.map +1 -0
  18. package/dist/provider-ctrl.build.json +1 -0
  19. package/dist/provider.d.ts +3 -0
  20. package/dist/provider.d.ts.map +1 -0
  21. package/dist/signer.d.ts +4 -0
  22. package/dist/signer.d.ts.map +1 -0
  23. package/dist/signers/solana.d.ts +13 -0
  24. package/dist/signers/solana.d.ts.map +1 -0
  25. package/dist/signers/utxo.d.ts +17 -0
  26. package/dist/signers/utxo.d.ts.map +1 -0
  27. package/dist/types.d.ts +14 -0
  28. package/dist/types.d.ts.map +1 -0
  29. package/dist/utils.d.ts +25 -0
  30. package/dist/utils.d.ts.map +1 -0
  31. package/package.json +37 -0
  32. package/readme.md +80 -0
  33. package/src/actions/utxo.ts +32 -0
  34. package/src/builders/utxo.ts +46 -0
  35. package/src/constants.ts +84 -0
  36. package/src/mod.ts +8 -0
  37. package/src/namespaces/evm.ts +50 -0
  38. package/src/namespaces/solana.ts +39 -0
  39. package/src/namespaces/utxo.ts +44 -0
  40. package/src/provider.ts +24 -0
  41. package/src/signer.ts +28 -0
  42. package/src/signers/solana.ts +22 -0
  43. package/src/signers/utxo.ts +167 -0
  44. package/src/types.ts +18 -0
  45. package/src/utils.ts +141 -0
@@ -0,0 +1,17 @@
1
+ import type { Provider } from '../types.js';
2
+ import type { GenericSigner, Transfer } from 'rango-types';
3
+ /**
4
+ * One signer for all of Ctrl's UTXO chains. BTC is signed via PSBT (`sign_psbt`);
5
+ * LTC/DOGE/BCH use the generic `transfer` request. It receives the whole provider
6
+ * map and resolves the right per-chain instance per transaction.
7
+ */
8
+ export declare class CustomTransferSigner implements GenericSigner<Transfer> {
9
+ #private;
10
+ private provider;
11
+ constructor(provider: Provider);
12
+ signMessage(): Promise<string>;
13
+ signAndSendTx(tx: Transfer): Promise<{
14
+ hash: string;
15
+ }>;
16
+ }
17
+ //# sourceMappingURL=utxo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utxo.d.ts","sourceRoot":"","sources":["../../src/signers/utxo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,aAAa,CAAC;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAqD3D;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,aAAa,CAAC,QAAQ,CAAC;;IAClE,OAAO,CAAC,QAAQ,CAAW;gBACf,QAAQ,EAAE,QAAQ;IAIxB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IAI9B,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAgG7D"}
@@ -0,0 +1,14 @@
1
+ import type { ProviderAPI as EvmProviderApi } from '@hub3js/evm';
2
+ import type { ProviderAPI as SolanaProviderApi } from '@hub3js/solana';
3
+ import type { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
4
+ import type { ProviderAPI as UtxoProviderApi } from '@rango-dev/wallets-core/namespaces/utxo';
5
+ export type ProviderObject = {
6
+ [LegacyNetworks.ETHEREUM]: EvmProviderApi;
7
+ [LegacyNetworks.SOLANA]: SolanaProviderApi;
8
+ [LegacyNetworks.BTC]: UtxoProviderApi;
9
+ [LegacyNetworks.LTC]: UtxoProviderApi;
10
+ [LegacyNetworks.DOGE]: UtxoProviderApi;
11
+ [LegacyNetworks.BCH]: UtxoProviderApi;
12
+ };
13
+ export type Provider = Map<keyof ProviderObject, ProviderObject[keyof ProviderObject]>;
14
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,IAAI,cAAc,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAE9F,MAAM,MAAM,cAAc,GAAG;IAC3B,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1C,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3C,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC;IACtC,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC;IACtC,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,eAAe,CAAC;IACvC,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,GAAG,CACxB,MAAM,cAAc,EACpB,cAAc,CAAC,MAAM,cAAc,CAAC,CACrC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { Provider } from './types.js';
2
+ import type { ProviderAPI as EvmProviderApi } from '@hub3js/evm';
3
+ import type { ProviderAPI as SolanaProviderApi } from '@hub3js/solana';
4
+ import type { CaipAccount } from '@hub3js/std/types';
5
+ export declare function ctrl(): Provider | null;
6
+ export declare function getInstanceOrThrow(): Provider;
7
+ export declare function evmCtrl(): EvmProviderApi;
8
+ export declare function solanaCtrl(): SolanaProviderApi;
9
+ /**
10
+ * The EVM instance, used as the trigger source for UTXO account changes.
11
+ *
12
+ * Ctrl switches the active account across every chain at once but only signals it
13
+ * reliably through the EVM provider's `accountsChanged` (the UTXO providers emit an
14
+ * empty `{}` payload, and re-fetching them while disconnected opens a wallet popup).
15
+ * Returns an empty object when EVM isn't injected so the subscriber attaches safely.
16
+ */
17
+ export declare function evmEventSource(): EvmProviderApi;
18
+ /**
19
+ * Fetch and CAIP-format accounts across every available UTXO chain, merged into a
20
+ * single array. Each account encodes its own chain via its CAIP reference, so
21
+ * BTC/LTC/DOGE/BCH addresses coexist in one UTXO namespace. Silent while the wallet
22
+ * is connected (only called on connect / switch, never on disconnect).
23
+ */
24
+ export declare function getAllUtxoAccounts(): Promise<CaipAccount[]>;
25
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,IAAI,cAAc,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQrD,wBAAgB,IAAI,IAAI,QAAQ,GAAG,IAAI,CAiCtC;AAED,wBAAgB,kBAAkB,IAAI,QAAQ,CAQ7C;AAED,wBAAgB,OAAO,IAAI,cAAc,CAWxC;AAED,wBAAgB,UAAU,IAAI,iBAAiB,CAW9C;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,IAAI,cAAc,CAE/C;AAoBD;;;;;GAKG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAsBjE"}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@rango-dev/provider-ctrl",
3
+ "version": "0.62.1-next.2",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "source": "./src/mod.ts",
7
+ "main": "./dist/mod.js",
8
+ "exports": {
9
+ ".": "./dist/mod.js"
10
+ },
11
+ "typings": "dist/mod.d.ts",
12
+ "files": [
13
+ "dist",
14
+ "src"
15
+ ],
16
+ "scripts": {
17
+ "build": "node ../../scripts/build/command.mjs --path wallets/provider-ctrl --inputs src/mod.ts",
18
+ "ts-check": "tsc --declaration --emitDeclarationOnly -p ./tsconfig.json",
19
+ "clean": "rimraf dist",
20
+ "format": "prettier --write '{.,src}/**/*.{ts,tsx}'",
21
+ "lint": "eslint \"**/*.{ts,tsx}\""
22
+ },
23
+ "dependencies": {
24
+ "@hub3js/core": "^0.60.1",
25
+ "@hub3js/evm": "^0.2.1",
26
+ "@hub3js/solana": "^0.2.1",
27
+ "@hub3js/std": "^0.1.1",
28
+ "@rango-dev/signer-evm": "^0.42.0",
29
+ "@rango-dev/signer-solana": "^0.48.0",
30
+ "@rango-dev/wallets-core": "^0.59.1-next.1",
31
+ "bs58": "^5.0.0",
32
+ "rango-types": "^0.5.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ }
37
+ }
package/readme.md ADDED
@@ -0,0 +1,80 @@
1
+ # Ctrl (formerly XDEFI)
2
+
3
+ Ctrl Wallet integration for hub.
4
+ [Homepage](https://ctrl.xyz/) | [Docs](https://developers.ctrl.xyz/)
5
+
6
+ More about implementation status can be found [here](../readme.md).
7
+
8
+ ## Implementation notes/limitations
9
+
10
+ ### Group
11
+
12
+ #### ✅ EVM
13
+
14
+ Supports the full EVM set Rango exposes (`evmBlockchains`), plus arbitrary EVM
15
+ chains via `wallet_addEthereumChain` / `wallet_switchEthereumChain`. Not limited to
16
+ a fixed list.
17
+
18
+ #### ⚠️ UTXO
19
+
20
+ A single UTXO namespace grouping **Bitcoin, Litecoin, Dogecoin, and Bitcoin Cash**
21
+ (this is the only multi-chain UTXO wallet in the hub). `connect` returns the
22
+ addresses of every available UTXO chain at once, each CAIP-encoded with its own
23
+ `bip122` chain id.
24
+
25
+ Signing differs per chain:
26
+
27
+ - **Bitcoin** is signed and broadcast by the wallet via `sign_psbt` (PSBT, `broadcast: true`).
28
+ - **Litecoin / Dogecoin / Bitcoin Cash** use the wallet's `transfer` method.
29
+
30
+ #### ✅ Solana
31
+
32
+ Supported. Message signing uses a custom signer because Ctrl exposes
33
+ `signMessage(bytes)` directly (rather than the `request({ method: 'signMessage' })`
34
+ form), then base58-encodes the signature.
35
+
36
+
37
+ ### Feature
38
+
39
+ #### ⚠️ Switch Account
40
+
41
+ EVM and Solana use their providers' native `accountsChanged` events.
42
+
43
+ UTXO is different: Ctrl's UTXO providers emit `accountsChanged` with an **empty
44
+ `{}` payload**, so they can't report the new account themselves, and re-fetching
45
+ them while disconnected opens a wallet popup. Because Ctrl switches the active
46
+ account across **all** chains at once and signals it reliably on the **EVM
47
+ provider**, the UTXO namespace is driven off the EVM `accountsChanged`: on a switch
48
+ (non-empty array) it re-fetches all UTXO chains; on disconnect (empty array) it
49
+ disconnects. This means UTXO switch/disconnect detection relies on EVM being
50
+ connected alongside UTXO — always the case in practice, since Ctrl grants all chains
51
+ together on connect.
52
+
53
+ **Known issues — Ctrl-wallet behavior, not Rango logic, and currently not worked
54
+ around here:**
55
+
56
+ - **Live switching snaps back.** Ctrl pins the dApp to the account it connected with,
57
+ so switching the active account in the wallet (without disconnecting first) snaps
58
+ back to the connected one. To use a different account you must disconnect the dApp
59
+ (Ctrl → Connected dApps) → switch account → reconnect.
60
+ - **Switching to a single-namespace (private-key-imported) account is unreliable,
61
+ because Ctrl's UTXO providers give no usable account info and the EVM `[]` signal is
62
+ ambiguous (full disconnect vs. switched-to-a-non-EVM account):**
63
+ - **→ EVM-only account:** the BTC/UTXO namespace may stay connected (stale) instead
64
+ of dropping.
65
+ - **→ BTC-only account:** the whole wallet may disconnect instead of keeping BTC
66
+ connected.
67
+
68
+ #### ⚠️ Disconnect
69
+
70
+ A wallet-initiated disconnect is detected through the EVM provider's empty
71
+ `accountsChanged` (see Switch Account). The UTXO providers expose no usable
72
+ disconnect signal on their own.
73
+
74
+ #### ⚠️ Cross Browser
75
+
76
+ Ctrl is supported **only on Chromium-based browsers** (e.g. Chrome, Brave).
77
+
78
+ ---
79
+
80
+ More wallet information can be found in [readme.md](../readme.md).
@@ -0,0 +1,32 @@
1
+ import type { Context, FunctionWithContext } from '@hub3js/core';
2
+
3
+ import { type UtxoActions } from '@rango-dev/wallets-core/namespaces/utxo';
4
+
5
+ import { getAllUtxoAccounts } from '../utils.js';
6
+
7
+ /**
8
+ * Connect the UTXO namespace across all of Ctrl's UTXO chains at once.
9
+ *
10
+ * Ctrl exposes a separate provider per chain (BTC/LTC/DOGE/BCH), and a UTXO account
11
+ * holds an address on each simultaneously (they're concurrent, not mutually exclusive
12
+ * like EVM networks). So we fetch every available chain's account and return one merged
13
+ * array where each entry is CAIP-encoded with its own `bip122` chain id — letting the
14
+ * single UTXO namespace represent all four chains. Throws only if no chain returns an
15
+ * address.
16
+ */
17
+ export function connect(): FunctionWithContext<
18
+ UtxoActions['connect'],
19
+ Context
20
+ > {
21
+ return async () => {
22
+ const accounts = await getAllUtxoAccounts();
23
+
24
+ if (!accounts.length) {
25
+ throw new Error("Couldn't find any address!");
26
+ }
27
+
28
+ return accounts;
29
+ };
30
+ }
31
+
32
+ export const utxoActions = { connect };
@@ -0,0 +1,46 @@
1
+ import type { ProviderAPI as EvmProviderApi } from '@hub3js/evm';
2
+
3
+ import { ActionBuilder } from '@hub3js/core';
4
+ import { ChangeAccountSubscriberBuilder } from '@hub3js/std/hooks';
5
+ import { type UtxoActions } from '@rango-dev/wallets-core/namespaces/utxo';
6
+
7
+ import { getAllUtxoAccounts } from '../utils.js';
8
+
9
+ /**
10
+ * Ctrl switches the active account across all chains at once, but its UTXO providers
11
+ * emit an empty `accountsChanged` payload (`{}`) and re-fetching them while
12
+ * disconnected opens a wallet popup — so they can't drive the update themselves. The
13
+ * EVM provider, however, emits a proper `accountsChanged`: a non-empty array on
14
+ * switch and an empty array on disconnect. We therefore drive UTXO off the EVM
15
+ * signal: re-fetch all UTXO chains on a switch (silent while connected), and
16
+ * disconnect WITHOUT re-fetching (so no popup) when EVM reports empty.
17
+ */
18
+ export const changeAccountSubscriber = (
19
+ getInstance: () => EvmProviderApi
20
+ ): ChangeAccountSubscriberBuilder<string[], EvmProviderApi, UtxoActions> =>
21
+ new ChangeAccountSubscriberBuilder<string[], EvmProviderApi, UtxoActions>()
22
+ .getInstance(getInstance)
23
+ .onSwitchAccount((event, context) => {
24
+ if (!event.payload?.length) {
25
+ context.action('disconnect');
26
+ event.preventDefault();
27
+ }
28
+ })
29
+ .format(async () => getAllUtxoAccounts())
30
+ .addEventListener((instance, callback) => {
31
+ instance.on?.('accountsChanged', callback);
32
+ })
33
+ .removeEventListener((instance, callback) => {
34
+ instance.removeListener?.('accountsChanged', callback);
35
+ });
36
+
37
+ function canEagerConnect() {
38
+ return new ActionBuilder<UtxoActions, 'canEagerConnect'>(
39
+ 'canEagerConnect'
40
+ ).action(async () => {
41
+ const accounts = await getAllUtxoAccounts().catch(() => []);
42
+ return accounts.length > 0;
43
+ });
44
+ }
45
+
46
+ export const utxoBuilders = { changeAccountSubscriber, canEagerConnect };
@@ -0,0 +1,84 @@
1
+ import type { ProviderMetadata } from '@hub3js/core';
2
+
3
+ import { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
4
+ import {
5
+ CAIP_BITCOIN_CHAIN_ID,
6
+ CAIP_BITCOINCASH_CHAIN_ID,
7
+ CAIP_DOGECOIN_CHAIN_ID,
8
+ CAIP_LITECOIN_CHAIN_ID,
9
+ } from '@rango-dev/wallets-core/namespaces/utxo';
10
+ import {
11
+ type BlockchainMeta,
12
+ evmBlockchains,
13
+ solanaBlockchain,
14
+ type TransferBlockchainMeta,
15
+ } from 'rango-types';
16
+
17
+ import getSigners from './signer.js';
18
+ import { getInstanceOrThrow } from './utils.js';
19
+
20
+ export const WALLET_ID = 'ctrl';
21
+
22
+ /**
23
+ * The UTXO chains Ctrl exposes, grouped under the single UTXO namespace, each paired
24
+ * with its CAIP-2 (bip122) reference so accounts can be self-describing.
25
+ */
26
+ export const UTXO_CHAINS = [
27
+ { network: LegacyNetworks.BTC, caip: CAIP_BITCOIN_CHAIN_ID },
28
+ { network: LegacyNetworks.LTC, caip: CAIP_LITECOIN_CHAIN_ID },
29
+ { network: LegacyNetworks.DOGE, caip: CAIP_DOGECOIN_CHAIN_ID },
30
+ { network: LegacyNetworks.BCH, caip: CAIP_BITCOINCASH_CHAIN_ID },
31
+ ] as const;
32
+
33
+ export const SUPPORTED_UTXO_CHAINS: string[] = UTXO_CHAINS.map(
34
+ (chain) => chain.network
35
+ );
36
+
37
+ export const metadata: ProviderMetadata = {
38
+ name: 'Ctrl',
39
+ icon: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/xdefi/icon.svg',
40
+ extensions: {
41
+ chrome:
42
+ 'https://chromewebstore.google.com/detail/ctrl-wallet/hmeobnfnfcmdkdcmlblgagmfpfboieaf',
43
+ brave:
44
+ 'https://chromewebstore.google.com/detail/ctrl-wallet/hmeobnfnfcmdkdcmlblgagmfpfboieaf',
45
+ homepage: 'https://ctrl.xyz/',
46
+ },
47
+ properties: [
48
+ {
49
+ name: 'namespaces',
50
+ value: {
51
+ selection: 'multiple',
52
+ data: [
53
+ {
54
+ label: 'EVM',
55
+ value: 'EVM',
56
+ id: 'ETH',
57
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
58
+ evmBlockchains(allBlockchains),
59
+ },
60
+ {
61
+ label: 'UTXO',
62
+ value: 'UTXO',
63
+ id: 'BTC',
64
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
65
+ allBlockchains.filter((chain): chain is TransferBlockchainMeta =>
66
+ SUPPORTED_UTXO_CHAINS.includes(chain.name)
67
+ ),
68
+ },
69
+ {
70
+ label: 'Solana',
71
+ value: 'Solana',
72
+ id: 'SOLANA',
73
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
74
+ solanaBlockchain(allBlockchains),
75
+ },
76
+ ],
77
+ },
78
+ },
79
+ {
80
+ name: 'signers',
81
+ value: { getSigners: async () => getSigners(getInstanceOrThrow()) },
82
+ },
83
+ ],
84
+ };
package/src/mod.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { defineVersions } from '@hub3js/core/utils';
2
+
3
+ import { buildProvider } from './provider.js';
4
+
5
+ const versions = () =>
6
+ defineVersions().version('1.0.0', buildProvider()).build();
7
+
8
+ export { versions };
@@ -0,0 +1,50 @@
1
+ import type { EvmActions } from '@hub3js/evm';
2
+
3
+ import { NamespaceBuilder } from '@hub3js/core';
4
+ import { actions, builders, hooks } from '@hub3js/evm';
5
+ import * as commonBuilders from '@hub3js/std/builders';
6
+ import { standardizeAndThrowError } from '@hub3js/std/operators';
7
+
8
+ import { WALLET_ID } from '../constants.js';
9
+ import { evmCtrl } from '../utils.js';
10
+
11
+ const [changeAccountSubscriber, changeAccountCleanup] =
12
+ hooks.changeAccountSubscriber(evmCtrl);
13
+
14
+ const connect = builders
15
+ .connect()
16
+ .action(actions.connect(evmCtrl))
17
+ .before(changeAccountSubscriber)
18
+ .or(changeAccountCleanup)
19
+ .or(standardizeAndThrowError)
20
+ .build();
21
+
22
+ const disconnect = commonBuilders
23
+ .disconnect<EvmActions>()
24
+ .after(changeAccountCleanup)
25
+ .build();
26
+
27
+ const canSwitchNetwork = builders
28
+ .canSwitchNetwork()
29
+ .action(actions.canSwitchNetwork())
30
+ .build();
31
+
32
+ const canEagerConnect = builders
33
+ .canEagerConnect()
34
+ .action(actions.canEagerConnect(evmCtrl))
35
+ .build();
36
+
37
+ const getChainId = builders
38
+ .getChainId()
39
+ .action(actions.getChainId(evmCtrl))
40
+ .build();
41
+
42
+ const evm = new NamespaceBuilder<EvmActions>('EVM', WALLET_ID)
43
+ .action(connect)
44
+ .action(disconnect)
45
+ .action(canEagerConnect)
46
+ .action(canSwitchNetwork)
47
+ .action(getChainId)
48
+ .build();
49
+
50
+ export { evm };
@@ -0,0 +1,39 @@
1
+ import type { SolanaActions } from '@hub3js/solana';
2
+
3
+ import { ActionBuilder, NamespaceBuilder } from '@hub3js/core';
4
+ import { actions, builders, hooks } from '@hub3js/solana';
5
+ import * as commonBuilders from '@hub3js/std/builders';
6
+ import { standardizeAndThrowError } from '@hub3js/std/operators';
7
+
8
+ import { WALLET_ID } from '../constants.js';
9
+ import { solanaCtrl } from '../utils.js';
10
+
11
+ const [changeAccountSubscriber, changeAccountCleanup] =
12
+ hooks.changeAccountSubscriber(solanaCtrl);
13
+
14
+ const connect = builders
15
+ .connect()
16
+ .action(actions.connect(solanaCtrl))
17
+ .before(changeAccountSubscriber)
18
+ .or(changeAccountCleanup)
19
+ .or(standardizeAndThrowError)
20
+ .build();
21
+
22
+ const disconnect = commonBuilders
23
+ .disconnect<SolanaActions>()
24
+ .after(changeAccountCleanup)
25
+ .build();
26
+
27
+ const canEagerConnect = new ActionBuilder<SolanaActions, 'canEagerConnect'>(
28
+ 'canEagerConnect'
29
+ )
30
+ .action(actions.canEagerConnect(solanaCtrl))
31
+ .build();
32
+
33
+ const solana = new NamespaceBuilder<SolanaActions>('Solana', WALLET_ID)
34
+ .action(connect)
35
+ .action(disconnect)
36
+ .action(canEagerConnect)
37
+ .build();
38
+
39
+ export { solana };
@@ -0,0 +1,44 @@
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 { utxoBuilders } from '../builders/utxo.js';
10
+ import { WALLET_ID } from '../constants.js';
11
+ import { evmEventSource } from '../utils.js';
12
+
13
+ /*
14
+ * UTXO account changes are driven by the EVM provider's `accountsChanged` (see
15
+ * builders/utxo.ts) because Ctrl's UTXO providers emit no usable payload. One
16
+ * subscriber, listening on the EVM instance, re-fetches all UTXO chains on a switch
17
+ * and disconnects on empty.
18
+ */
19
+ const [changeAccountSubscriber, changeAccountCleanup] = utxoBuilders
20
+ .changeAccountSubscriber(evmEventSource)
21
+ .build();
22
+
23
+ const connect = builders
24
+ .connect()
25
+ .action(utxoActions.connect())
26
+ .before(changeAccountSubscriber)
27
+ .or(changeAccountCleanup)
28
+ .or(standardizeAndThrowError)
29
+ .build();
30
+
31
+ const disconnect = commonBuilders
32
+ .disconnect<UtxoActions>()
33
+ .after(changeAccountCleanup)
34
+ .build();
35
+
36
+ const canEagerConnect = utxoBuilders.canEagerConnect().build();
37
+
38
+ const utxo = new NamespaceBuilder<UtxoActions>('UTXO', WALLET_ID)
39
+ .action(connect)
40
+ .action(disconnect)
41
+ .action(canEagerConnect)
42
+ .build();
43
+
44
+ export { utxo };
@@ -0,0 +1,24 @@
1
+ import { ProviderBuilder } from '@hub3js/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 { utxo } from './namespaces/utxo.js';
7
+ import { ctrl as ctrlInstance } from './utils.js';
8
+
9
+ const buildProvider = () =>
10
+ new ProviderBuilder(WALLET_ID)
11
+ .init(function (context) {
12
+ const [, setState] = context.state();
13
+ if (ctrlInstance()) {
14
+ setState('installed', true);
15
+ console.debug('[ctrl] instance detected.', context);
16
+ }
17
+ })
18
+ .config('metadata', metadata)
19
+ .add('evm', evm)
20
+ .add('utxo', utxo)
21
+ .add('solana', solana)
22
+ .build();
23
+
24
+ export { buildProvider };
package/src/signer.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { Provider } from './types.js';
2
+ import type { ProviderAPI as EvmProviderApi } from '@hub3js/evm';
3
+ import type { SolanaExternalProvider } from '@rango-dev/signer-solana';
4
+ import type { SignerFactory } from 'rango-types';
5
+
6
+ import { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
7
+ import { DefaultSignerFactory, TransactionType as TxType } from 'rango-types';
8
+
9
+ import { CustomSolanaSigner } from './signers/solana.js';
10
+ import { CustomTransferSigner } from './signers/utxo.js';
11
+
12
+ export default async function getSigners(
13
+ provider: Provider
14
+ ): Promise<SignerFactory> {
15
+ const ethProvider = provider.get(LegacyNetworks.ETHEREUM) as EvmProviderApi;
16
+ const solProvider = provider.get(
17
+ LegacyNetworks.SOLANA
18
+ ) as SolanaExternalProvider;
19
+
20
+ const signers = new DefaultSignerFactory();
21
+ const { DefaultEvmSigner } = await import('@rango-dev/signer-evm');
22
+
23
+ signers.registerSigner(TxType.EVM, new DefaultEvmSigner(ethProvider));
24
+ signers.registerSigner(TxType.SOLANA, new CustomSolanaSigner(solProvider));
25
+ // the transfer signer comprises several UTXO chains, so it gets the whole map
26
+ signers.registerSigner(TxType.TRANSFER, new CustomTransferSigner(provider));
27
+ return signers;
28
+ }
@@ -0,0 +1,22 @@
1
+ import type { SolanaExternalProvider } from '@rango-dev/signer-solana';
2
+
3
+ import { DefaultSolanaSigner } from '@rango-dev/signer-solana';
4
+ import base58 from 'bs58';
5
+
6
+ /**
7
+ * Ctrl's Solana provider signs messages via a direct `signMessage(bytes)` call
8
+ * (the standard wallet-adapter shape). `DefaultSolanaSigner` signs through
9
+ * `request({ method: 'signMessage' })`, which Ctrl does not support, so we keep the
10
+ * direct call and inherit everything else (incl. base58 output and `signAndSendTx`).
11
+ */
12
+ export class CustomSolanaSigner extends DefaultSolanaSigner {
13
+ constructor(provider: SolanaExternalProvider) {
14
+ super(provider);
15
+ }
16
+
17
+ async signMessage(msg: string): Promise<string> {
18
+ const encodedMessage = new TextEncoder().encode(msg);
19
+ const { signature } = await this.provider.signMessage(encodedMessage);
20
+ return base58.encode(signature);
21
+ }
22
+ }