@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.
- package/CHANGELOG.md +319 -0
- package/dist/actions/utxo.d.ts +17 -0
- package/dist/actions/utxo.d.ts.map +1 -0
- package/dist/builders/utxo.d.ts +21 -0
- package/dist/builders/utxo.d.ts.map +1 -0
- package/dist/constants.d.ts +23 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/mod.d.ts +3 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +2 -0
- package/dist/mod.js.map +7 -0
- package/dist/namespaces/evm.d.ts +4 -0
- package/dist/namespaces/evm.d.ts.map +1 -0
- package/dist/namespaces/solana.d.ts +4 -0
- package/dist/namespaces/solana.d.ts.map +1 -0
- package/dist/namespaces/utxo.d.ts +4 -0
- package/dist/namespaces/utxo.d.ts.map +1 -0
- package/dist/provider-ctrl.build.json +1 -0
- package/dist/provider.d.ts +3 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/signer.d.ts +4 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signers/solana.d.ts +13 -0
- package/dist/signers/solana.d.ts.map +1 -0
- package/dist/signers/utxo.d.ts +17 -0
- package/dist/signers/utxo.d.ts.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +25 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +37 -0
- package/readme.md +80 -0
- package/src/actions/utxo.ts +32 -0
- package/src/builders/utxo.ts +46 -0
- package/src/constants.ts +84 -0
- package/src/mod.ts +8 -0
- package/src/namespaces/evm.ts +50 -0
- package/src/namespaces/solana.ts +39 -0
- package/src/namespaces/utxo.ts +44 -0
- package/src/provider.ts +24 -0
- package/src/signer.ts +28 -0
- package/src/signers/solana.ts +22 -0
- package/src/signers/utxo.ts +167 -0
- package/src/types.ts +18 -0
- package/src/utils.ts +141 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Provider, ProviderObject } from '../types.js';
|
|
2
|
+
import type { ProviderAPI as UtxoProviderApi } from '@rango-dev/wallets-core/namespaces/utxo';
|
|
3
|
+
import type { GenericSigner, Transfer } from 'rango-types';
|
|
4
|
+
|
|
5
|
+
import { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
|
|
6
|
+
import { SignerError, SignerErrorCode } from 'rango-types';
|
|
7
|
+
|
|
8
|
+
import { SUPPORTED_UTXO_CHAINS } from '../constants.js';
|
|
9
|
+
|
|
10
|
+
interface CtrlTransferParams {
|
|
11
|
+
asset: { chain: string; symbol: string; ticker: string };
|
|
12
|
+
from: string;
|
|
13
|
+
amount: { amount: string; decimals: number };
|
|
14
|
+
memo?: string;
|
|
15
|
+
recipient?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Callback-style transfer for non-PSBT UTXO chains (LTC/DOGE/BCH). */
|
|
19
|
+
async function ctrlTransfer(
|
|
20
|
+
blockchain: string,
|
|
21
|
+
ticker: string,
|
|
22
|
+
from: string,
|
|
23
|
+
amount: string,
|
|
24
|
+
decimals: number,
|
|
25
|
+
recipientAddress: string | null,
|
|
26
|
+
provider: UtxoProviderApi,
|
|
27
|
+
method: string,
|
|
28
|
+
memo?: string
|
|
29
|
+
): Promise<string> {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const params: CtrlTransferParams = {
|
|
32
|
+
asset: { chain: blockchain, symbol: ticker, ticker },
|
|
33
|
+
from,
|
|
34
|
+
amount: { amount, decimals },
|
|
35
|
+
memo,
|
|
36
|
+
};
|
|
37
|
+
if (recipientAddress) {
|
|
38
|
+
params.recipient = recipientAddress;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
provider.request(
|
|
42
|
+
{ method, params: [params] },
|
|
43
|
+
(error: unknown, result: unknown) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
reject(
|
|
46
|
+
new SignerError(SignerErrorCode.SEND_TX_ERROR, undefined, error)
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
resolve(result as string);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* One signer for all of Ctrl's UTXO chains. BTC is signed via PSBT (`sign_psbt`);
|
|
58
|
+
* LTC/DOGE/BCH use the generic `transfer` request. It receives the whole provider
|
|
59
|
+
* map and resolves the right per-chain instance per transaction.
|
|
60
|
+
*/
|
|
61
|
+
export class CustomTransferSigner implements GenericSigner<Transfer> {
|
|
62
|
+
private provider: Provider;
|
|
63
|
+
constructor(provider: Provider) {
|
|
64
|
+
this.provider = provider;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async signMessage(): Promise<string> {
|
|
68
|
+
throw SignerError.UnimplementedError('signMessage');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async signAndSendTx(tx: Transfer): Promise<{ hash: string }> {
|
|
72
|
+
const { blockchain } = tx.asset;
|
|
73
|
+
|
|
74
|
+
if (!SUPPORTED_UTXO_CHAINS.includes(blockchain)) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`blockchain: ${blockchain} transfer not implemented yet.`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (blockchain === LegacyNetworks.BTC) {
|
|
81
|
+
return this.#signPsbt(tx);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return this.#signTransferObject(tx);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// https://developers.ctrl.xyz/developers/extension-bitcoin#sign-psbt-partially-signed-bitcoin-transaction
|
|
88
|
+
async #signPsbt(tx: Transfer): Promise<{ hash: string }> {
|
|
89
|
+
const { asset, psbt } = tx;
|
|
90
|
+
|
|
91
|
+
if (!psbt) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'No PSBT found to sign. Ensure a valid PSBT is provided.'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const provider = this.provider.get(
|
|
98
|
+
asset.blockchain as keyof ProviderObject
|
|
99
|
+
) as UtxoProviderApi;
|
|
100
|
+
|
|
101
|
+
const signInputs: { [key: string]: number[] } = {};
|
|
102
|
+
psbt.inputsToSign.forEach((input) => {
|
|
103
|
+
signInputs[input.address] = input.signingIndexes;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const response = await provider
|
|
107
|
+
.request({
|
|
108
|
+
method: 'sign_psbt',
|
|
109
|
+
/*
|
|
110
|
+
* Ctrl expects `params` as an array (same as its other RPCs); passing a bare
|
|
111
|
+
* object makes the extension read `psbt` off `undefined`. The docs showing a
|
|
112
|
+
* plain object are wrong.
|
|
113
|
+
*/
|
|
114
|
+
params: [
|
|
115
|
+
{
|
|
116
|
+
psbt: psbt.unsignedPsbtBase64,
|
|
117
|
+
signInputs,
|
|
118
|
+
allowedSignHash: 1,
|
|
119
|
+
broadcast: true,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
})
|
|
123
|
+
.catch((error: unknown) => {
|
|
124
|
+
throw new SignerError(SignerErrorCode.SEND_TX_ERROR, undefined, error);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (response.status === 'success') {
|
|
128
|
+
return { hash: response.result.txId };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw new Error(
|
|
132
|
+
'The operation (sign and broadcast) failed on your wallet.',
|
|
133
|
+
{ cause: response }
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async #signTransferObject(tx: Transfer): Promise<{ hash: string }> {
|
|
138
|
+
const { blockchain } = tx.asset;
|
|
139
|
+
|
|
140
|
+
const transferProvider = this.provider.get(
|
|
141
|
+
blockchain as keyof ProviderObject
|
|
142
|
+
) as UtxoProviderApi;
|
|
143
|
+
|
|
144
|
+
const {
|
|
145
|
+
method,
|
|
146
|
+
memo,
|
|
147
|
+
recipientAddress,
|
|
148
|
+
decimals,
|
|
149
|
+
amount,
|
|
150
|
+
fromWalletAddress: from,
|
|
151
|
+
asset,
|
|
152
|
+
} = tx;
|
|
153
|
+
|
|
154
|
+
const hash = await ctrlTransfer(
|
|
155
|
+
blockchain,
|
|
156
|
+
asset.ticker,
|
|
157
|
+
from,
|
|
158
|
+
amount,
|
|
159
|
+
decimals,
|
|
160
|
+
recipientAddress,
|
|
161
|
+
transferProvider,
|
|
162
|
+
method,
|
|
163
|
+
memo ?? undefined
|
|
164
|
+
);
|
|
165
|
+
return { hash };
|
|
166
|
+
}
|
|
167
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
|
|
6
|
+
export type ProviderObject = {
|
|
7
|
+
[LegacyNetworks.ETHEREUM]: EvmProviderApi;
|
|
8
|
+
[LegacyNetworks.SOLANA]: SolanaProviderApi;
|
|
9
|
+
[LegacyNetworks.BTC]: UtxoProviderApi;
|
|
10
|
+
[LegacyNetworks.LTC]: UtxoProviderApi;
|
|
11
|
+
[LegacyNetworks.DOGE]: UtxoProviderApi;
|
|
12
|
+
[LegacyNetworks.BCH]: UtxoProviderApi;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type Provider = Map<
|
|
16
|
+
keyof ProviderObject,
|
|
17
|
+
ProviderObject[keyof ProviderObject]
|
|
18
|
+
>;
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
import type { ProviderAPI as UtxoProviderApi } from '@rango-dev/wallets-core/namespaces/utxo';
|
|
6
|
+
|
|
7
|
+
import { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
|
|
8
|
+
import { utils } from '@rango-dev/wallets-core/namespaces/utxo';
|
|
9
|
+
|
|
10
|
+
import { UTXO_CHAINS } from './constants.js';
|
|
11
|
+
|
|
12
|
+
export function ctrl(): Provider | null {
|
|
13
|
+
const { ctrl } = window;
|
|
14
|
+
|
|
15
|
+
if (!ctrl) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const instances: Provider = new Map();
|
|
20
|
+
|
|
21
|
+
if (ctrl.ethereum) {
|
|
22
|
+
instances.set(LegacyNetworks.ETHEREUM, ctrl.ethereum);
|
|
23
|
+
}
|
|
24
|
+
if (ctrl.bitcoin) {
|
|
25
|
+
instances.set(LegacyNetworks.BTC, ctrl.bitcoin);
|
|
26
|
+
}
|
|
27
|
+
if (ctrl.litecoin) {
|
|
28
|
+
instances.set(LegacyNetworks.LTC, ctrl.litecoin);
|
|
29
|
+
}
|
|
30
|
+
if (ctrl.dogecoin) {
|
|
31
|
+
instances.set(LegacyNetworks.DOGE, ctrl.dogecoin);
|
|
32
|
+
}
|
|
33
|
+
if (ctrl.bitcoincash) {
|
|
34
|
+
instances.set(LegacyNetworks.BCH, ctrl.bitcoincash);
|
|
35
|
+
}
|
|
36
|
+
if (ctrl.solana) {
|
|
37
|
+
instances.set(LegacyNetworks.SOLANA, ctrl.solana);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (instances.size === 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return instances;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getInstanceOrThrow(): Provider {
|
|
48
|
+
const instances = ctrl();
|
|
49
|
+
|
|
50
|
+
if (!instances) {
|
|
51
|
+
throw new Error('Ctrl is not injected. Please check your wallet.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return instances;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function evmCtrl(): EvmProviderApi {
|
|
58
|
+
const instances = ctrl();
|
|
59
|
+
const evmInstance = instances?.get(LegacyNetworks.ETHEREUM);
|
|
60
|
+
|
|
61
|
+
if (!evmInstance) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
'Ctrl not injected or EVM not enabled. Please check your wallet.'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return evmInstance as EvmProviderApi;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function solanaCtrl(): SolanaProviderApi {
|
|
71
|
+
const instances = ctrl();
|
|
72
|
+
const solanaInstance = instances?.get(LegacyNetworks.SOLANA);
|
|
73
|
+
|
|
74
|
+
if (!solanaInstance) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Ctrl not injected or Solana not enabled. Please check your wallet.'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return solanaInstance;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The EVM instance, used as the trigger source for UTXO account changes.
|
|
85
|
+
*
|
|
86
|
+
* Ctrl switches the active account across every chain at once but only signals it
|
|
87
|
+
* reliably through the EVM provider's `accountsChanged` (the UTXO providers emit an
|
|
88
|
+
* empty `{}` payload, and re-fetching them while disconnected opens a wallet popup).
|
|
89
|
+
* Returns an empty object when EVM isn't injected so the subscriber attaches safely.
|
|
90
|
+
*/
|
|
91
|
+
export function evmEventSource(): EvmProviderApi {
|
|
92
|
+
return (ctrl()?.get(LegacyNetworks.ETHEREUM) ?? {}) as EvmProviderApi;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Promisified `request_accounts` for a callback-style ctrl UTXO instance. */
|
|
96
|
+
async function requestUtxoAccounts(
|
|
97
|
+
instance: UtxoProviderApi
|
|
98
|
+
): Promise<string[]> {
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
instance.request(
|
|
101
|
+
{ method: 'request_accounts', params: [] },
|
|
102
|
+
(error: unknown, accounts: unknown) => {
|
|
103
|
+
if (error) {
|
|
104
|
+
reject(error);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
resolve((accounts as string[]) ?? []);
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Fetch and CAIP-format accounts across every available UTXO chain, merged into a
|
|
115
|
+
* single array. Each account encodes its own chain via its CAIP reference, so
|
|
116
|
+
* BTC/LTC/DOGE/BCH addresses coexist in one UTXO namespace. Silent while the wallet
|
|
117
|
+
* is connected (only called on connect / switch, never on disconnect).
|
|
118
|
+
*/
|
|
119
|
+
export async function getAllUtxoAccounts(): Promise<CaipAccount[]> {
|
|
120
|
+
const instances = ctrl();
|
|
121
|
+
if (!instances) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/*
|
|
126
|
+
* `Promise.all` rejects as soon as any chain's `request_accounts` fails, discarding
|
|
127
|
+
* the rest and propagating that error — we don't want to silently return a partial
|
|
128
|
+
* account set if one chain errors.
|
|
129
|
+
*/
|
|
130
|
+
const perChain = await Promise.all(
|
|
131
|
+
UTXO_CHAINS.filter(({ network }) => instances.get(network)).map(
|
|
132
|
+
async ({ network, caip }) => {
|
|
133
|
+
const instance = instances.get(network) as UtxoProviderApi;
|
|
134
|
+
const accounts = await requestUtxoAccounts(instance);
|
|
135
|
+
return utils.formatAccountsToCAIP(accounts, caip);
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return perChain.flat();
|
|
141
|
+
}
|