@hot-labs/kit 1.4.0 → 1.4.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/build/HotConnector.js +3 -2
- package/build/HotConnector.js.map +1 -1
- package/build/core/address.d.ts +25 -0
- package/build/core/address.js +162 -0
- package/build/core/address.js.map +1 -0
- package/build/core/chains.d.ts +1 -1
- package/build/core/chains.js +1 -1
- package/build/core/chains.js.map +1 -1
- package/build/core/recipient.d.ts +1 -0
- package/build/core/recipient.js +6 -0
- package/build/core/recipient.js.map +1 -1
- package/build/core/token.d.ts +1 -1
- package/build/core/token.js +3 -1
- package/build/core/token.js.map +1 -1
- package/build/cosmos/connector.d.ts +7 -2
- package/build/cosmos/connector.js +42 -11
- package/build/cosmos/connector.js.map +1 -1
- package/build/near/connector.js +14 -6
- package/build/near/connector.js.map +1 -1
- package/build/ton/connector.js +1 -1
- package/build/ton/connector.js.map +1 -1
- package/build/ui/bridge/Bridge.js +8 -9
- package/build/ui/bridge/Bridge.js.map +1 -1
- package/build/ui/bridge/SelectRecipient.js +4 -3
- package/build/ui/bridge/SelectRecipient.js.map +1 -1
- package/build/ui/bridge/SelectToken.js +2 -2
- package/build/ui/bridge/SelectToken.js.map +1 -1
- package/build/ui/connect/WalletPicker.js +29 -26
- package/build/ui/connect/WalletPicker.js.map +1 -1
- package/build/ui/profile/Payment.js +3 -3
- package/build/ui/profile/Payment.js.map +1 -1
- package/build/ui/uikit/button.d.ts +3 -1
- package/build/ui/uikit/button.js +20 -1
- package/build/ui/uikit/button.js.map +1 -1
- package/package.json +1 -1
- package/src/HotConnector.ts +3 -2
- package/src/core/address.ts +155 -0
- package/src/core/chains.ts +1 -1
- package/src/core/recipient.ts +7 -0
- package/src/core/token.ts +2 -1
- package/src/cosmos/connector.ts +44 -11
- package/src/near/connector.ts +12 -5
- package/src/ton/connector.ts +1 -1
- package/src/ui/bridge/Bridge.tsx +11 -13
- package/src/ui/bridge/SelectRecipient.tsx +16 -9
- package/src/ui/bridge/SelectToken.tsx +2 -2
- package/src/ui/connect/WalletPicker.tsx +45 -35
- package/src/ui/profile/Payment.tsx +3 -3
- package/src/ui/uikit/button.tsx +22 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","sourceRoot":"","sources":["../../../src/ui/uikit/button.tsx"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"button.js","sourceRoot":"","sources":["../../../src/ui/uikit/button.tsx"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsC5D,CAAC,CAAC,EAAE,EAAE,CACN,CAAC,CAAC,OAAO;IACT,GAAG,CAAA;;;;;;;;;;;;;;;KAeF;CACJ,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;;;;;;;;;;;;CAYlC,CAAC"}
|
package/package.json
CHANGED
package/src/HotConnector.ts
CHANGED
|
@@ -109,7 +109,6 @@ export class HotConnector {
|
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
this.onConnect((payload) => {
|
|
112
|
-
console.log("onConnect", payload.wallet.type);
|
|
113
112
|
payload.wallet.fetchBalances(Network.Omni);
|
|
114
113
|
this.fetchTokens(payload.wallet);
|
|
115
114
|
});
|
|
@@ -231,7 +230,9 @@ export class HotConnector {
|
|
|
231
230
|
omniBalance(token: OmniToken) {
|
|
232
231
|
const omni = tokens.get(token);
|
|
233
232
|
const omniToken = this.balance(this.priorityWallet, omni);
|
|
234
|
-
|
|
233
|
+
const onchainTokens = this.walletsTokens.filter((t) => t.token.type !== WalletType.OMNI && t.token.originalId === omni.originalId);
|
|
234
|
+
const onchainBalances = onchainTokens.map((t) => Math.max(0, t.token.float(t.balance) - t.token.reserve));
|
|
235
|
+
let onchainToken = Math.max(0, ...onchainBalances);
|
|
235
236
|
onchainToken *= 0.99; // Slippage protection
|
|
236
237
|
|
|
237
238
|
return {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Address } from "@ton/core";
|
|
2
|
+
import { Address as StellarAddress } from "@stellar/stellar-sdk";
|
|
3
|
+
import { base32, base58, hex } from "@scure/base";
|
|
4
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
5
|
+
import * as ethers from "ethers";
|
|
6
|
+
import { chains, Network, WalletType } from "./chains";
|
|
7
|
+
|
|
8
|
+
export const MinAccountIdLen = 2;
|
|
9
|
+
export const MaxAccountIdLen = 64;
|
|
10
|
+
export const ValidAccountRe = /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/;
|
|
11
|
+
export const NEAR_DOMAINS = [".near", ".sweat", ".usn", ".tg"];
|
|
12
|
+
export const NEAR_ADDRESS_HEX_LENGTH = 64;
|
|
13
|
+
export const EVM_DOMAINS = [".eth", ".cb.id"];
|
|
14
|
+
|
|
15
|
+
export const validateAddress = (address: string) => {
|
|
16
|
+
if (isValidNearAddress(address)) return { chainId: Network.Near, isEvm: false };
|
|
17
|
+
if (isValidSolanaAddress(address)) return { chainId: Network.Solana, isEvm: false };
|
|
18
|
+
if (isValidTronAddress(address)) return { chainId: Network.Tron, isEvm: false };
|
|
19
|
+
if (isValidTonAddress(address)) return { chainId: Network.Ton, isEvm: false };
|
|
20
|
+
if (isValidEvmAddress(address)) return { chainId: 1, isEvm: true };
|
|
21
|
+
if (isValidBtcAddress(address)) return { chainId: Network.Btc, isEvm: false };
|
|
22
|
+
if (isValidStellarAddress(address)) return { chainId: Network.Stellar, isEvm: false };
|
|
23
|
+
return { chainId: -1, isEvm: false };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const isBase58 = (address: string) => {
|
|
27
|
+
try {
|
|
28
|
+
base58.decode(address);
|
|
29
|
+
return true;
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const isBase32 = (address: string) => {
|
|
36
|
+
try {
|
|
37
|
+
base32.decode(address);
|
|
38
|
+
return true;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const isHex = (address: string) => {
|
|
45
|
+
try {
|
|
46
|
+
hex.decode(address);
|
|
47
|
+
return true;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const isValidBtcAddress = (address: string) => {
|
|
54
|
+
// Basic validation for Bitcoin addresses (P2PKH, P2SH, Bech32)
|
|
55
|
+
const p2pkhRegex = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
|
56
|
+
const p2shRegex = /^[2mn][a-km-zA-HJ-NP-Z1-9]{25,39}$/;
|
|
57
|
+
const bech32Regex = /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,90}$/;
|
|
58
|
+
|
|
59
|
+
return p2pkhRegex.test(address) || p2shRegex.test(address) || bech32Regex.test(address);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function isValidNearAccountId(accountId: string) {
|
|
63
|
+
return !!accountId && accountId.length >= MinAccountIdLen && accountId.length <= MaxAccountIdLen && accountId.match(ValidAccountRe) != null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const isValidNearAddress = (address: string, { allowWithoutDomain, allowSubdomain } = { allowWithoutDomain: false, allowSubdomain: true }) => {
|
|
67
|
+
if (!isValidNearAccountId(address)) return false;
|
|
68
|
+
|
|
69
|
+
if (address.length === NEAR_ADDRESS_HEX_LENGTH) return true;
|
|
70
|
+
if (allowWithoutDomain && !address.includes(".")) return true;
|
|
71
|
+
|
|
72
|
+
const endsWithValidDomain = NEAR_DOMAINS.some((t) => address.endsWith(t));
|
|
73
|
+
if (!endsWithValidDomain) return false;
|
|
74
|
+
|
|
75
|
+
const parts = address.split(".");
|
|
76
|
+
|
|
77
|
+
const lastPart = `.${parts[parts.length - 1]}`;
|
|
78
|
+
if (!NEAR_DOMAINS.includes(lastPart)) return false;
|
|
79
|
+
|
|
80
|
+
const otherParts = parts.slice(0, -1);
|
|
81
|
+
const hasOtherDomains = otherParts.some((part) => NEAR_DOMAINS.includes(`.${part}`));
|
|
82
|
+
if (hasOtherDomains) return false;
|
|
83
|
+
|
|
84
|
+
if (allowSubdomain) return true;
|
|
85
|
+
return parts.length <= 2;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const isValidTronAddress = (base58Sting: string) => {
|
|
89
|
+
if (base58Sting.length <= 4) return false;
|
|
90
|
+
let address: Uint8Array;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
address = base58.decode(base58Sting);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (base58Sting.length <= 4) return false;
|
|
99
|
+
|
|
100
|
+
const len = address.length;
|
|
101
|
+
const offset = len - 4;
|
|
102
|
+
const checkSum = address.slice(offset);
|
|
103
|
+
address = address.slice(0, offset);
|
|
104
|
+
const hash0 = sha256(address);
|
|
105
|
+
const hash1 = sha256(new Uint8Array(hash0));
|
|
106
|
+
const checkSum1 = hash1.slice(0, 4);
|
|
107
|
+
|
|
108
|
+
if (checkSum[0] === checkSum1[0] && checkSum[1] === checkSum1[1] && checkSum[2] === checkSum1[2] && checkSum[3] === checkSum1[3]) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return false;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const isValidSolanaAddress = (address: string) => {
|
|
116
|
+
if (address.startsWith("0x")) return false;
|
|
117
|
+
if (ethers.isAddress(address) as boolean) return false;
|
|
118
|
+
if (isBase58(address) && [32, 44].includes(address.length)) return true;
|
|
119
|
+
return !!isValidNearAccountId(address) && address.endsWith(".sol");
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const isValidEvmAddress = (address: string) => {
|
|
123
|
+
return EVM_DOMAINS.some((t) => address.endsWith(t)) || ethers.isAddress(address);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const isValidStellarAddress = (address: string) => {
|
|
127
|
+
try {
|
|
128
|
+
new StellarAddress(address);
|
|
129
|
+
return true;
|
|
130
|
+
} catch (e) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const isValidTonAddress = (address: string) => {
|
|
136
|
+
try {
|
|
137
|
+
Address.parse(address);
|
|
138
|
+
return true;
|
|
139
|
+
} catch (e) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const isValidAddress = (chain: number, address: string) => {
|
|
145
|
+
if (chains.get(chain)?.type === WalletType.EVM) return isValidEvmAddress(address);
|
|
146
|
+
if (chains.get(chain)?.type === WalletType.TON) return isValidTonAddress(address);
|
|
147
|
+
|
|
148
|
+
if (chain === Network.Omni) return isValidNearAddress(address, { allowWithoutDomain: true, allowSubdomain: false });
|
|
149
|
+
if (chain === Network.Near) return isValidNearAddress(address);
|
|
150
|
+
if (chain === Network.Solana) return isValidSolanaAddress(address);
|
|
151
|
+
if (chain === Network.Tron) return isValidTronAddress(address);
|
|
152
|
+
if (chain === Network.Btc) return isValidBtcAddress(address);
|
|
153
|
+
if (chain === Network.Stellar) return isValidStellarAddress(address);
|
|
154
|
+
return false;
|
|
155
|
+
};
|
package/src/core/chains.ts
CHANGED
package/src/core/recipient.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { hex, base32, base58 } from "@scure/base";
|
|
|
4
4
|
import { type OmniWallet } from "./OmniWallet";
|
|
5
5
|
import { tonApi } from "../ton/utils";
|
|
6
6
|
import { WalletType } from "./chains";
|
|
7
|
+
import { isValidAddress } from "./address";
|
|
7
8
|
|
|
8
9
|
export class Recipient {
|
|
9
10
|
constructor(readonly type: WalletType, readonly address: string, readonly omniAddress: string) {}
|
|
@@ -13,7 +14,13 @@ export class Recipient {
|
|
|
13
14
|
return new Recipient(wallet.type, wallet.address, wallet.omniAddress);
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
static isValidAddress(type: WalletType, address: string) {
|
|
18
|
+
return isValidAddress(type, address);
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
static async fromAddress(type: WalletType, address: string) {
|
|
22
|
+
if (!isValidAddress(type, address)) throw new Error("Invalid address");
|
|
23
|
+
|
|
17
24
|
if (type === WalletType.TON) {
|
|
18
25
|
const data = await tonApi.accounts.getAccountPublicKey(Address.parse(address));
|
|
19
26
|
return new Recipient(WalletType.TON, address, data.publicKey.toLowerCase());
|
package/src/core/token.ts
CHANGED
|
@@ -114,8 +114,9 @@ export class Token {
|
|
|
114
114
|
return BigInt(formatter.parseAmount(t.toString(), this.decimals));
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
readable(t: number | bigint | string, rate = 1) {
|
|
117
|
+
readable(t: number | bigint | string, rate = 1, min = 0) {
|
|
118
118
|
const n = typeof t === "number" ? t : formatter.formatAmount(t ?? 0, this.decimals);
|
|
119
|
+
if (n * rate < min) return "0";
|
|
119
120
|
return formatter.amount(n * rate);
|
|
120
121
|
}
|
|
121
122
|
}
|
package/src/cosmos/connector.ts
CHANGED
|
@@ -4,12 +4,12 @@ import { StargateClient } from "@cosmjs/stargate";
|
|
|
4
4
|
import { base64, hex } from "@scure/base";
|
|
5
5
|
import { runInAction } from "mobx";
|
|
6
6
|
|
|
7
|
-
import { api } from "../core/api";
|
|
8
|
-
import { chains, WalletType } from "../core/chains";
|
|
9
7
|
import { ConnectorType, OmniConnector, OmniConnectorOption, WC_ICON } from "../core/OmniConnector";
|
|
10
|
-
import {
|
|
8
|
+
import { chains, WalletType } from "../core/chains";
|
|
11
9
|
import { OmniWallet } from "../core/OmniWallet";
|
|
10
|
+
import { api } from "../core/api";
|
|
12
11
|
|
|
12
|
+
import type { HotConnector } from "../HotConnector";
|
|
13
13
|
import { signAndSendTx } from "./helpers";
|
|
14
14
|
import CosmosWallet from "./wallet";
|
|
15
15
|
|
|
@@ -118,13 +118,28 @@ export default class CosmosConnector extends OmniConnector<CosmosWallet> {
|
|
|
118
118
|
publicKey = Buffer.from(account.pubKey, "base64").toString("hex");
|
|
119
119
|
address = account.bech32Address || "";
|
|
120
120
|
} else {
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
const savedAccount = await this.getStorage().catch(() => null);
|
|
122
|
+
if (savedAccount?.address && savedAccount?.publicKey) {
|
|
123
|
+
publicKey = savedAccount.publicKey;
|
|
124
|
+
address = savedAccount.address;
|
|
125
|
+
} else {
|
|
126
|
+
const data = await this.requestWalletConnect({
|
|
127
|
+
deeplink: id ? wallets[id].deeplink : undefined,
|
|
128
|
+
icon: id ? wallets[id].icon : undefined,
|
|
129
|
+
name: id ? wallets[id].name : undefined,
|
|
130
|
+
request: {
|
|
131
|
+
method: "cosmos_getAccounts",
|
|
132
|
+
params: { chainId: "gonka-mainnet" },
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!Array.isArray(data) || data.length === 0) throw new Error("Account not found");
|
|
137
|
+
publicKey = hex.encode(base64.decode(data[0].pubkey));
|
|
138
|
+
address = data[0].address;
|
|
139
|
+
}
|
|
125
140
|
}
|
|
126
141
|
|
|
127
|
-
this.setStorage({ type: "walletconnect", id });
|
|
142
|
+
this.setStorage({ type: "walletconnect", id, address, publicKey });
|
|
128
143
|
const wallet = new CosmosWallet({
|
|
129
144
|
address: address,
|
|
130
145
|
publicKeyHex: publicKey,
|
|
@@ -150,8 +165,8 @@ export default class CosmosConnector extends OmniConnector<CosmosWallet> {
|
|
|
150
165
|
});
|
|
151
166
|
|
|
152
167
|
const protobufTx = TxRaw.encode({
|
|
153
|
-
bodyBytes: signDoc.bodyBytes,
|
|
154
|
-
authInfoBytes: signDoc.authInfoBytes,
|
|
168
|
+
bodyBytes: Object.keys(signed.bodyBytes).length > 0 ? signed.bodyBytes : signDoc.bodyBytes,
|
|
169
|
+
authInfoBytes: Object.keys(signed.authInfoBytes).length > 0 ? signed.authInfoBytes : signDoc.authInfoBytes,
|
|
155
170
|
signatures: [Buffer.from(signature.signature, "base64")],
|
|
156
171
|
}).finish();
|
|
157
172
|
|
|
@@ -183,6 +198,24 @@ export default class CosmosConnector extends OmniConnector<CosmosWallet> {
|
|
|
183
198
|
);
|
|
184
199
|
}
|
|
185
200
|
|
|
201
|
+
async connectGonkaWallet(): Promise<OmniWallet | { qrcode: string; deeplink?: string; task: Promise<OmniWallet> }> {
|
|
202
|
+
const result = await this.connectWalletConnect({
|
|
203
|
+
onConnect: () => this.setupWalletConnect("gonkaWallet"),
|
|
204
|
+
deeplink: wallets["gonkaWallet"].deeplink,
|
|
205
|
+
namespaces: {
|
|
206
|
+
cosmos: {
|
|
207
|
+
methods: ["cosmos_getAccounts", "cosmos_signDirect"],
|
|
208
|
+
events: ["chainChanged", "accountsChanged"],
|
|
209
|
+
chains: this.chains.map((chain) => `cosmos:${chain}`),
|
|
210
|
+
rpcMap: {},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
window.parent.postMessage({ type: "wc_connect", payload: { wc: result.qrcode } }, "*");
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
|
|
186
219
|
async connectKeplr(type: "keplr" | "leap" | "gonkaWallet", extension?: Keplr): Promise<OmniWallet | { qrcode: string; deeplink?: string; task: Promise<OmniWallet> }> {
|
|
187
220
|
if (!extension) {
|
|
188
221
|
return await this.connectWalletConnect({
|
|
@@ -233,7 +266,7 @@ export default class CosmosConnector extends OmniConnector<CosmosWallet> {
|
|
|
233
266
|
}
|
|
234
267
|
|
|
235
268
|
if (id === "gonkaWallet") {
|
|
236
|
-
return await this.
|
|
269
|
+
return await this.connectGonkaWallet();
|
|
237
270
|
}
|
|
238
271
|
|
|
239
272
|
if (id === "keplr") {
|
package/src/near/connector.ts
CHANGED
|
@@ -29,12 +29,15 @@ class Connector extends OmniConnector<NearWallet> {
|
|
|
29
29
|
this.connector.on("wallet:signIn", async ({ wallet, accounts }) => {
|
|
30
30
|
if (accounts.length === 0) return;
|
|
31
31
|
const { accountId, publicKey } = accounts[0];
|
|
32
|
+
if (this.wallets.find((t) => t.address === accountId)) return;
|
|
32
33
|
this.setWallet(new NearWallet(accountId, publicKey, wallet));
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
this.connector.getConnectedWallet().then(async ({ wallet }) => {
|
|
36
37
|
const [account] = await wallet.getAccounts();
|
|
37
|
-
if (account)
|
|
38
|
+
if (!account) return;
|
|
39
|
+
if (this.wallets.find((t) => t.address === account.accountId)) return;
|
|
40
|
+
this.setWallet(new NearWallet(account.accountId, account.publicKey, wallet));
|
|
38
41
|
});
|
|
39
42
|
|
|
40
43
|
this.connector.whenManifestLoaded.then(() => {
|
|
@@ -51,11 +54,15 @@ class Connector extends OmniConnector<NearWallet> {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
async connect(id: string) {
|
|
54
|
-
const
|
|
55
|
-
if (!
|
|
56
|
-
|
|
57
|
+
const instance = await this.connector.connect(id);
|
|
58
|
+
if (!instance) throw new Error("Wallet not found");
|
|
59
|
+
|
|
60
|
+
const [account] = await instance.getAccounts();
|
|
57
61
|
if (!account) throw new Error("No account found");
|
|
58
|
-
|
|
62
|
+
|
|
63
|
+
const wallet = this.wallets.find((t) => t.address === account.accountId);
|
|
64
|
+
if (!wallet) throw new Error("Wallet not found");
|
|
65
|
+
return wallet;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
async disconnect() {
|
package/src/ton/connector.ts
CHANGED
|
@@ -19,7 +19,7 @@ const hotWallet = {
|
|
|
19
19
|
app_name: "hot",
|
|
20
20
|
image: "https://raw.githubusercontent.com/hot-dao/media/main/logo.png",
|
|
21
21
|
about_url: "https://hot-labs.org/",
|
|
22
|
-
universal_url: "https://
|
|
22
|
+
universal_url: "https://app.hot-labs.org/link",
|
|
23
23
|
bridge: [
|
|
24
24
|
{ type: "sse", url: "https://sse-bridge.hot-labs.org" },
|
|
25
25
|
{ type: "js", key: "hotWallet" },
|
package/src/ui/bridge/Bridge.tsx
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import styled from "styled-components";
|
|
3
2
|
import { observer } from "mobx-react-lite";
|
|
3
|
+
import styled from "styled-components";
|
|
4
4
|
import uuid4 from "uuid4";
|
|
5
5
|
|
|
6
|
-
import RefreshIcon from "../icons/refresh";
|
|
7
6
|
import { ArrowRightIcon } from "../icons/arrow-right";
|
|
7
|
+
import ExchangeIcon from "../icons/exchange";
|
|
8
|
+
import RefreshIcon from "../icons/refresh";
|
|
8
9
|
|
|
9
10
|
import { HotConnector } from "../../HotConnector";
|
|
11
|
+
import { chains, WalletType } from "../../core/chains";
|
|
10
12
|
import { BridgeReview } from "../../core/exchange";
|
|
11
13
|
import { OmniWallet } from "../../core/OmniWallet";
|
|
12
|
-
|
|
13
|
-
import { chains, WalletType } from "../../core/chains";
|
|
14
14
|
import { Recipient } from "../../core/recipient";
|
|
15
15
|
import { formatter } from "../../core/utils";
|
|
16
16
|
import { tokens } from "../../core/tokens";
|
|
@@ -20,10 +20,8 @@ import { ActionButton, Button } from "../uikit/button";
|
|
|
20
20
|
import { PLarge, PSmall, PTiny } from "../uikit/text";
|
|
21
21
|
import { Skeleton } from "../uikit/loader";
|
|
22
22
|
import { ImageView } from "../uikit/image";
|
|
23
|
-
import ExchangeIcon from "../icons/exchange";
|
|
24
23
|
|
|
25
24
|
import Popup from "../Popup";
|
|
26
|
-
import { PopupButton } from "../styles";
|
|
27
25
|
import { openSelectRecipient, openSelectSender, openSelectTokenPopup, openWalletPicker } from "../router";
|
|
28
26
|
import DepositQR from "../profile/DepositQR";
|
|
29
27
|
import { TokenIcon } from "./TokenCard";
|
|
@@ -261,11 +259,11 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
|
|
|
261
259
|
<div style={{ width: "100%", height: 400, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
|
|
262
260
|
{/* @ts-expect-error: dotlottie-wc is not typed */}
|
|
263
261
|
<dotlottie-wc key="success" src={animations.success} speed="1" style={{ width: 300, height: 300 }} mode="forward" loop autoplay></dotlottie-wc>
|
|
264
|
-
<p style={{ fontSize: 24, marginTop: -32, fontWeight: "bold" }}>
|
|
262
|
+
<p style={{ fontSize: 24, marginTop: -32, fontWeight: "bold" }}>{title} successful</p>
|
|
265
263
|
</div>
|
|
266
|
-
<
|
|
264
|
+
<ActionButton style={{ marginTop: "auto" }} onClick={() => (cancelReview(), onClose())}>
|
|
267
265
|
Continue
|
|
268
|
-
</
|
|
266
|
+
</ActionButton>
|
|
269
267
|
</Popup>
|
|
270
268
|
);
|
|
271
269
|
}
|
|
@@ -276,7 +274,7 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
|
|
|
276
274
|
<div style={{ width: "100%", height: 400, gap: 8, display: "flex", justifyContent: "center", alignItems: "center", flexDirection: "column" }}>
|
|
277
275
|
{/* @ts-expect-error: dotlottie-wc is not typed */}
|
|
278
276
|
<dotlottie-wc key="error" src={animations.failed} speed="1" style={{ width: 300, height: 300 }} mode="forward" loop autoplay></dotlottie-wc>
|
|
279
|
-
<p style={{ fontSize: 24, marginTop: -32, fontWeight: "bold" }}>
|
|
277
|
+
<p style={{ fontSize: 24, marginTop: -32, fontWeight: "bold" }}>{title} failed</p>
|
|
280
278
|
<p style={{ fontSize: 14 }}>{processing.message}</p>
|
|
281
279
|
</div>
|
|
282
280
|
<ActionButton onClick={() => (cancelReview(), onClose())}>Continue</ActionButton>
|
|
@@ -285,9 +283,9 @@ export const Bridge = observer(({ hot, widget, setup, onClose, onProcess, onSele
|
|
|
285
283
|
}
|
|
286
284
|
|
|
287
285
|
const button = () => {
|
|
288
|
-
if (sender == null) return <
|
|
289
|
-
if (recipient == null) return <
|
|
290
|
-
if (sender !== "qr" && +from.float(hot.balance(sender, from)).toFixed(FIXED) < +amountFrom.toFixed(FIXED)) return <
|
|
286
|
+
if (sender == null) return <ActionButton disabled>Set sender</ActionButton>;
|
|
287
|
+
if (recipient == null) return <ActionButton disabled>Set recipient</ActionButton>;
|
|
288
|
+
if (sender !== "qr" && +from.float(hot.balance(sender, from)).toFixed(FIXED) < +amountFrom.toFixed(FIXED)) return <ActionButton disabled>Insufficient balance</ActionButton>;
|
|
291
289
|
return (
|
|
292
290
|
<ActionButton style={{ width: "100%", marginTop: 40 }} disabled={isReviewing || isError != null} onClick={handleConfirm}>
|
|
293
291
|
{isReviewing ? "Quoting..." : isError != null ? isError : "Confirm"}
|
|
@@ -5,7 +5,7 @@ import { useState } from "react";
|
|
|
5
5
|
import { ArrowRightIcon } from "../icons/arrow-right";
|
|
6
6
|
|
|
7
7
|
import { Recipient } from "../../core/recipient";
|
|
8
|
-
import { WalletType } from "../../core/chains";
|
|
8
|
+
import { chains, WalletType } from "../../core/chains";
|
|
9
9
|
import { formatter } from "../../core/utils";
|
|
10
10
|
|
|
11
11
|
import { PSmall } from "../uikit/text";
|
|
@@ -28,6 +28,8 @@ export const SelectRecipient = observer(({ recipient, hot, type, onSelect, onClo
|
|
|
28
28
|
const connectors = hot.connectors.filter((t) => t.walletTypes.includes(type) && t.type !== ConnectorType.SOCIAL);
|
|
29
29
|
const [customAddress, setCustomAddress] = useState<string>(recipient?.address || "");
|
|
30
30
|
|
|
31
|
+
const isError = !Recipient.isValidAddress(type, customAddress) && customAddress.length > 0;
|
|
32
|
+
|
|
31
33
|
const selectCustom = async () => {
|
|
32
34
|
const recipient = await Recipient.fromAddress(type, customAddress);
|
|
33
35
|
onSelect(recipient);
|
|
@@ -55,14 +57,17 @@ export const SelectRecipient = observer(({ recipient, hot, type, onSelect, onClo
|
|
|
55
57
|
|
|
56
58
|
{type !== WalletType.OMNI && (
|
|
57
59
|
<>
|
|
58
|
-
|
|
59
|
-
<div style={{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
{connectors.length > 0 && (
|
|
61
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8, width: "100%", margin: "12px 0" }}>
|
|
62
|
+
<div style={{ width: "100%", height: 1, background: "rgba(255,255,255,0.1)" }}></div>
|
|
63
|
+
<PSmall>OR</PSmall>
|
|
64
|
+
<div style={{ width: "100%", height: 1, background: "rgba(255,255,255,0.1)" }}></div>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
63
68
|
<div style={{ width: "100%" }}>
|
|
64
69
|
<PSmall style={{ textAlign: "left" }}>Enter recipient address, avoid CEX</PSmall>
|
|
65
|
-
<CustomRecipient>
|
|
70
|
+
<CustomRecipient $error={isError}>
|
|
66
71
|
<input //
|
|
67
72
|
type="text"
|
|
68
73
|
placeholder="Enter wallet address"
|
|
@@ -73,6 +78,8 @@ export const SelectRecipient = observer(({ recipient, hot, type, onSelect, onClo
|
|
|
73
78
|
Select
|
|
74
79
|
</button>
|
|
75
80
|
</CustomRecipient>
|
|
81
|
+
|
|
82
|
+
{isError && <PSmall style={{ marginTop: 4, textAlign: "left", color: "#F34747" }}>Invalid {type === WalletType.EVM ? "EVM" : chains.get(type)?.name} address</PSmall>}
|
|
76
83
|
</div>
|
|
77
84
|
</>
|
|
78
85
|
)}
|
|
@@ -80,10 +87,10 @@ export const SelectRecipient = observer(({ recipient, hot, type, onSelect, onClo
|
|
|
80
87
|
);
|
|
81
88
|
});
|
|
82
89
|
|
|
83
|
-
const CustomRecipient = styled.div
|
|
90
|
+
const CustomRecipient = styled.div<{ $error: boolean }>`
|
|
84
91
|
display: flex;
|
|
85
92
|
align-items: center;
|
|
86
|
-
border: 1px solid #2d2d2d;
|
|
93
|
+
border: 1px solid ${({ $error }) => ($error ? "#F34747" : "#2d2d2d")};
|
|
87
94
|
border-radius: 12px;
|
|
88
95
|
overflow: hidden;
|
|
89
96
|
margin-top: 8px;
|
|
@@ -73,7 +73,7 @@ export const SelectTokenPopup = observer(({ hot, initialChain, onClose, onSelect
|
|
|
73
73
|
<SearchInput type="text" placeholder="Search token" onChange={(e) => setSearch(e.target.value)} />
|
|
74
74
|
|
|
75
75
|
{tokens.list
|
|
76
|
-
.filter((token) => token.chain === chain)
|
|
76
|
+
.filter((token) => token.chain === chain && token.symbol.toLowerCase().includes(search.toLowerCase()))
|
|
77
77
|
.sort((a, b) => {
|
|
78
78
|
const wallet = hot.wallets.find((w) => w.type === a.type)!;
|
|
79
79
|
const aBalance = a.float(hot.balance(wallet, a)) * a.usd;
|
|
@@ -111,7 +111,7 @@ export const SelectTokenPopup = observer(({ hot, initialChain, onClose, onSelect
|
|
|
111
111
|
))}
|
|
112
112
|
|
|
113
113
|
{Object.values(OmniToken)
|
|
114
|
-
.filter((token) => !used.has(token))
|
|
114
|
+
.filter((token) => !used.has(token) && hot.omni(token).symbol.toLowerCase().includes(search.toLowerCase()))
|
|
115
115
|
.map((token) => (
|
|
116
116
|
<TokenCard key={token} token={hot.omni(token)} onSelect={onSelect} hot={hot} wallet={hot.priorityWallet} />
|
|
117
117
|
))}
|
|
@@ -2,9 +2,13 @@ import { useState } from "react";
|
|
|
2
2
|
import { observer } from "mobx-react-lite";
|
|
3
3
|
|
|
4
4
|
import { ImageView } from "../uikit/image";
|
|
5
|
+
import { ActionButton } from "../uikit/button";
|
|
6
|
+
import { H4, PMedium } from "../uikit/text";
|
|
7
|
+
|
|
5
8
|
import { OmniWallet } from "../../core/OmniWallet";
|
|
6
9
|
import { OmniConnector, OmniConnectorOption } from "../../core/OmniConnector";
|
|
7
|
-
import {
|
|
10
|
+
import { PopupOption, PopupOptionInfo } from "../styles";
|
|
11
|
+
|
|
8
12
|
import { WCPopup } from "./WCPopup";
|
|
9
13
|
import Popup from "../Popup";
|
|
10
14
|
|
|
@@ -21,6 +25,31 @@ export const WalletPicker = observer(({ initialConnector, onSelect, onClose }: W
|
|
|
21
25
|
const [error, setError] = useState<string | null>(null);
|
|
22
26
|
const [loading, setLoading] = useState<boolean>(false);
|
|
23
27
|
|
|
28
|
+
const connectWallet = async (connector: OmniConnector, wallet: OmniConnectorOption) => {
|
|
29
|
+
try {
|
|
30
|
+
setLoading(true);
|
|
31
|
+
setError(null);
|
|
32
|
+
setWallet(wallet);
|
|
33
|
+
|
|
34
|
+
const instance = await connector.connect(wallet.id);
|
|
35
|
+
if (typeof instance === "object" && "qrcode" in instance) {
|
|
36
|
+
setQrcode({ uri: instance.qrcode, deeplink: instance.deeplink, icon: connector.icon });
|
|
37
|
+
const wallet = await instance.task;
|
|
38
|
+
onSelect?.(wallet);
|
|
39
|
+
onClose();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onSelect?.(instance);
|
|
44
|
+
onClose();
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.error(e);
|
|
47
|
+
setError(e instanceof Error ? e.message : "Unknown error");
|
|
48
|
+
} finally {
|
|
49
|
+
setLoading(false);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
24
53
|
if (qrcode) {
|
|
25
54
|
return (
|
|
26
55
|
<WCPopup //
|
|
@@ -36,14 +65,21 @@ export const WalletPicker = observer(({ initialConnector, onSelect, onClose }: W
|
|
|
36
65
|
if (wallet != null) {
|
|
37
66
|
return (
|
|
38
67
|
<Popup onClose={onClose}>
|
|
39
|
-
<div style={{ width: "100%",
|
|
40
|
-
<ImageView style={{ marginTop:
|
|
68
|
+
<div style={{ width: "100%", display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center", gap: 0 }}>
|
|
69
|
+
<ImageView style={{ marginTop: 32 }} src={wallet.icon} alt={wallet.name} size={100} />
|
|
41
70
|
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
71
|
+
<H4 style={{ marginTop: 12, textAlign: "center" }}>{wallet.name}</H4>
|
|
72
|
+
<PMedium style={{ textAlign: "center" }}>{error}</PMedium>
|
|
73
|
+
|
|
74
|
+
<ActionButton disabled={loading} style={{ marginTop: 32 }} onClick={() => window.open(wallet.download, "_blank")}>
|
|
45
75
|
{loading ? "Connecting..." : "Get wallet"}
|
|
46
|
-
</
|
|
76
|
+
</ActionButton>
|
|
77
|
+
|
|
78
|
+
{!!error && !loading && (
|
|
79
|
+
<ActionButton $stroke style={{ marginTop: 8 }} onClick={() => connectWallet(connector!, wallet)}>
|
|
80
|
+
Try again
|
|
81
|
+
</ActionButton>
|
|
82
|
+
)}
|
|
47
83
|
</div>
|
|
48
84
|
</Popup>
|
|
49
85
|
);
|
|
@@ -51,35 +87,9 @@ export const WalletPicker = observer(({ initialConnector, onSelect, onClose }: W
|
|
|
51
87
|
|
|
52
88
|
if (connector != null) {
|
|
53
89
|
return (
|
|
54
|
-
<Popup header={<p>Select
|
|
90
|
+
<Popup header={<p>Select {connector.name}</p>} onClose={onClose}>
|
|
55
91
|
{connector.options.map((wallet) => (
|
|
56
|
-
<PopupOption
|
|
57
|
-
key={wallet.id}
|
|
58
|
-
onClick={async () => {
|
|
59
|
-
try {
|
|
60
|
-
setLoading(true);
|
|
61
|
-
setError(null);
|
|
62
|
-
setWallet(wallet);
|
|
63
|
-
|
|
64
|
-
const instance = await connector.connect(wallet.id);
|
|
65
|
-
if (typeof instance === "object" && "qrcode" in instance) {
|
|
66
|
-
setQrcode({ uri: instance.qrcode, deeplink: instance.deeplink, icon: connector.icon });
|
|
67
|
-
const wallet = await instance.task;
|
|
68
|
-
onSelect?.(wallet);
|
|
69
|
-
onClose();
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
onSelect?.(instance);
|
|
74
|
-
onClose();
|
|
75
|
-
} catch (e) {
|
|
76
|
-
console.error(e);
|
|
77
|
-
setError(e instanceof Error ? e.message : "Unknown error");
|
|
78
|
-
} finally {
|
|
79
|
-
setLoading(false);
|
|
80
|
-
}
|
|
81
|
-
}}
|
|
82
|
-
>
|
|
92
|
+
<PopupOption key={wallet.id} onClick={() => connectWallet(connector, wallet)}>
|
|
83
93
|
<ImageView src={wallet.icon} alt={wallet.name} size={44} />
|
|
84
94
|
<PopupOptionInfo className="connect-item-info">
|
|
85
95
|
<p style={{ fontSize: 20, fontWeight: "bold" }}>{wallet.name}</p>
|