@b3dotfun/sdk 0.0.40-alpha.4 → 0.0.40-alpha.6
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/dist/cjs/bondkit/bondkitToken.d.ts +36 -1
- package/dist/cjs/bondkit/bondkitToken.js +266 -0
- package/dist/cjs/bondkit/constants.d.ts +4 -0
- package/dist/cjs/bondkit/constants.js +6 -1
- package/dist/cjs/bondkit/index.d.ts +1 -0
- package/dist/cjs/bondkit/index.js +4 -1
- package/dist/cjs/bondkit/swapService.d.ts +43 -0
- package/dist/cjs/bondkit/swapService.js +373 -0
- package/dist/cjs/bondkit/types.d.ts +10 -4
- package/dist/cjs/bondkit/types.js +4 -5
- package/dist/cjs/global-account/react/components/LinkAccount/LinkAccount.js +63 -3
- package/dist/cjs/global-account/react/components/ManageAccount/ManageAccount.js +35 -2
- package/dist/esm/bondkit/bondkitToken.d.ts +36 -1
- package/dist/esm/bondkit/bondkitToken.js +266 -0
- package/dist/esm/bondkit/constants.d.ts +4 -0
- package/dist/esm/bondkit/constants.js +5 -0
- package/dist/esm/bondkit/index.d.ts +1 -0
- package/dist/esm/bondkit/index.js +2 -0
- package/dist/esm/bondkit/swapService.d.ts +43 -0
- package/dist/esm/bondkit/swapService.js +369 -0
- package/dist/esm/bondkit/types.d.ts +10 -4
- package/dist/esm/bondkit/types.js +4 -5
- package/dist/esm/global-account/react/components/LinkAccount/LinkAccount.js +65 -5
- package/dist/esm/global-account/react/components/ManageAccount/ManageAccount.js +35 -2
- package/dist/styles/index.css +1 -1
- package/dist/types/bondkit/bondkitToken.d.ts +36 -1
- package/dist/types/bondkit/constants.d.ts +4 -0
- package/dist/types/bondkit/index.d.ts +1 -0
- package/dist/types/bondkit/swapService.d.ts +43 -0
- package/dist/types/bondkit/types.d.ts +10 -4
- package/package.json +1 -1
- package/src/bondkit/bondkitToken.ts +321 -1
- package/src/bondkit/constants.ts +7 -0
- package/src/bondkit/index.ts +3 -0
- package/src/bondkit/swapService.ts +461 -0
- package/src/bondkit/types.ts +12 -5
- package/src/global-account/react/components/LinkAccount/LinkAccount.tsx +106 -32
- package/src/global-account/react/components/ManageAccount/ManageAccount.tsx +60 -5
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { parseUnits, formatUnits, encodeAbiParameters, parseAbiParameters, getContract, createPublicClient, http, } from "viem";
|
|
2
|
+
import { base } from "viem/chains";
|
|
3
|
+
import { UniversalRouterAddress, QuoterAddress, Permit2Address, BaseMainnetRpcUrl } from "./constants.js";
|
|
4
|
+
// Minimal ABIs needed for swap functionality
|
|
5
|
+
const UNIVERSAL_ROUTER_ABI = [
|
|
6
|
+
{
|
|
7
|
+
inputs: [
|
|
8
|
+
{ name: "commands", type: "bytes" },
|
|
9
|
+
{ name: "inputs", type: "bytes[]" },
|
|
10
|
+
{ name: "deadline", type: "uint256" },
|
|
11
|
+
],
|
|
12
|
+
name: "execute",
|
|
13
|
+
outputs: [],
|
|
14
|
+
stateMutability: "payable",
|
|
15
|
+
type: "function",
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
const QUOTER_ABI = [
|
|
19
|
+
{
|
|
20
|
+
inputs: [
|
|
21
|
+
{
|
|
22
|
+
components: [
|
|
23
|
+
{
|
|
24
|
+
components: [
|
|
25
|
+
{ internalType: "Currency", name: "currency0", type: "address" },
|
|
26
|
+
{ internalType: "Currency", name: "currency1", type: "address" },
|
|
27
|
+
{ internalType: "uint24", name: "fee", type: "uint24" },
|
|
28
|
+
{ internalType: "int24", name: "tickSpacing", type: "int24" },
|
|
29
|
+
{ internalType: "contract IHooks", name: "hooks", type: "address" },
|
|
30
|
+
],
|
|
31
|
+
internalType: "struct PoolKey",
|
|
32
|
+
name: "poolKey",
|
|
33
|
+
type: "tuple",
|
|
34
|
+
},
|
|
35
|
+
{ internalType: "bool", name: "zeroForOne", type: "bool" },
|
|
36
|
+
{ internalType: "uint128", name: "exactAmount", type: "uint128" },
|
|
37
|
+
{ internalType: "bytes", name: "hookData", type: "bytes" },
|
|
38
|
+
],
|
|
39
|
+
internalType: "struct IV4Quoter.QuoteExactSingleParams",
|
|
40
|
+
name: "params",
|
|
41
|
+
type: "tuple",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
name: "quoteExactInputSingle",
|
|
45
|
+
outputs: [
|
|
46
|
+
{ internalType: "uint256", name: "amountOut", type: "uint256" },
|
|
47
|
+
{ internalType: "uint256", name: "gasEstimate", type: "uint256" },
|
|
48
|
+
],
|
|
49
|
+
stateMutability: "nonpayable",
|
|
50
|
+
type: "function",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
const ERC20_ABI = [
|
|
54
|
+
{
|
|
55
|
+
inputs: [
|
|
56
|
+
{ name: "spender", type: "address" },
|
|
57
|
+
{ name: "amount", type: "uint256" },
|
|
58
|
+
],
|
|
59
|
+
name: "approve",
|
|
60
|
+
outputs: [{ name: "", type: "bool" }],
|
|
61
|
+
stateMutability: "nonpayable",
|
|
62
|
+
type: "function",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
inputs: [
|
|
66
|
+
{ name: "owner", type: "address" },
|
|
67
|
+
{ name: "spender", type: "address" },
|
|
68
|
+
],
|
|
69
|
+
name: "allowance",
|
|
70
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
71
|
+
stateMutability: "view",
|
|
72
|
+
type: "function",
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
const PERMIT2_ABI = [
|
|
76
|
+
{
|
|
77
|
+
inputs: [
|
|
78
|
+
{ name: "token", type: "address" },
|
|
79
|
+
{ name: "spender", type: "address" },
|
|
80
|
+
{ name: "amount", type: "uint160" },
|
|
81
|
+
{ name: "expiration", type: "uint48" },
|
|
82
|
+
],
|
|
83
|
+
name: "approve",
|
|
84
|
+
outputs: [],
|
|
85
|
+
stateMutability: "nonpayable",
|
|
86
|
+
type: "function",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
inputs: [
|
|
90
|
+
{ name: "owner", type: "address" },
|
|
91
|
+
{ name: "token", type: "address" },
|
|
92
|
+
{ name: "spender", type: "address" },
|
|
93
|
+
],
|
|
94
|
+
name: "allowance",
|
|
95
|
+
outputs: [
|
|
96
|
+
{ name: "amount", type: "uint160" },
|
|
97
|
+
{ name: "expiration", type: "uint48" },
|
|
98
|
+
{ name: "nonce", type: "uint48" },
|
|
99
|
+
],
|
|
100
|
+
stateMutability: "view",
|
|
101
|
+
type: "function",
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
const TOKEN_V4_CONFIG_ABI = [
|
|
105
|
+
{
|
|
106
|
+
inputs: [],
|
|
107
|
+
name: "v4Hook",
|
|
108
|
+
outputs: [{ internalType: "address", name: "", type: "address" }],
|
|
109
|
+
stateMutability: "view",
|
|
110
|
+
type: "function",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
inputs: [],
|
|
114
|
+
name: "v4PoolFee",
|
|
115
|
+
outputs: [{ internalType: "uint24", name: "", type: "uint24" }],
|
|
116
|
+
stateMutability: "view",
|
|
117
|
+
type: "function",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
inputs: [],
|
|
121
|
+
name: "v4TickSpacing",
|
|
122
|
+
outputs: [{ internalType: "int24", name: "", type: "int24" }],
|
|
123
|
+
stateMutability: "view",
|
|
124
|
+
type: "function",
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
// Command and action constants
|
|
128
|
+
const COMMANDS = {
|
|
129
|
+
V4_SWAP: "0x10",
|
|
130
|
+
};
|
|
131
|
+
const V4_ACTIONS = {
|
|
132
|
+
SWAP_EXACT_IN_SINGLE: 6,
|
|
133
|
+
TAKE_ALL: 15,
|
|
134
|
+
SETTLE_ALL: 12,
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Internal swap service for handling Uniswap V4 swaps between trading token and bondkit token
|
|
138
|
+
*/
|
|
139
|
+
export class BondkitSwapService {
|
|
140
|
+
constructor(bondkitTokenAddress) {
|
|
141
|
+
this.v4Config = null;
|
|
142
|
+
this.configInitialized = false;
|
|
143
|
+
this.bondkitTokenAddress = bondkitTokenAddress;
|
|
144
|
+
this.publicClient = createPublicClient({
|
|
145
|
+
chain: base,
|
|
146
|
+
transport: http(BaseMainnetRpcUrl),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Initialize V4 pool configuration from bondkit token contract
|
|
151
|
+
*/
|
|
152
|
+
async initializeV4Config() {
|
|
153
|
+
if (this.configInitialized) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const tokenContract = getContract({
|
|
158
|
+
address: this.bondkitTokenAddress,
|
|
159
|
+
abi: TOKEN_V4_CONFIG_ABI,
|
|
160
|
+
client: this.publicClient,
|
|
161
|
+
});
|
|
162
|
+
const [hook, fee, tickSpacing] = await Promise.all([
|
|
163
|
+
tokenContract.read.v4Hook(),
|
|
164
|
+
tokenContract.read.v4PoolFee(),
|
|
165
|
+
tokenContract.read.v4TickSpacing(),
|
|
166
|
+
]);
|
|
167
|
+
this.v4Config = {
|
|
168
|
+
hook: hook,
|
|
169
|
+
fee: Number(fee),
|
|
170
|
+
tickSpacing: Number(tickSpacing),
|
|
171
|
+
};
|
|
172
|
+
this.configInitialized = true;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
console.warn("Failed to initialize V4 configuration:", error);
|
|
176
|
+
// Use fallback configuration
|
|
177
|
+
this.v4Config = {
|
|
178
|
+
hook: "0xB36f4A2FB18b745ef8eD31452781a463d2B3f0cC",
|
|
179
|
+
fee: 30000,
|
|
180
|
+
tickSpacing: 60,
|
|
181
|
+
};
|
|
182
|
+
this.configInitialized = true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get V4 pool configuration
|
|
187
|
+
*/
|
|
188
|
+
async getV4Config() {
|
|
189
|
+
await this.initializeV4Config();
|
|
190
|
+
return this.v4Config;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Handle token approvals for swap
|
|
194
|
+
*/
|
|
195
|
+
async handleTokenApprovals(tokenAddress, amountIn, walletClient, deadline) {
|
|
196
|
+
// Skip approvals for ETH
|
|
197
|
+
if (tokenAddress === "0x0000000000000000000000000000000000000000") {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const userAddress = walletClient.account?.address;
|
|
201
|
+
if (!userAddress) {
|
|
202
|
+
throw new Error("No user address found");
|
|
203
|
+
}
|
|
204
|
+
const erc20Contract = getContract({
|
|
205
|
+
address: tokenAddress,
|
|
206
|
+
abi: ERC20_ABI,
|
|
207
|
+
client: walletClient,
|
|
208
|
+
});
|
|
209
|
+
const permit2Contract = getContract({
|
|
210
|
+
address: Permit2Address,
|
|
211
|
+
abi: PERMIT2_ABI,
|
|
212
|
+
client: walletClient,
|
|
213
|
+
});
|
|
214
|
+
// Check ERC20 allowance to Permit2
|
|
215
|
+
const currentAllowance = (await erc20Contract.read.allowance([userAddress, Permit2Address]));
|
|
216
|
+
const requiredAmount = BigInt(amountIn);
|
|
217
|
+
if (currentAllowance < requiredAmount) {
|
|
218
|
+
await erc20Contract.write.approve([Permit2Address, BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")], {
|
|
219
|
+
account: userAddress,
|
|
220
|
+
chain: base,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
// Check Permit2 allowance for Universal Router
|
|
224
|
+
const permit2Allowance = (await permit2Contract.read.allowance([
|
|
225
|
+
userAddress,
|
|
226
|
+
tokenAddress,
|
|
227
|
+
UniversalRouterAddress,
|
|
228
|
+
]));
|
|
229
|
+
const [currentPermit2Amount, expiration] = permit2Allowance;
|
|
230
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
231
|
+
const isExpired = expiration <= currentTime;
|
|
232
|
+
if (currentPermit2Amount < requiredAmount || isExpired) {
|
|
233
|
+
await permit2Contract.write.approve([tokenAddress, UniversalRouterAddress, BigInt("0xffffffffffffffffffffffffffffffffffffff"), Number(deadline)], {
|
|
234
|
+
account: userAddress,
|
|
235
|
+
chain: base,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get swap quote
|
|
241
|
+
*/
|
|
242
|
+
async getSwapQuote(params) {
|
|
243
|
+
try {
|
|
244
|
+
const { tokenIn, tokenOut, amountIn, tokenInDecimals, tokenOutDecimals, slippageTolerance } = params;
|
|
245
|
+
const v4Config = await this.getV4Config();
|
|
246
|
+
const amountInWei = parseUnits(amountIn, tokenInDecimals);
|
|
247
|
+
// Determine token order for pool
|
|
248
|
+
const currency0 = tokenIn.toLowerCase() < tokenOut.toLowerCase() ? tokenIn : tokenOut;
|
|
249
|
+
const currency1 = tokenIn.toLowerCase() < tokenOut.toLowerCase() ? tokenOut : tokenIn;
|
|
250
|
+
const zeroForOne = tokenIn.toLowerCase() === currency0.toLowerCase();
|
|
251
|
+
const poolKey = {
|
|
252
|
+
currency0: currency0,
|
|
253
|
+
currency1: currency1,
|
|
254
|
+
fee: v4Config.fee,
|
|
255
|
+
tickSpacing: v4Config.tickSpacing,
|
|
256
|
+
hooks: v4Config.hook,
|
|
257
|
+
};
|
|
258
|
+
const quoteParams = {
|
|
259
|
+
poolKey,
|
|
260
|
+
zeroForOne,
|
|
261
|
+
exactAmount: BigInt(amountInWei.toString()),
|
|
262
|
+
hookData: "0x",
|
|
263
|
+
};
|
|
264
|
+
const { result } = await this.publicClient.simulateContract({
|
|
265
|
+
address: QuoterAddress,
|
|
266
|
+
abi: QUOTER_ABI,
|
|
267
|
+
functionName: "quoteExactInputSingle",
|
|
268
|
+
args: [quoteParams],
|
|
269
|
+
});
|
|
270
|
+
const [amountOut] = result;
|
|
271
|
+
const amountOutRaw = formatUnits(amountOut, tokenOutDecimals);
|
|
272
|
+
const amountOutFormatted = parseFloat(amountOutRaw).toFixed(Math.min(6, tokenOutDecimals));
|
|
273
|
+
// Calculate minimum amount out with slippage
|
|
274
|
+
const slippageMultiplier = (100 - slippageTolerance) / 100;
|
|
275
|
+
const amountOutMinRaw = parseFloat(amountOutFormatted) * slippageMultiplier;
|
|
276
|
+
const amountOutMin = amountOutMinRaw.toFixed(tokenOutDecimals);
|
|
277
|
+
// Simple execution price calculation
|
|
278
|
+
const rate = parseFloat(amountOutFormatted) / parseFloat(amountIn);
|
|
279
|
+
const executionPrice = `1 = ${rate.toFixed(6)}`;
|
|
280
|
+
return {
|
|
281
|
+
amountOut: amountOutFormatted,
|
|
282
|
+
amountOutMin,
|
|
283
|
+
priceImpact: "0.0", // Simplified
|
|
284
|
+
executionPrice,
|
|
285
|
+
fee: (v4Config.fee / 10000).toString(),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
console.warn("Error getting swap quote:", error);
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Execute swap transaction
|
|
295
|
+
*/
|
|
296
|
+
async executeSwap(params, walletClient) {
|
|
297
|
+
try {
|
|
298
|
+
const { tokenIn, tokenOut, amountIn, tokenInDecimals, tokenOutDecimals, deadline } = params;
|
|
299
|
+
const swapDeadline = deadline || Math.floor(Date.now() / 1000) + 3600;
|
|
300
|
+
if (!walletClient.account) {
|
|
301
|
+
throw new Error("Wallet client must have an account");
|
|
302
|
+
}
|
|
303
|
+
const amountInWei = parseUnits(amountIn, tokenInDecimals);
|
|
304
|
+
// Handle token approvals
|
|
305
|
+
await this.handleTokenApprovals(tokenIn, amountInWei.toString(), walletClient, swapDeadline);
|
|
306
|
+
// Get quote for minimum amount out
|
|
307
|
+
const quote = await this.getSwapQuote(params);
|
|
308
|
+
if (!quote) {
|
|
309
|
+
throw new Error("Unable to get swap quote");
|
|
310
|
+
}
|
|
311
|
+
const amountOutMinimum = parseUnits(quote.amountOutMin, tokenOutDecimals);
|
|
312
|
+
const v4Config = await this.getV4Config();
|
|
313
|
+
// Determine token order
|
|
314
|
+
const currency0 = tokenIn.toLowerCase() < tokenOut.toLowerCase() ? tokenIn : tokenOut;
|
|
315
|
+
const currency1 = tokenIn.toLowerCase() < tokenOut.toLowerCase() ? tokenOut : tokenIn;
|
|
316
|
+
const zeroForOne = tokenIn.toLowerCase() === currency0.toLowerCase();
|
|
317
|
+
const poolKey = [currency0, currency1, v4Config.fee, v4Config.tickSpacing, v4Config.hook];
|
|
318
|
+
// Encode V4 actions
|
|
319
|
+
const actions = [
|
|
320
|
+
{
|
|
321
|
+
type: V4_ACTIONS.SWAP_EXACT_IN_SINGLE,
|
|
322
|
+
params: [poolKey, zeroForOne, amountInWei, amountOutMinimum, "0x"],
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
type: V4_ACTIONS.TAKE_ALL,
|
|
326
|
+
params: [(zeroForOne ? currency1 : currency0), BigInt(0)],
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
type: V4_ACTIONS.SETTLE_ALL,
|
|
330
|
+
params: [(zeroForOne ? currency0 : currency1), amountInWei],
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
// Encode actions
|
|
334
|
+
const actionTypes = actions.map(action => action.type);
|
|
335
|
+
const actionsBytes = ("0x" + actionTypes.map(type => type.toString(16).padStart(2, "0")).join(""));
|
|
336
|
+
const actionParams = actions.map(action => {
|
|
337
|
+
switch (action.type) {
|
|
338
|
+
case V4_ACTIONS.SWAP_EXACT_IN_SINGLE:
|
|
339
|
+
return encodeAbiParameters(parseAbiParameters("((address,address,uint24,int24,address),bool,uint128,uint128,bytes)"), [action.params]);
|
|
340
|
+
case V4_ACTIONS.TAKE_ALL:
|
|
341
|
+
return encodeAbiParameters(parseAbiParameters("address,uint256"), action.params);
|
|
342
|
+
case V4_ACTIONS.SETTLE_ALL:
|
|
343
|
+
return encodeAbiParameters(parseAbiParameters("address,uint256"), action.params);
|
|
344
|
+
default:
|
|
345
|
+
return "0x00";
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
const v4SwapInput = encodeAbiParameters(parseAbiParameters("bytes,bytes[]"), [actionsBytes, actionParams]);
|
|
349
|
+
const commands = COMMANDS.V4_SWAP;
|
|
350
|
+
const inputs = [v4SwapInput];
|
|
351
|
+
// Execute swap
|
|
352
|
+
const universalRouter = getContract({
|
|
353
|
+
address: UniversalRouterAddress,
|
|
354
|
+
abi: UNIVERSAL_ROUTER_ABI,
|
|
355
|
+
client: walletClient,
|
|
356
|
+
});
|
|
357
|
+
const txHash = await universalRouter.write.execute([commands, inputs, BigInt(swapDeadline)], {
|
|
358
|
+
account: walletClient.account,
|
|
359
|
+
chain: base,
|
|
360
|
+
value: tokenIn === "0x0000000000000000000000000000000000000000" ? amountInWei : BigInt(0),
|
|
361
|
+
});
|
|
362
|
+
return txHash;
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
console.warn("Error executing swap:", error);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -49,10 +49,9 @@ export type DexMigrationEventArgs = {
|
|
|
49
49
|
ethForFeeRecipient: bigint;
|
|
50
50
|
};
|
|
51
51
|
export declare enum TokenStatus {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Migrated = 3
|
|
52
|
+
Uninitialized = 0,
|
|
53
|
+
Bonding = 1,
|
|
54
|
+
Dex = 2
|
|
56
55
|
}
|
|
57
56
|
export interface GetTransactionHistoryOptions {
|
|
58
57
|
userAddress?: Address;
|
|
@@ -80,3 +79,10 @@ export interface TransactionResponse {
|
|
|
80
79
|
skip: number;
|
|
81
80
|
data: Transaction[];
|
|
82
81
|
}
|
|
82
|
+
export interface SwapQuote {
|
|
83
|
+
amountOut: string;
|
|
84
|
+
amountOutMin: string;
|
|
85
|
+
priceImpact: string;
|
|
86
|
+
executionPrice: string;
|
|
87
|
+
fee: string;
|
|
88
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
// Enum for Status (
|
|
1
|
+
// Enum for Status (matches contract Status enum exactly)
|
|
2
2
|
export var TokenStatus;
|
|
3
3
|
(function (TokenStatus) {
|
|
4
|
-
TokenStatus[TokenStatus["
|
|
5
|
-
TokenStatus[TokenStatus["
|
|
6
|
-
TokenStatus[TokenStatus["
|
|
7
|
-
TokenStatus[TokenStatus["Migrated"] = 3] = "Migrated";
|
|
4
|
+
TokenStatus[TokenStatus["Uninitialized"] = 0] = "Uninitialized";
|
|
5
|
+
TokenStatus[TokenStatus["Bonding"] = 1] = "Bonding";
|
|
6
|
+
TokenStatus[TokenStatus["Dex"] = 2] = "Dex";
|
|
8
7
|
})(TokenStatus || (TokenStatus = {}));
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import app from "../../../../global-account/app.js";
|
|
3
3
|
import { ecosystemWalletId } from "../../../../shared/constants/index.js";
|
|
4
|
+
import { thirdwebB3Mainnet } from "../../../../shared/constants/chains/b3Chain.js";
|
|
4
5
|
import { client } from "../../../../shared/utils/thirdweb.js";
|
|
5
|
-
import { Loader2, Mail, Phone } from "lucide-react";
|
|
6
|
+
import { Loader2, Mail, Phone, WalletIcon } from "lucide-react";
|
|
6
7
|
import { useCallback, useEffect, useState } from "react";
|
|
7
8
|
import { toast } from "sonner";
|
|
8
9
|
import { useLinkProfile, useProfiles } from "thirdweb/react";
|
|
9
|
-
import { preAuthenticate } from "thirdweb/wallets";
|
|
10
|
+
import { createWallet, preAuthenticate } from "thirdweb/wallets";
|
|
11
|
+
import { WalletRow } from "../../index.js";
|
|
10
12
|
import { useModalStore } from "../../stores/useModalStore.js";
|
|
11
13
|
import { getProfileDisplayInfo } from "../../utils/profileDisplay.js";
|
|
12
14
|
import { useB3 } from "../B3Provider/useB3.js";
|
|
@@ -30,6 +32,38 @@ const AUTH_METHODS = [
|
|
|
30
32
|
icon: _jsx(FarcasterIcon, { className: "size-6" }),
|
|
31
33
|
},
|
|
32
34
|
];
|
|
35
|
+
const WALLET_METHODS = [
|
|
36
|
+
{
|
|
37
|
+
id: "wallet",
|
|
38
|
+
label: "Wallet",
|
|
39
|
+
enabled: true,
|
|
40
|
+
icon: _jsx(WalletIcon, { className: "size-6" }),
|
|
41
|
+
walletType: "com.coinbase.wallet",
|
|
42
|
+
},
|
|
43
|
+
{ id: "wallet", label: "Wallet", enabled: true, icon: _jsx(WalletIcon, { className: "size-6" }), walletType: "io.metamask" },
|
|
44
|
+
{
|
|
45
|
+
id: "wallet",
|
|
46
|
+
label: "Wallet",
|
|
47
|
+
enabled: true,
|
|
48
|
+
icon: _jsx(WalletIcon, { className: "size-6" }),
|
|
49
|
+
walletType: "me.rainbow",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "wallet",
|
|
53
|
+
label: "Wallet",
|
|
54
|
+
enabled: true,
|
|
55
|
+
icon: _jsx(WalletIcon, { className: "size-6" }),
|
|
56
|
+
walletType: "app.phantom",
|
|
57
|
+
},
|
|
58
|
+
{ id: "wallet", label: "Wallet", enabled: true, icon: _jsx(WalletIcon, { className: "size-6" }), walletType: "io.rabby" },
|
|
59
|
+
{
|
|
60
|
+
id: "wallet",
|
|
61
|
+
label: "Wallet",
|
|
62
|
+
enabled: true,
|
|
63
|
+
icon: _jsx(WalletIcon, { className: "size-6" }),
|
|
64
|
+
walletType: "walletConnect",
|
|
65
|
+
},
|
|
66
|
+
];
|
|
33
67
|
export function LinkAccount({ onSuccess: onSuccessCallback, onError, onClose, chain, partnerId, className, }) {
|
|
34
68
|
const { isLinking, linkingMethod, setLinkingState, navigateBack, setB3ModalContentType } = useModalStore();
|
|
35
69
|
const [selectedMethod, setSelectedMethod] = useState(null);
|
|
@@ -41,12 +75,12 @@ export function LinkAccount({ onSuccess: onSuccessCallback, onError, onClose, ch
|
|
|
41
75
|
const { data: profilesRaw = [] } = useProfiles({ client });
|
|
42
76
|
// Get connected auth methods
|
|
43
77
|
const connectedAuthMethods = profilesRaw
|
|
44
|
-
.filter((profile) => !["custom_auth_endpoint"
|
|
78
|
+
.filter((profile) => !["custom_auth_endpoint"].includes(profile.type))
|
|
45
79
|
.map((profile) => profile.type);
|
|
46
80
|
// Filter available auth methods
|
|
47
81
|
const availableAuthMethods = AUTH_METHODS.filter(method => !connectedAuthMethods.includes(method.id) && method.enabled);
|
|
48
82
|
const profiles = profilesRaw
|
|
49
|
-
.filter((profile) => !["custom_auth_endpoint"
|
|
83
|
+
.filter((profile) => !["custom_auth_endpoint"].includes(profile.type))
|
|
50
84
|
.map((profile) => ({
|
|
51
85
|
...getProfileDisplayInfo(profile),
|
|
52
86
|
originalProfile: profile,
|
|
@@ -176,6 +210,26 @@ export function LinkAccount({ onSuccess: onSuccessCallback, onError, onClose, ch
|
|
|
176
210
|
onError?.(error);
|
|
177
211
|
}
|
|
178
212
|
};
|
|
213
|
+
const handleLinkWallet = async (walletType) => {
|
|
214
|
+
setLinkingState(true, "wallet");
|
|
215
|
+
console.log("selectedMethod", walletType);
|
|
216
|
+
try {
|
|
217
|
+
if (!walletType) {
|
|
218
|
+
throw new Error("Wallet type not found");
|
|
219
|
+
}
|
|
220
|
+
await linkProfile({
|
|
221
|
+
client,
|
|
222
|
+
strategy: "wallet",
|
|
223
|
+
wallet: createWallet(walletType),
|
|
224
|
+
chain: thirdwebB3Mainnet,
|
|
225
|
+
}, mutationOptions);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.error("Error linking account:", error);
|
|
229
|
+
setError(error instanceof Error ? error.message : "Failed to link account");
|
|
230
|
+
onError?.(error);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
179
233
|
const handleSocialLink = async (strategy) => {
|
|
180
234
|
try {
|
|
181
235
|
console.log("handleSocialLink", strategy);
|
|
@@ -256,5 +310,11 @@ export function LinkAccount({ onSuccess: onSuccessCallback, onError, onClose, ch
|
|
|
256
310
|
else {
|
|
257
311
|
handleSocialLink(method.id);
|
|
258
312
|
}
|
|
259
|
-
}, disabled: linkingMethod === method.id, children: isLinking && linkingMethod === method.id ? (_jsx(Loader2, { className: "h-5 w-5 animate-spin" })) : (_jsxs("div", { className: "b3-link-account-method-content flex items-center gap-4", children: [_jsx("div", { className: "b3-link-account-method-icon flex items-center justify-center rounded-full", children: method.icon }), _jsx("span", { className: "b3-link-account-method-label font-medium", children: method.label })] })) }, method.id))),
|
|
313
|
+
}, disabled: linkingMethod === method.id, children: isLinking && linkingMethod === method.id ? (_jsx(Loader2, { className: "h-5 w-5 animate-spin" })) : (_jsxs("div", { className: "b3-link-account-method-content flex items-center gap-4", children: [_jsx("div", { className: "b3-link-account-method-icon flex items-center justify-center rounded-full", children: method.icon }), _jsx("span", { className: "b3-link-account-method-label font-medium", children: method.label })] })) }, method.id))), WALLET_METHODS.map(method => {
|
|
314
|
+
if (!method.walletType) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
return (_jsx(WalletRow, { walletId: method.walletType, onClick: () => handleLinkWallet(method.walletType), isLoading: isLinking }, method.walletType));
|
|
318
|
+
}), availableAuthMethods.length === 0 && (_jsx("div", { className: "text-b3-foreground-muted py-8 text-center", children: "All available authentication methods have been connected" }))] })) : (_jsxs("div", { className: "b3-link-account-form space-y-4", children: [selectedMethod === "email" && (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-b3-grey font-neue-montreal-medium text-sm", children: "Email Address" }), _jsx("input", { type: "email", placeholder: "Enter your email", className: "bg-b3-line text-b3-grey font-neue-montreal-medium focus:ring-b3-primary-blue/20 w-full rounded-xl p-4 focus:outline-none focus:ring-2", value: email, onChange: e => setEmail(e.target.value), disabled: otpSent || (isLinking && linkingMethod === "email") })] })), selectedMethod === "phone" && (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-b3-grey font-neue-montreal-medium text-sm", children: "Phone Number" }), _jsx("input", { type: "tel", placeholder: "Enter your phone number", className: "bg-b3-line text-b3-grey font-neue-montreal-medium focus:ring-b3-primary-blue/20 w-full rounded-xl p-4 focus:outline-none focus:ring-2", value: phone, onChange: e => setPhone(e.target.value), disabled: otpSent || (isLinking && linkingMethod === "phone") }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Include country code (e.g., +1 for US)" })] })), error && _jsx("div", { className: "text-b3-negative font-neue-montreal-medium py-2 text-sm", children: error }), (selectedMethod === "email" || selectedMethod === "phone") &&
|
|
319
|
+
(otpSent ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-b3-grey font-neue-montreal-medium text-sm", children: "Verification Code" }), _jsx("input", { type: "text", placeholder: "Enter verification code", className: "bg-b3-line text-b3-grey font-neue-montreal-medium focus:ring-b3-primary-blue/20 w-full rounded-xl p-4 focus:outline-none focus:ring-2", value: otp, onChange: e => setOtp(e.target.value) })] }), _jsx(Button, { className: "bg-b3-primary-blue hover:bg-b3-primary-blue/90 font-neue-montreal-semibold h-12 w-full text-white", onClick: handleLinkAccount, children: "Link Account" })] })) : (_jsx(Button, { className: "bg-b3-primary-blue hover:bg-b3-primary-blue/90 font-neue-montreal-semibold h-12 w-full text-white", onClick: handleSendOTP, disabled: (!email && !phone) || (isLinking && linkingMethod === selectedMethod), children: isLinking && linkingMethod === selectedMethod ? (_jsx(Loader2, { className: "animate-spin" })) : ("Send Verification Code") })))] }))] }));
|
|
260
320
|
}
|
|
@@ -4,6 +4,7 @@ import { Button, TabsContentPrimitive, TabsListPrimitive, TabsPrimitive, TabTrig
|
|
|
4
4
|
import { SignOutIcon } from "../../../../global-account/react/components/icons/SignOutIcon.js";
|
|
5
5
|
import { formatNumber } from "../../../../shared/utils/formatNumber.js";
|
|
6
6
|
import { client } from "../../../../shared/utils/thirdweb.js";
|
|
7
|
+
import { truncateAddress } from "../../../../shared/utils/truncateAddress.js";
|
|
7
8
|
import { BarChart3, Coins, Copy, Image, LinkIcon, Loader2, Pencil, Settings, UnlinkIcon } from "lucide-react";
|
|
8
9
|
import { useRef, useState } from "react";
|
|
9
10
|
import { toast } from "sonner";
|
|
@@ -12,6 +13,21 @@ import { formatUnits } from "viem";
|
|
|
12
13
|
import { getProfileDisplayInfo } from "../../utils/profileDisplay.js";
|
|
13
14
|
import { AccountAssets } from "../AccountAssets/AccountAssets.js";
|
|
14
15
|
import { ContentTokens } from "./ContentTokens.js";
|
|
16
|
+
// Helper function to check if a string is a wallet address and format it
|
|
17
|
+
const formatProfileTitle = (title) => {
|
|
18
|
+
// Check if title looks like an Ethereum address (0x followed by 40 hex characters)
|
|
19
|
+
const isEthereumAddress = /^0x[a-fA-F0-9]{40}$/.test(title);
|
|
20
|
+
if (isEthereumAddress) {
|
|
21
|
+
return {
|
|
22
|
+
displayTitle: truncateAddress(title),
|
|
23
|
+
isAddress: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
displayTitle: title,
|
|
28
|
+
isAddress: false,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
15
31
|
import { BalanceContent } from "./BalanceContent.js";
|
|
16
32
|
export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit, chain, partnerId, showSwap, showDeposit, }) {
|
|
17
33
|
const [revokingSignerId, setRevokingSignerId] = useState(null);
|
|
@@ -111,7 +127,7 @@ export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit
|
|
|
111
127
|
}
|
|
112
128
|
};
|
|
113
129
|
const profiles = profilesRaw
|
|
114
|
-
.filter((profile) => !["custom_auth_endpoint"
|
|
130
|
+
.filter((profile) => !["custom_auth_endpoint"].includes(profile.type))
|
|
115
131
|
.map((profile) => ({
|
|
116
132
|
...getProfileDisplayInfo(profile),
|
|
117
133
|
originalProfile: profile,
|
|
@@ -146,7 +162,24 @@ export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit
|
|
|
146
162
|
},
|
|
147
163
|
});
|
|
148
164
|
};
|
|
149
|
-
|
|
165
|
+
console.log("@@profiles", profiles);
|
|
166
|
+
return (_jsxs("div", { className: "linked-accounts-settings space-y-8", children: [_jsxs("div", { className: "linked-accounts-section space-y-4", children: [_jsxs("div", { className: "linked-accounts-header flex items-center justify-between", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold linked-accounts-settings-title text-xl", children: "Linked Accounts" }), _jsxs(Button, { className: "linked-accounts-settings-button linked-accounts-link-button bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex items-center gap-2 rounded-full px-4 py-2", onClick: handleOpenLinkModal, disabled: isLinking, children: [isLinking ? (_jsx(Loader2, { className: "linked-accounts-link-loading text-b3-primary-blue animate-spin", size: 16 })) : (_jsx(LinkIcon, { size: 16, className: "linked-accounts-link-icon text-b3-primary-blue" })), _jsx("span", { className: "linked-accounts-link-text text-b3-grey font-neue-montreal-semibold", children: isLinking ? "Linking..." : "Link New Account" })] })] }), isLoadingProfiles ? (_jsx("div", { className: "linked-accounts-loading flex justify-center py-8", children: _jsx(Loader2, { className: "text-b3-grey animate-spin" }) })) : profiles.length > 0 ? (_jsx("div", { className: "linked-accounts-list space-y-4", children: profiles.map(profile => (_jsxs("div", { className: "linked-account-item bg-b3-line group flex items-center justify-between rounded-xl p-4", children: [_jsxs("div", { className: "linked-account-info flex items-center gap-3", children: [profile.imageUrl ? (_jsx("img", { src: profile.imageUrl, alt: profile.title, className: "linked-account-avatar linked-account-avatar-image size-10 rounded-full" })) : (_jsx("div", { className: "linked-account-avatar linked-account-avatar-placeholder bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("span", { className: "linked-account-initial text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), _jsxs("div", { className: "linked-account-details", children: [_jsxs("div", { className: "linked-account-title-row flex items-center gap-2", children: [(() => {
|
|
167
|
+
const { displayTitle, isAddress } = formatProfileTitle(profile.title);
|
|
168
|
+
const handleCopyAddress = async (e) => {
|
|
169
|
+
e.stopPropagation();
|
|
170
|
+
try {
|
|
171
|
+
await navigator.clipboard.writeText(profile.title);
|
|
172
|
+
toast.success("Address copied to clipboard!");
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
toast.error("Failed to copy address");
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
return (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: `linked-account-title text-b3-grey font-neue-montreal-semibold ${isAddress
|
|
179
|
+
? "font-mono text-sm" // Use monospace font for addresses
|
|
180
|
+
: "break-words" // Use break-words for emails/names (better than break-all)
|
|
181
|
+
}`, title: isAddress ? profile.title : undefined, children: displayTitle }), isAddress && (_jsx("button", { onClick: handleCopyAddress, className: "linked-account-copy-button ml-1 rounded p-1 opacity-0 transition-opacity hover:bg-gray-100 group-hover:opacity-100", title: "Copy full address", children: _jsx(Copy, { size: 12, className: "text-gray-500 hover:text-gray-700" }) }))] }));
|
|
182
|
+
})(), _jsx("span", { className: "linked-account-type text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs", children: profile.type.toUpperCase() })] }), _jsx("div", { className: "linked-account-subtitle text-b3-foreground-muted font-neue-montreal-medium text-sm", children: profile.subtitle })] })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "linked-account-unlink-button text-b3-grey hover:text-b3-negative", onClick: () => handleUnlink(profile), disabled: unlinkingAccountId === profile.title || isUnlinking, children: unlinkingAccountId === profile.title || isUnlinking ? (_jsx(Loader2, { className: "linked-account-unlink-loading animate-spin" })) : (_jsx(UnlinkIcon, { size: 16, className: "linked-account-unlink-icon" })) })] }, profile.title))) })) : (_jsx("div", { className: "linked-accounts-empty text-b3-foreground-muted py-8 text-center", children: "No linked accounts found" }))] }), showReferralInfo && (
|
|
150
183
|
/* Referral Section */
|
|
151
184
|
_jsxs("div", { className: "referrals-section space-y-4", children: [_jsx("h3", { className: "referrals-title text-b3-grey font-neue-montreal-semibold text-xl", children: "Referrals" }), _jsxs("div", { className: "referral-code-container bg-b3-line rounded-xl p-4", children: [isEditingCode && (_jsxs("div", { className: "referral-code-header-editing", children: [_jsx("div", { className: "referral-code-title text-b3-grey font-neue-montreal-semibold", children: "Your Referral Code" }), _jsx("div", { className: "referral-code-description text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Share this code with friends to earn rewards" })] })), _jsxs("div", { className: "referral-code-content flex items-center justify-between", children: [!isEditingCode && (_jsxs("div", { className: "referral-code-header", children: [_jsx("div", { className: "referral-code-title text-b3-grey font-neue-montreal-semibold", children: "Your Referral Code" }), _jsx("div", { className: "referral-code-description text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Share this code with friends to earn rewards" })] })), _jsx("div", { className: "referral-code-actions flex items-center gap-2", children: isEditingCode ? (_jsxs("div", { className: "referral-code-edit-form flex items-center gap-2", children: [_jsx("input", { type: "text", value: newReferralCode, onChange: e => setNewReferralCode(e.target.value), className: "referral-code-input rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm", placeholder: "Enter new code", ref: referallCodeRef }), _jsx(Button, { size: "sm", className: "referral-code-save-button", onClick: handleUpdateReferralCode, disabled: isUpdatingCode || !newReferralCode, children: isUpdatingCode ? (_jsx(Loader2, { className: "referral-code-save-loading h-4 w-4 animate-spin" })) : ("Save") }), _jsx(Button, { size: "sm", variant: "ghost", className: "referral-code-cancel-button", onClick: () => {
|
|
152
185
|
setIsEditingCode(false);
|