@sodax/wallet-sdk-react 0.0.1-rc.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/LICENSE +21 -0
- package/README.md +233 -0
- package/dist/index.cjs +1589 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +15970 -0
- package/dist/index.d.ts +15970 -0
- package/dist/index.mjs +1513 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +74 -0
- package/src/SodaxWalletProvider.tsx +55 -0
- package/src/actions/getXChainType.ts +10 -0
- package/src/actions/getXService.ts +25 -0
- package/src/actions/index.ts +2 -0
- package/src/assets/wallets/hana.svg +6 -0
- package/src/assets/wallets/havah.svg +76 -0
- package/src/assets/wallets/keplr.svg +30 -0
- package/src/assets/wallets/metamask.svg +60 -0
- package/src/assets/wallets/phantom.svg +4 -0
- package/src/assets/wallets/sui.svg +20 -0
- package/src/constants/index.ts +1 -0
- package/src/constants/xChains.ts +164 -0
- package/src/core/XConnector.ts +54 -0
- package/src/core/XService.ts +91 -0
- package/src/core/index.ts +2 -0
- package/src/hooks/index.ts +11 -0
- package/src/hooks/useEthereumChainId.ts +36 -0
- package/src/hooks/useEvmSwitchChain.ts +92 -0
- package/src/hooks/useWalletProvider.ts +149 -0
- package/src/hooks/useWalletProviderOptions.ts +51 -0
- package/src/hooks/useXAccount.ts +51 -0
- package/src/hooks/useXAccounts.ts +56 -0
- package/src/hooks/useXBalances.ts +66 -0
- package/src/hooks/useXConnect.ts +119 -0
- package/src/hooks/useXConnection.ts +72 -0
- package/src/hooks/useXConnectors.ts +56 -0
- package/src/hooks/useXDisconnect.ts +65 -0
- package/src/hooks/useXService.ts +8 -0
- package/src/index.ts +18 -0
- package/src/types/index.ts +44 -0
- package/src/useXWagmiStore.tsx +164 -0
- package/src/utils/index.ts +33 -0
- package/src/xchains/evm/EvmXConnector.ts +27 -0
- package/src/xchains/evm/EvmXService.ts +189 -0
- package/src/xchains/evm/index.ts +2 -0
- package/src/xchains/icon/IconHanaXConnector.ts +39 -0
- package/src/xchains/icon/IconXService.ts +115 -0
- package/src/xchains/icon/iconex/index.tsx +46 -0
- package/src/xchains/icon/index.ts +2 -0
- package/src/xchains/injective/InjectiveKelprXConnector.ts +37 -0
- package/src/xchains/injective/InjectiveMetamaskXConnector.ts +40 -0
- package/src/xchains/injective/InjectiveXService.ts +71 -0
- package/src/xchains/injective/index.ts +4 -0
- package/src/xchains/injective/utils.ts +17 -0
- package/src/xchains/solana/SolanaXConnector.ts +26 -0
- package/src/xchains/solana/SolanaXService.ts +50 -0
- package/src/xchains/solana/hooks/useAnchorProvider.tsx +9 -0
- package/src/xchains/solana/index.ts +2 -0
- package/src/xchains/stellar/CustomSorobanServer.ts +93 -0
- package/src/xchains/stellar/StellarWalletsKitXConnector.ts +45 -0
- package/src/xchains/stellar/StellarXService.ts +61 -0
- package/src/xchains/stellar/index.tsx +2 -0
- package/src/xchains/stellar/useStellarXConnectors.ts +30 -0
- package/src/xchains/stellar/utils.ts +49 -0
- package/src/xchains/sui/SuiXConnector.ts +28 -0
- package/src/xchains/sui/SuiXService.ts +56 -0
- package/src/xchains/sui/index.ts +2 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { XService } from '@/core/XService';
|
|
2
|
+
import type { IconService } from 'icon-sdk-js';
|
|
3
|
+
import { IconService as IconServiceConstructor, Builder as IconBuilder, Converter as IconConverter } from 'icon-sdk-js';
|
|
4
|
+
import type { ChainId, XToken } from '@sodax/types';
|
|
5
|
+
import { isNativeToken } from '@/utils';
|
|
6
|
+
|
|
7
|
+
export interface CallData {
|
|
8
|
+
target: string;
|
|
9
|
+
method: string;
|
|
10
|
+
params: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum SupportedChainId {
|
|
14
|
+
MAINNET = 1,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ChainInfo {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly node: string;
|
|
20
|
+
readonly APIEndpoint: string;
|
|
21
|
+
readonly debugAPIEndpoint: string;
|
|
22
|
+
readonly chainId: number;
|
|
23
|
+
readonly tracker: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const CHAIN_INFO: { readonly [chainId: number]: ChainInfo } = {
|
|
27
|
+
[SupportedChainId.MAINNET]: {
|
|
28
|
+
name: 'ICON Mainnet',
|
|
29
|
+
node: 'https://ctz.solidwallet.io',
|
|
30
|
+
APIEndpoint: 'https://ctz.solidwallet.io/api/v3',
|
|
31
|
+
debugAPIEndpoint: 'https://api.icon.community/api/v3d',
|
|
32
|
+
chainId: 1,
|
|
33
|
+
tracker: 'https://tracker.icon.community',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export class IconXService extends XService {
|
|
38
|
+
private static instance: IconXService;
|
|
39
|
+
|
|
40
|
+
public iconService: IconService;
|
|
41
|
+
|
|
42
|
+
private constructor() {
|
|
43
|
+
super('ICON');
|
|
44
|
+
this.iconService = new IconServiceConstructor(
|
|
45
|
+
new IconServiceConstructor.HttpProvider(CHAIN_INFO[SupportedChainId.MAINNET].APIEndpoint),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static getInstance(): IconXService {
|
|
50
|
+
if (!IconXService.instance) {
|
|
51
|
+
IconXService.instance = new IconXService();
|
|
52
|
+
}
|
|
53
|
+
return IconXService.instance;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async getAggregateData(requireSuccess: boolean, calls: CallData[]) {
|
|
57
|
+
const rawTx = new IconBuilder.CallBuilder()
|
|
58
|
+
// muticall address on mainnet
|
|
59
|
+
.to('cxa4aa9185e23558cff990f494c1fd2845f6cbf741')
|
|
60
|
+
.method('tryAggregate')
|
|
61
|
+
.params({ requireSuccess: IconConverter.toHex(requireSuccess ? 1 : 0), calls })
|
|
62
|
+
.build();
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const result = await this.iconService.call(rawTx).execute();
|
|
66
|
+
const aggs = result['returnData'];
|
|
67
|
+
|
|
68
|
+
const data = aggs.map(agg => {
|
|
69
|
+
if (agg['success'] === '0x0') {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return agg['returnData'];
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return data;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error(err);
|
|
78
|
+
return Array(calls.length).fill(null);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getBalances(address: string | undefined, xTokens: XToken[], xChainId: ChainId) {
|
|
83
|
+
if (!address) return {};
|
|
84
|
+
|
|
85
|
+
const balances = {};
|
|
86
|
+
|
|
87
|
+
const nativeXToken = xTokens.find(xToken => isNativeToken(xToken));
|
|
88
|
+
const nonNativeXTokens = xTokens.filter(xToken => !isNativeToken(xToken));
|
|
89
|
+
|
|
90
|
+
if (nativeXToken) {
|
|
91
|
+
const balance = await this.iconService.getBalance(address).execute();
|
|
92
|
+
balances[nativeXToken.address] = BigInt(balance.toFixed());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const cds: CallData[] = nonNativeXTokens.map(token => {
|
|
96
|
+
return {
|
|
97
|
+
target: token.address,
|
|
98
|
+
method: 'balanceOf',
|
|
99
|
+
params: [address],
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const data: string[] = await this.getAggregateData(
|
|
104
|
+
false,
|
|
105
|
+
cds.filter(cd => cd.target.startsWith('cx')),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return nonNativeXTokens.reduce((agg, token, idx) => {
|
|
109
|
+
const balance = data[idx];
|
|
110
|
+
balances[token.address] = BigInt(balance);
|
|
111
|
+
|
|
112
|
+
return agg;
|
|
113
|
+
}, balances);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const ICONEX_RELAY_RESPONSE = 'ICONEX_RELAY_RESPONSE';
|
|
2
|
+
const ICONEX_RELAY_REQUEST = 'ICONEX_RELAY_REQUEST';
|
|
3
|
+
|
|
4
|
+
export enum ICONexRequestEventType {
|
|
5
|
+
REQUEST_HAS_ACCOUNT = 'REQUEST_HAS_ACCOUNT',
|
|
6
|
+
REQUEST_HAS_ADDRESS = 'REQUEST_HAS_ADDRESS',
|
|
7
|
+
REQUEST_ADDRESS = 'REQUEST_ADDRESS',
|
|
8
|
+
REQUEST_JSON = 'REQUEST_JSON',
|
|
9
|
+
REQUEST_SIGNING = 'REQUEST_SIGNING',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export enum ICONexResponseEventType {
|
|
13
|
+
RESPONSE_HAS_ACCOUNT = 'RESPONSE_HAS_ACCOUNT',
|
|
14
|
+
RESPONSE_HAS_ADDRESS = 'RESPONSE_HAS_ADDRESS',
|
|
15
|
+
RESPONSE_ADDRESS = 'RESPONSE_ADDRESS',
|
|
16
|
+
RESPONSE_JSON = 'RESPONSE_JSON',
|
|
17
|
+
RESPONSE_SIGNING = 'RESPONSE_SIGNING',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ICONexRequestEvent {
|
|
21
|
+
type: ICONexRequestEventType;
|
|
22
|
+
payload?: any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ICONexResponseEvent {
|
|
26
|
+
type: ICONexResponseEventType;
|
|
27
|
+
payload?: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ICONexEvent = ICONexRequestEvent | ICONexResponseEvent;
|
|
31
|
+
|
|
32
|
+
export const request = (event: ICONexRequestEvent): Promise<ICONexResponseEvent> => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const handler = evt => {
|
|
35
|
+
window.removeEventListener(ICONEX_RELAY_RESPONSE, handler);
|
|
36
|
+
resolve(evt.detail);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
window.addEventListener(ICONEX_RELAY_RESPONSE, handler);
|
|
40
|
+
window.dispatchEvent(
|
|
41
|
+
new CustomEvent(ICONEX_RELAY_REQUEST, {
|
|
42
|
+
detail: event,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { XAccount } from '@/types';
|
|
2
|
+
|
|
3
|
+
import { XConnector } from '@/core';
|
|
4
|
+
import { Wallet } from '@injectivelabs/wallet-base';
|
|
5
|
+
import { isCosmosWalletInstalled } from '@injectivelabs/wallet-cosmos';
|
|
6
|
+
import { InjectiveXService } from './InjectiveXService';
|
|
7
|
+
|
|
8
|
+
export class InjectiveKelprXConnector extends XConnector {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('INJECTIVE', 'Keplr', 'keplr');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getXService(): InjectiveXService {
|
|
14
|
+
return InjectiveXService.getInstance();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async connect(): Promise<XAccount | undefined> {
|
|
18
|
+
if (!isCosmosWalletInstalled(Wallet.Keplr)) {
|
|
19
|
+
window.open('https://chrome.google.com/webstore/detail/keplr/dmkamcknogkgcdfhhbddcghachkejeap?hl=en', '_blank');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.getXService().walletStrategy.setWallet(Wallet.Keplr);
|
|
24
|
+
const addresses = await this.getXService().walletStrategy.getAddresses();
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
address: addresses?.[0],
|
|
28
|
+
xChainType: this.xChainType,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async disconnect(): Promise<void> {}
|
|
33
|
+
|
|
34
|
+
public get icon() {
|
|
35
|
+
return 'https://raw.githubusercontent.com/balancednetwork/icons/master/wallets/keplr.svg';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { XAccount } from '@/types';
|
|
2
|
+
|
|
3
|
+
import { XConnector } from '@/core';
|
|
4
|
+
import { getInjectiveAddress } from '@injectivelabs/sdk-ts';
|
|
5
|
+
import { isEvmBrowserWallet, Wallet } from '@injectivelabs/wallet-base';
|
|
6
|
+
import { InjectiveXService } from './InjectiveXService';
|
|
7
|
+
|
|
8
|
+
export class InjectiveMetamaskXConnector extends XConnector {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('INJECTIVE', 'MetaMask', 'metamask');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getXService(): InjectiveXService {
|
|
14
|
+
return InjectiveXService.getInstance();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async connect(): Promise<XAccount | undefined> {
|
|
18
|
+
if (!isEvmBrowserWallet(Wallet.Metamask)) {
|
|
19
|
+
window.open('https://chromewebstore.google.com/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en', '_blank');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.getXService().walletStrategy.setWallet(Wallet.Metamask);
|
|
24
|
+
const addresses = await this.getXService().walletStrategy.getAddresses();
|
|
25
|
+
const injectiveAddresses = addresses.map(getInjectiveAddress);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
address: injectiveAddresses?.[0],
|
|
29
|
+
xChainType: this.xChainType,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async disconnect(): Promise<void> {
|
|
34
|
+
await this.getXService().walletStrategy.disconnect();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public get icon() {
|
|
38
|
+
return 'https://raw.githubusercontent.com/balancednetwork/icons/master/wallets/metamask.svg';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { XService } from '@/core/XService';
|
|
2
|
+
import { Network, getNetworkEndpoints } from '@injectivelabs/networks';
|
|
3
|
+
import { ChainGrpcWasmApi, IndexerGrpcAccountPortfolioApi } from '@injectivelabs/sdk-ts';
|
|
4
|
+
import { ChainId as InjectiveChainId, EvmChainId } from '@injectivelabs/ts-types';
|
|
5
|
+
import { EvmWalletStrategy } from '@injectivelabs/wallet-evm';
|
|
6
|
+
import { MsgBroadcaster, BaseWalletStrategy } from '@injectivelabs/wallet-core';
|
|
7
|
+
import { Wallet } from '@injectivelabs/wallet-base';
|
|
8
|
+
// import { CosmosWalletStrategy } from '@injectivelabs/wallet-cosmos';
|
|
9
|
+
import type { XToken } from '@sodax/types';
|
|
10
|
+
import { mainnet } from 'wagmi/chains';
|
|
11
|
+
|
|
12
|
+
export class InjectiveXService extends XService {
|
|
13
|
+
private static instance: InjectiveXService;
|
|
14
|
+
|
|
15
|
+
public walletStrategy: BaseWalletStrategy;
|
|
16
|
+
public indexerGrpcAccountPortfolioApi: IndexerGrpcAccountPortfolioApi;
|
|
17
|
+
public chainGrpcWasmApi: ChainGrpcWasmApi;
|
|
18
|
+
public msgBroadcaster: MsgBroadcaster;
|
|
19
|
+
|
|
20
|
+
private constructor() {
|
|
21
|
+
super('INJECTIVE');
|
|
22
|
+
|
|
23
|
+
const endpoints = getNetworkEndpoints(Network.Mainnet);
|
|
24
|
+
this.walletStrategy = new BaseWalletStrategy({
|
|
25
|
+
chainId: InjectiveChainId.Mainnet,
|
|
26
|
+
strategies: {
|
|
27
|
+
[Wallet.Metamask]: new EvmWalletStrategy({
|
|
28
|
+
chainId: InjectiveChainId.Mainnet,
|
|
29
|
+
wallet: Wallet.Metamask,
|
|
30
|
+
evmOptions: {
|
|
31
|
+
evmChainId: EvmChainId.Mainnet,
|
|
32
|
+
rpcUrl: mainnet.rpcUrls.default.http[0],
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
// [Wallet.Keplr]: new CosmosWalletStrategy({
|
|
36
|
+
// chainId: InjectiveChainId.Mainnet,
|
|
37
|
+
// wallet: Wallet.Keplr,
|
|
38
|
+
// }),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
this.indexerGrpcAccountPortfolioApi = new IndexerGrpcAccountPortfolioApi(endpoints.indexer);
|
|
42
|
+
this.chainGrpcWasmApi = new ChainGrpcWasmApi(endpoints.grpc);
|
|
43
|
+
this.msgBroadcaster = new MsgBroadcaster({
|
|
44
|
+
walletStrategy: this.walletStrategy,
|
|
45
|
+
network: Network.Mainnet,
|
|
46
|
+
endpoints,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static getInstance(): InjectiveXService {
|
|
51
|
+
if (!InjectiveXService.instance) {
|
|
52
|
+
InjectiveXService.instance = new InjectiveXService();
|
|
53
|
+
}
|
|
54
|
+
return InjectiveXService.instance;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getBalance(address: string | undefined, xToken: XToken) {
|
|
58
|
+
if (!address) return 0n;
|
|
59
|
+
|
|
60
|
+
const portfolio = await this.indexerGrpcAccountPortfolioApi.fetchAccountPortfolioBalances(address);
|
|
61
|
+
|
|
62
|
+
const xTokenAddress = xToken.address;
|
|
63
|
+
|
|
64
|
+
const balance = portfolio.bankBalancesList.find(_balance => _balance.denom === xTokenAddress);
|
|
65
|
+
if (balance) {
|
|
66
|
+
return BigInt(balance.amount);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return 0n;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const switchEthereumChain = async chainId => {
|
|
2
|
+
const metamaskProvider = (window as any).ethereum as any;
|
|
3
|
+
|
|
4
|
+
await Promise.race([
|
|
5
|
+
metamaskProvider.request({
|
|
6
|
+
method: 'wallet_switchEthereumChain',
|
|
7
|
+
params: [{ chainId: `0x${chainId}` }],
|
|
8
|
+
}),
|
|
9
|
+
new Promise<void>(resolve =>
|
|
10
|
+
metamaskProvider.on('change', ({ chain }: any) => {
|
|
11
|
+
if (chain?.id === chainId) {
|
|
12
|
+
resolve();
|
|
13
|
+
}
|
|
14
|
+
}),
|
|
15
|
+
),
|
|
16
|
+
]);
|
|
17
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { XAccount } from '@/types';
|
|
2
|
+
|
|
3
|
+
import { XConnector } from '@/core';
|
|
4
|
+
import { SolanaXService } from './SolanaXService';
|
|
5
|
+
|
|
6
|
+
export class SolanaXConnector extends XConnector {
|
|
7
|
+
wallet: any;
|
|
8
|
+
constructor(wallet: any) {
|
|
9
|
+
super('SOLANA', wallet?.adapter.name, wallet?.adapter.name);
|
|
10
|
+
this.wallet = wallet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getXService(): SolanaXService {
|
|
14
|
+
return SolanaXService.getInstance();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async connect(): Promise<XAccount | undefined> {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async disconnect(): Promise<void> {}
|
|
22
|
+
|
|
23
|
+
public get icon() {
|
|
24
|
+
return this.wallet?.adapter.icon;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { XService } from '@/core/XService';
|
|
2
|
+
import { isNativeToken } from '@/utils';
|
|
3
|
+
import type { XToken } from '@sodax/types';
|
|
4
|
+
import { type Connection, PublicKey } from '@solana/web3.js';
|
|
5
|
+
import { getAccount, getAssociatedTokenAddressSync } from '@solana/spl-token';
|
|
6
|
+
import type { AnchorProvider } from '@coral-xyz/anchor';
|
|
7
|
+
import type { WalletContextState } from '@solana/wallet-adapter-react';
|
|
8
|
+
|
|
9
|
+
export class SolanaXService extends XService {
|
|
10
|
+
private static instance: SolanaXService;
|
|
11
|
+
|
|
12
|
+
public connection: Connection | undefined;
|
|
13
|
+
public wallet: WalletContextState | undefined;
|
|
14
|
+
public provider: AnchorProvider | undefined;
|
|
15
|
+
|
|
16
|
+
private constructor() {
|
|
17
|
+
super('SOLANA');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public static getInstance(): SolanaXService {
|
|
21
|
+
if (!SolanaXService.instance) {
|
|
22
|
+
SolanaXService.instance = new SolanaXService();
|
|
23
|
+
}
|
|
24
|
+
return SolanaXService.instance;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async getBalance(address: string | undefined, xToken: XToken): Promise<bigint> {
|
|
28
|
+
if (!address) return BigInt(0);
|
|
29
|
+
|
|
30
|
+
const connection = this.connection;
|
|
31
|
+
if (!connection) {
|
|
32
|
+
throw new Error('Connection is not initialized');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
if (isNativeToken(xToken)) {
|
|
37
|
+
const newBalance = await connection.getBalance(new PublicKey(address));
|
|
38
|
+
return BigInt(newBalance);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const tokenAccountPubkey = getAssociatedTokenAddressSync(new PublicKey(xToken.address), new PublicKey(address));
|
|
42
|
+
const tokenAccount = await getAccount(connection, tokenAccountPubkey);
|
|
43
|
+
return BigInt(tokenAccount.amount);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.log('error', e);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return BigInt(0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
2
|
+
import { type AnchorWallet, useConnection, useWallet } from '@solana/wallet-adapter-react';
|
|
3
|
+
|
|
4
|
+
export function useAnchorProvider() {
|
|
5
|
+
const { connection } = useConnection();
|
|
6
|
+
const wallet = useWallet();
|
|
7
|
+
|
|
8
|
+
return new AnchorProvider(connection, wallet as AnchorWallet, { commitment: 'confirmed' });
|
|
9
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FeeBumpTransaction,
|
|
3
|
+
type Memo,
|
|
4
|
+
type MemoType,
|
|
5
|
+
type Operation,
|
|
6
|
+
SorobanRpc,
|
|
7
|
+
type Transaction,
|
|
8
|
+
} from '@stellar/stellar-sdk';
|
|
9
|
+
|
|
10
|
+
class CustomSorobanServer extends SorobanRpc.Server {
|
|
11
|
+
private readonly customHeaders: Record<string, string>;
|
|
12
|
+
|
|
13
|
+
constructor(serverUrl: string, customHeaders: Record<string, string>) {
|
|
14
|
+
super(serverUrl, {
|
|
15
|
+
allowHttp: true,
|
|
16
|
+
});
|
|
17
|
+
this.customHeaders = customHeaders;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async simulateTransaction(
|
|
21
|
+
tx: Transaction<Memo<MemoType>, Operation[]>,
|
|
22
|
+
): Promise<SorobanRpc.Api.SimulateTransactionResponse> {
|
|
23
|
+
const requestOptions = {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
...this.customHeaders,
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
id: 1,
|
|
31
|
+
jsonrpc: '2.0',
|
|
32
|
+
method: 'simulateTransaction',
|
|
33
|
+
params: {
|
|
34
|
+
transaction: tx.toXDR(),
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const response = await fetch(`${this.serverURL}`, requestOptions);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`HTTP error simulating TX! status: ${response.status}`);
|
|
42
|
+
}
|
|
43
|
+
return response.json().then(json => json.result);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async sendTransaction(tx: Transaction | FeeBumpTransaction): Promise<SorobanRpc.Api.SendTransactionResponse> {
|
|
47
|
+
const requestOptions = {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
...this.customHeaders,
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
id: 1,
|
|
55
|
+
jsonrpc: '2.0',
|
|
56
|
+
method: 'sendTransaction',
|
|
57
|
+
params: {
|
|
58
|
+
transaction: tx.toXDR(),
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const response = await fetch(`${this.serverURL}`, requestOptions);
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(`HTTP error submitting TX! status: ${response.status}`);
|
|
66
|
+
}
|
|
67
|
+
return response.json().then(json => json.result);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getTransaction(hash: string): Promise<SorobanRpc.Api.GetTransactionResponse> {
|
|
71
|
+
const requestOptions = {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
...this.customHeaders,
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
id: 1,
|
|
79
|
+
jsonrpc: '2.0',
|
|
80
|
+
method: 'getTransaction',
|
|
81
|
+
params: { hash },
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const response = await fetch(`${this.serverURL}`, requestOptions);
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`HTTP error getting TX! status: ${response.status}`);
|
|
88
|
+
}
|
|
89
|
+
return response.json().then(json => json.result);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default CustomSorobanServer;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { XAccount } from '@/types';
|
|
2
|
+
|
|
3
|
+
import { XConnector } from '@/core';
|
|
4
|
+
import { StellarXService } from './StellarXService';
|
|
5
|
+
import type { StellarWalletType } from './useStellarXConnectors';
|
|
6
|
+
|
|
7
|
+
export class StellarWalletsKitXConnector extends XConnector {
|
|
8
|
+
_wallet: StellarWalletType;
|
|
9
|
+
|
|
10
|
+
constructor(wallet: StellarWalletType) {
|
|
11
|
+
super('STELLAR', wallet.name, wallet.id);
|
|
12
|
+
this._wallet = wallet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getXService(): StellarXService {
|
|
16
|
+
return StellarXService.getInstance();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async connect(): Promise<XAccount | undefined> {
|
|
20
|
+
const kit = this.getXService().walletsKit;
|
|
21
|
+
|
|
22
|
+
if (!this._wallet) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!this._wallet.isAvailable && this._wallet.url) {
|
|
27
|
+
window.open(this._wallet.url, '_blank');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
kit.setWallet(this._wallet.id);
|
|
32
|
+
const { address } = await kit.getAddress();
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
address: address,
|
|
36
|
+
xChainType: this.xChainType,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async disconnect(): Promise<void> {}
|
|
41
|
+
|
|
42
|
+
public get icon() {
|
|
43
|
+
return this._wallet.icon;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { XService } from '@/core/XService';
|
|
2
|
+
import { FREIGHTER_ID, StellarWalletsKit, WalletNetwork, allowAllModules } from '@creit.tech/stellar-wallets-kit';
|
|
3
|
+
import * as StellarSdk from '@stellar/stellar-sdk';
|
|
4
|
+
import CustomSorobanServer from './CustomSorobanServer';
|
|
5
|
+
import { getTokenBalance } from './utils';
|
|
6
|
+
import type { XToken } from '@sodax/types';
|
|
7
|
+
|
|
8
|
+
export class StellarXService extends XService {
|
|
9
|
+
private static instance: StellarXService;
|
|
10
|
+
|
|
11
|
+
public walletsKit: StellarWalletsKit;
|
|
12
|
+
public server: StellarSdk.Horizon.Server;
|
|
13
|
+
public sorobanServer: CustomSorobanServer;
|
|
14
|
+
|
|
15
|
+
private constructor() {
|
|
16
|
+
super('STELLAR');
|
|
17
|
+
|
|
18
|
+
this.walletsKit = new StellarWalletsKit({
|
|
19
|
+
network: WalletNetwork.PUBLIC,
|
|
20
|
+
selectedWalletId: FREIGHTER_ID,
|
|
21
|
+
modules: allowAllModules(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.server = new StellarSdk.Horizon.Server('https://horizon.stellar.org', { allowHttp: true });
|
|
25
|
+
this.sorobanServer = new CustomSorobanServer('https://rpc.ankr.com/stellar_soroban', {});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static getInstance(): StellarXService {
|
|
29
|
+
if (!StellarXService.instance) {
|
|
30
|
+
StellarXService.instance = new StellarXService();
|
|
31
|
+
}
|
|
32
|
+
return StellarXService.instance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getBalance(address: string | undefined, xToken: XToken): Promise<bigint> {
|
|
36
|
+
if (!address) return BigInt(0);
|
|
37
|
+
|
|
38
|
+
const stellarAccount = await this.server.loadAccount(address);
|
|
39
|
+
|
|
40
|
+
if (xToken.symbol === 'XLM') {
|
|
41
|
+
const xlmBalance = stellarAccount.balances.find(balance => balance.asset_type === 'native');
|
|
42
|
+
if (xlmBalance) {
|
|
43
|
+
return BigInt(xlmBalance.balance.replace('.', ''));
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
try {
|
|
47
|
+
const txBuilder = new StellarSdk.TransactionBuilder(stellarAccount, {
|
|
48
|
+
fee: StellarSdk.BASE_FEE,
|
|
49
|
+
networkPassphrase: StellarSdk.Networks.PUBLIC,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const balance = await getTokenBalance(address, xToken.address, txBuilder, this.sorobanServer);
|
|
53
|
+
return balance;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`Error while fetching token on Stellar: ${xToken.symbol}, Error: ${e}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return BigInt(0);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useXService } from '@/hooks';
|
|
2
|
+
import { type UseQueryResult, useQuery } from '@tanstack/react-query';
|
|
3
|
+
|
|
4
|
+
import { StellarWalletsKitXConnector, type StellarXService } from '.';
|
|
5
|
+
|
|
6
|
+
export type StellarWalletType = {
|
|
7
|
+
icon: string;
|
|
8
|
+
id: string;
|
|
9
|
+
isAvailable: boolean;
|
|
10
|
+
name: string;
|
|
11
|
+
type: string;
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const useStellarXConnectors = (): UseQueryResult<StellarWalletsKitXConnector[] | undefined, Error | null> => {
|
|
16
|
+
const xService = useXService('STELLAR') as StellarXService;
|
|
17
|
+
|
|
18
|
+
return useQuery({
|
|
19
|
+
queryKey: ['stellar-wallets', xService],
|
|
20
|
+
queryFn: async () => {
|
|
21
|
+
if (!xService) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const wallets: StellarWalletType[] = await xService.walletsKit.getSupportedWallets();
|
|
26
|
+
|
|
27
|
+
return wallets.filter(wallet => wallet.isAvailable).map(wallet => new StellarWalletsKitXConnector(wallet));
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
};
|