@tomo-inc/chains-service 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/README.md +15 -0
- package/package.json +38 -0
- package/project.json +59 -0
- package/src/api/__tests__/config.ts +21 -0
- package/src/api/__tests__/token.test.ts +120 -0
- package/src/api/__tests__/transaction.test.ts +86 -0
- package/src/api/__tests__/user.test.ts +105 -0
- package/src/api/__tests__/wallet.test.ts +73 -0
- package/src/api/base.ts +52 -0
- package/src/api/index.ts +24 -0
- package/src/api/network-data.ts +572 -0
- package/src/api/network.ts +81 -0
- package/src/api/token.ts +182 -0
- package/src/api/transaction.ts +59 -0
- package/src/api/types/common.ts +35 -0
- package/src/api/types/index.ts +13 -0
- package/src/api/types/type.ts +283 -0
- package/src/api/user.ts +83 -0
- package/src/api/utils/index.ts +34 -0
- package/src/api/utils/signature.ts +60 -0
- package/src/api/wallet.ts +57 -0
- package/src/base/network.ts +55 -0
- package/src/base/service.ts +33 -0
- package/src/base/token.ts +43 -0
- package/src/base/transaction.ts +58 -0
- package/src/config.ts +21 -0
- package/src/dogecoin/base.ts +39 -0
- package/src/dogecoin/config.ts +43 -0
- package/src/dogecoin/rpc.ts +449 -0
- package/src/dogecoin/service.ts +451 -0
- package/src/dogecoin/type.ts +29 -0
- package/src/dogecoin/utils-doge.ts +105 -0
- package/src/dogecoin/utils.ts +601 -0
- package/src/evm/rpc.ts +68 -0
- package/src/evm/service.ts +403 -0
- package/src/evm/utils.ts +92 -0
- package/src/index.ts +28 -0
- package/src/solana/config.ts +5 -0
- package/src/solana/service.ts +312 -0
- package/src/solana/types.ts +91 -0
- package/src/solana/utils.ts +635 -0
- package/src/types/account.ts +58 -0
- package/src/types/dapp.ts +7 -0
- package/src/types/gas.ts +53 -0
- package/src/types/index.ts +81 -0
- package/src/types/network.ts +66 -0
- package/src/types/tx.ts +181 -0
- package/src/types/wallet.ts +49 -0
- package/src/wallet.ts +96 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +18 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
3
|
+
createAssociatedTokenAccountInstruction,
|
|
4
|
+
createTransferInstruction,
|
|
5
|
+
getAccount,
|
|
6
|
+
getAssociatedTokenAddress,
|
|
7
|
+
TOKEN_PROGRAM_ID,
|
|
8
|
+
TokenAccountNotFoundError,
|
|
9
|
+
TokenInvalidAccountOwnerError,
|
|
10
|
+
} from "@solana/spl-token";
|
|
11
|
+
import {
|
|
12
|
+
ComputeBudgetProgram,
|
|
13
|
+
Connection,
|
|
14
|
+
LAMPORTS_PER_SOL,
|
|
15
|
+
ParsedAccountData,
|
|
16
|
+
PublicKey,
|
|
17
|
+
SystemProgram,
|
|
18
|
+
Transaction,
|
|
19
|
+
TransactionMessage,
|
|
20
|
+
VersionedTransaction,
|
|
21
|
+
} from "@solana/web3.js";
|
|
22
|
+
|
|
23
|
+
import Bignumber from "bignumber.js";
|
|
24
|
+
import BN from "bn.js";
|
|
25
|
+
|
|
26
|
+
import { IntentTxData, SolSignParam, TokenInfo } from "./types";
|
|
27
|
+
|
|
28
|
+
import { CenterSubmitParams, SubmitParamsType } from "../types";
|
|
29
|
+
import { TOKEN_METADATA_PROGRAM_ID } from "./config";
|
|
30
|
+
|
|
31
|
+
export const TOKEN_METADATA_PROGRAM_PUBLICKEY = new PublicKey(TOKEN_METADATA_PROGRAM_ID);
|
|
32
|
+
|
|
33
|
+
export function isAddress(address: string): boolean {
|
|
34
|
+
try {
|
|
35
|
+
new PublicKey(address);
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const txToHex = (tx: VersionedTransaction | Transaction) => {
|
|
43
|
+
if (tx instanceof VersionedTransaction) {
|
|
44
|
+
return Buffer.from(tx.serialize()).toString("hex");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (tx instanceof Transaction) {
|
|
48
|
+
return Buffer.from(
|
|
49
|
+
tx.serialize({
|
|
50
|
+
requireAllSignatures: false,
|
|
51
|
+
verifySignatures: false,
|
|
52
|
+
}),
|
|
53
|
+
).toString("hex");
|
|
54
|
+
}
|
|
55
|
+
return tx;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const hexToTx = (hexString: string): Transaction | VersionedTransaction => {
|
|
59
|
+
try {
|
|
60
|
+
const buffer = Buffer.from(hexString, "hex");
|
|
61
|
+
try {
|
|
62
|
+
return VersionedTransaction.deserialize(buffer);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return Transaction.from(buffer);
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error(`Failed to deserialize transaction: ${error}`);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function hexToUint8Array(hexString: string): Uint8Array {
|
|
72
|
+
const cleanHex = hexString.startsWith("0x") ? hexString.slice(2) : hexString;
|
|
73
|
+
|
|
74
|
+
if (cleanHex.length !== 128) {
|
|
75
|
+
throw new Error("Invalid signature length");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const bytes = new Uint8Array(64);
|
|
79
|
+
for (let i = 0; i < 64; i++) {
|
|
80
|
+
bytes[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return bytes;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function createTransaction(
|
|
87
|
+
{
|
|
88
|
+
from = "",
|
|
89
|
+
to = "",
|
|
90
|
+
amount = 0,
|
|
91
|
+
priorityFee = 0, // microLamports (1 SOL = 1e6 microLamports)
|
|
92
|
+
}: IntentTxData,
|
|
93
|
+
connection: Connection,
|
|
94
|
+
) {
|
|
95
|
+
const fromPubkey = new PublicKey(from);
|
|
96
|
+
|
|
97
|
+
const instructions = [];
|
|
98
|
+
if (priorityFee > 0) {
|
|
99
|
+
instructions.push(
|
|
100
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
101
|
+
microLamports: priorityFee,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
instructions.push(
|
|
106
|
+
SystemProgram.transfer({
|
|
107
|
+
fromPubkey,
|
|
108
|
+
toPubkey: new PublicKey(to),
|
|
109
|
+
lamports: amount * LAMPORTS_PER_SOL,
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const blockhash = await connection.getLatestBlockhash().then((res) => res.blockhash);
|
|
114
|
+
|
|
115
|
+
// create v0 compatible message
|
|
116
|
+
const messageV0 = new TransactionMessage({
|
|
117
|
+
payerKey: fromPubkey,
|
|
118
|
+
recentBlockhash: blockhash,
|
|
119
|
+
instructions,
|
|
120
|
+
}).compileToV0Message();
|
|
121
|
+
|
|
122
|
+
// make a versioned transaction
|
|
123
|
+
const transactionV0 = new VersionedTransaction(messageV0);
|
|
124
|
+
// return Buffer.from(transactionV0.serialize()).toString('hex');
|
|
125
|
+
|
|
126
|
+
return transactionV0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function createLegacyTx({ from = "", to = "", amount = 0 }: IntentTxData, connection: Connection) {
|
|
130
|
+
const fromPubkey = new PublicKey(from);
|
|
131
|
+
const toPubkey = new PublicKey(to);
|
|
132
|
+
|
|
133
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
|
|
134
|
+
|
|
135
|
+
const transaction = new Transaction().add(
|
|
136
|
+
SystemProgram.transfer({
|
|
137
|
+
fromPubkey, //payer
|
|
138
|
+
toPubkey, //toAccount
|
|
139
|
+
lamports: amount * LAMPORTS_PER_SOL,
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
transaction.feePayer = fromPubkey;
|
|
143
|
+
transaction.recentBlockhash = blockhash;
|
|
144
|
+
transaction.lastValidBlockHeight = lastValidBlockHeight;
|
|
145
|
+
|
|
146
|
+
return transaction;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function createTokenLegacyTransaction(
|
|
150
|
+
{
|
|
151
|
+
from = "",
|
|
152
|
+
to = "",
|
|
153
|
+
amount = 0,
|
|
154
|
+
tokenAddress = "",
|
|
155
|
+
priorityFee = 0, // microLamports (1 SOL = 1e6 microLamports)
|
|
156
|
+
decimals = 6,
|
|
157
|
+
}: IntentTxData,
|
|
158
|
+
connection: Connection,
|
|
159
|
+
) {
|
|
160
|
+
try {
|
|
161
|
+
const fromPubkey = new PublicKey(from);
|
|
162
|
+
const toPubkey = new PublicKey(to);
|
|
163
|
+
const tokenPublicKey = new PublicKey(tokenAddress);
|
|
164
|
+
|
|
165
|
+
const fromTokenPubKey = await getAssociatedTokenAddress(tokenPublicKey, fromPubkey);
|
|
166
|
+
const toTokenPubKey = await getAssociatedTokenAddress(tokenPublicKey, toPubkey);
|
|
167
|
+
|
|
168
|
+
const transaction = new Transaction();
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
await getAccount(connection as any, toTokenPubKey);
|
|
172
|
+
} catch (error: unknown) {
|
|
173
|
+
if (error instanceof TokenAccountNotFoundError || error instanceof TokenInvalidAccountOwnerError) {
|
|
174
|
+
try {
|
|
175
|
+
const instruction = createAssociatedTokenAccountInstruction(
|
|
176
|
+
fromPubkey,
|
|
177
|
+
toTokenPubKey,
|
|
178
|
+
toPubkey,
|
|
179
|
+
tokenPublicKey,
|
|
180
|
+
);
|
|
181
|
+
transaction.add(instruction);
|
|
182
|
+
} catch (err: unknown) {
|
|
183
|
+
console.error(err);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const tokenAmount = new Bignumber(amount).multipliedBy(new Bignumber(10).pow(decimals)).toNumber();
|
|
191
|
+
|
|
192
|
+
transaction.add(
|
|
193
|
+
createTransferInstruction(fromTokenPubKey, toTokenPubKey, fromPubkey, tokenAmount, [], TOKEN_PROGRAM_ID),
|
|
194
|
+
);
|
|
195
|
+
const { blockhash } = await connection.getLatestBlockhash("finalized");
|
|
196
|
+
transaction.feePayer = fromPubkey;
|
|
197
|
+
transaction.recentBlockhash = blockhash;
|
|
198
|
+
return transaction;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error("Error creating transaction:", error);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function createTokenLegacyTransaction2(
|
|
206
|
+
{
|
|
207
|
+
from = "",
|
|
208
|
+
to = "",
|
|
209
|
+
amount = 0,
|
|
210
|
+
tokenAddress = "",
|
|
211
|
+
priorityFee = 0, // microLamports (1 SOL = 1e6 microLamports)
|
|
212
|
+
decimals = 6,
|
|
213
|
+
}: IntentTxData,
|
|
214
|
+
connection: Connection,
|
|
215
|
+
) {
|
|
216
|
+
try {
|
|
217
|
+
const fromPubkey = new PublicKey(from);
|
|
218
|
+
const toPubkey = new PublicKey(to);
|
|
219
|
+
const tokenPublicKey = new PublicKey(tokenAddress);
|
|
220
|
+
|
|
221
|
+
const fromTokenPubKey = await getAssociatedTokenAddress(tokenPublicKey, fromPubkey);
|
|
222
|
+
const toTokenPubKey = await getAssociatedTokenAddress(tokenPublicKey, toPubkey);
|
|
223
|
+
|
|
224
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
225
|
+
const transaction = new Transaction();
|
|
226
|
+
transaction.feePayer = fromPubkey;
|
|
227
|
+
transaction.recentBlockhash = blockhash;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const instruction = createAssociatedTokenAccountInstruction(fromPubkey, toTokenPubKey, toPubkey, tokenPublicKey);
|
|
231
|
+
transaction.add(instruction);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error(error);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const tokenAmount = new Bignumber(amount).multipliedBy(new Bignumber(10).pow(decimals)).toNumber();
|
|
238
|
+
|
|
239
|
+
transaction.add(
|
|
240
|
+
createTransferInstruction(fromTokenPubKey, toTokenPubKey, fromPubkey, tokenAmount, [], TOKEN_PROGRAM_ID),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return transaction;
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error("Error creating transaction:", error);
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function createOkxTxData(
|
|
251
|
+
{ amount = 0, from = "", to = "", tokenAddress = "", decimals = LAMPORTS_PER_SOL }: IntentTxData,
|
|
252
|
+
method: "sendSolana" | "sendToken",
|
|
253
|
+
connection: Connection,
|
|
254
|
+
): Promise<SolSignParam> {
|
|
255
|
+
const { blockhash: blockHash } = await connection.getLatestBlockhash();
|
|
256
|
+
|
|
257
|
+
if (method === "sendSolana") {
|
|
258
|
+
return {
|
|
259
|
+
type: "transfer",
|
|
260
|
+
payer: from,
|
|
261
|
+
blockHash,
|
|
262
|
+
from,
|
|
263
|
+
to,
|
|
264
|
+
amount: Math.floor(amount * decimals),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const tokenDetail = await getTokenDetailFromRpc(tokenAddress, connection);
|
|
269
|
+
return {
|
|
270
|
+
type: "tokenTransfer",
|
|
271
|
+
payer: from,
|
|
272
|
+
blockHash,
|
|
273
|
+
from,
|
|
274
|
+
to,
|
|
275
|
+
amount: new Bignumber(amount).multipliedBy(tokenDetail.decimals || 1).toString(),
|
|
276
|
+
mint: tokenAddress,
|
|
277
|
+
createAssociatedAddress: false,
|
|
278
|
+
token2022: false,
|
|
279
|
+
computeUnitLimit: 140000,
|
|
280
|
+
computeUnitPrice: 10,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function estimateFees(
|
|
285
|
+
{
|
|
286
|
+
txData,
|
|
287
|
+
priorityFee = 0,
|
|
288
|
+
computeUnits = 200000,
|
|
289
|
+
}: {
|
|
290
|
+
txData: any;
|
|
291
|
+
priorityFee?: number; // lamports per compute unit
|
|
292
|
+
computeUnits?: number;
|
|
293
|
+
},
|
|
294
|
+
connection: Connection,
|
|
295
|
+
) {
|
|
296
|
+
const { tokenAddress } = txData;
|
|
297
|
+
let transaction = null;
|
|
298
|
+
if (tokenAddress) {
|
|
299
|
+
transaction = await createTokenLegacyTransaction(txData, connection);
|
|
300
|
+
} else {
|
|
301
|
+
transaction = await createLegacyTx(txData, connection);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let message = (transaction as any).message;
|
|
305
|
+
// const rawTransaction = transaction?.serialize();
|
|
306
|
+
// if (rawTransaction) {
|
|
307
|
+
// message = Message.from(rawTransaction);
|
|
308
|
+
// }
|
|
309
|
+
if (!message) {
|
|
310
|
+
message = transaction.compileMessage();
|
|
311
|
+
}
|
|
312
|
+
const fee = await connection.getFeeForMessage(message, "confirmed");
|
|
313
|
+
const baseFee = ((fee.value || 5000) * 2) / LAMPORTS_PER_SOL;
|
|
314
|
+
const totalPriorityFee = (priorityFee * computeUnits) / 1_000_000; // to lamports
|
|
315
|
+
return {
|
|
316
|
+
baseFee: baseFee.toString(),
|
|
317
|
+
priorityFee: totalPriorityFee,
|
|
318
|
+
totalFee: (baseFee + totalPriorityFee / LAMPORTS_PER_SOL).toString(),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export async function getBalances(addresses: string[], connection: Connection) {
|
|
323
|
+
const balances = await connection.getMultipleAccountsInfo(addresses.map((addr) => new PublicKey(addr)));
|
|
324
|
+
|
|
325
|
+
const balancesMap: { [key: string]: number } = {};
|
|
326
|
+
addresses.map((address, index) => {
|
|
327
|
+
balancesMap[address] = (balances[index]?.lamports || 0) / LAMPORTS_PER_SOL;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return balancesMap;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export async function getTokenBalances(address: string, connection: Connection) {
|
|
334
|
+
const pubkey = new PublicKey(address);
|
|
335
|
+
|
|
336
|
+
const accounts = await connection.getParsedTokenAccountsByOwner(pubkey, {
|
|
337
|
+
programId: TOKEN_PROGRAM_ID,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const balancesMap: { [key: string]: { tokenAddress: string; balance: number; decimals: number } } = {};
|
|
341
|
+
accounts.value.map((account) => {
|
|
342
|
+
const parsedData = account.account.data as ParsedAccountData;
|
|
343
|
+
const info = parsedData.parsed?.info;
|
|
344
|
+
|
|
345
|
+
const res = {
|
|
346
|
+
tokenAddress: info.mint,
|
|
347
|
+
balance: Number(info.tokenAmount.amount),
|
|
348
|
+
decimals: info.tokenAmount.decimals || LAMPORTS_PER_SOL,
|
|
349
|
+
};
|
|
350
|
+
balancesMap[info.mint] = res;
|
|
351
|
+
// return res;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return balancesMap;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const tokenCache = new Map();
|
|
358
|
+
export const getTokenDetailFromRpc = async (tokenAddress: string, connection: Connection): Promise<TokenInfo> => {
|
|
359
|
+
try {
|
|
360
|
+
if (tokenCache.has(tokenAddress)) {
|
|
361
|
+
return tokenCache.get(tokenAddress);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const mint = new PublicKey(tokenAddress);
|
|
365
|
+
const [metadataPDA] = PublicKey.findProgramAddressSync(
|
|
366
|
+
[Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_PUBLICKEY.toBuffer(), mint.toBuffer()],
|
|
367
|
+
TOKEN_METADATA_PROGRAM_PUBLICKEY,
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const accountInfo = await connection.getAccountInfo(metadataPDA);
|
|
371
|
+
if (!accountInfo || !accountInfo.data) {
|
|
372
|
+
return null as unknown as TokenInfo;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Parse metadata directly from buffer
|
|
376
|
+
const metadata = decodeMetadata(accountInfo.data);
|
|
377
|
+
|
|
378
|
+
const tokenInfo = {
|
|
379
|
+
address: tokenAddress,
|
|
380
|
+
name: metadata.name,
|
|
381
|
+
symbol: metadata.symbol,
|
|
382
|
+
icon: "",
|
|
383
|
+
uri: metadata.uri,
|
|
384
|
+
decimals: metadata.decimals,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Fetch token metadata from URI if available
|
|
388
|
+
//uri = https://gateway.pinata.cloud/ipfs/QmbtPVC8Ki79jKMioSDSY9SSDiNgk4mZA5HHZXeMiPTgmT;
|
|
389
|
+
if (metadata.uri) {
|
|
390
|
+
try {
|
|
391
|
+
const response = await fetch(metadata.uri);
|
|
392
|
+
const json: any = await response.json();
|
|
393
|
+
tokenInfo.icon = json.image || "";
|
|
394
|
+
} catch (e) {
|
|
395
|
+
console.error("Failed to fetch token metadata:", e);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
tokenCache.set(tokenAddress, tokenInfo);
|
|
400
|
+
return tokenInfo;
|
|
401
|
+
} catch (e) {
|
|
402
|
+
console.error("Failed to get token details:", e);
|
|
403
|
+
return null as unknown as TokenInfo;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Helper function to decode metadata from buffer
|
|
408
|
+
export function decodeMetadata(buffer: Buffer) {
|
|
409
|
+
try {
|
|
410
|
+
// Key (1 byte) + UpdateAuthority (32 bytes) + Mint (32 bytes) + Data
|
|
411
|
+
let offset = 1 + 32 + 32;
|
|
412
|
+
|
|
413
|
+
const readByte = function () {
|
|
414
|
+
const value = buffer[offset];
|
|
415
|
+
offset += 1;
|
|
416
|
+
return value;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const readString = function () {
|
|
420
|
+
// String length (4 bytes)
|
|
421
|
+
const length = buffer.readUInt32LE(offset);
|
|
422
|
+
offset += 4;
|
|
423
|
+
if (length > 1000) {
|
|
424
|
+
throw new Error("String too long");
|
|
425
|
+
}
|
|
426
|
+
// Read string data
|
|
427
|
+
const str = buffer.slice(offset, offset + length).toString("utf8");
|
|
428
|
+
offset += length;
|
|
429
|
+
return str.replace(/x00/g, "");
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// Read name, symbol, uri
|
|
433
|
+
const name = readString();
|
|
434
|
+
const symbol = readString();
|
|
435
|
+
const uri = readString();
|
|
436
|
+
|
|
437
|
+
// Read seller fee basis points (2 bytes)
|
|
438
|
+
offset += 2;
|
|
439
|
+
|
|
440
|
+
// Read creators array length
|
|
441
|
+
const creatorLength = readByte();
|
|
442
|
+
// Skip creators if any
|
|
443
|
+
if (creatorLength) {
|
|
444
|
+
offset += creatorLength * (32 + 1 + 1);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Read decimals
|
|
448
|
+
const decimals = readByte();
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
name,
|
|
452
|
+
symbol,
|
|
453
|
+
uri,
|
|
454
|
+
decimals,
|
|
455
|
+
};
|
|
456
|
+
} catch (e) {
|
|
457
|
+
console.error("Metadata decode error:", e);
|
|
458
|
+
return {
|
|
459
|
+
name: "",
|
|
460
|
+
symbol: "",
|
|
461
|
+
uri: "",
|
|
462
|
+
decimals: 0,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Parses a Solana transaction and extracts relevant information.
|
|
469
|
+
*
|
|
470
|
+
* @param transaction The Solana transaction to parse.
|
|
471
|
+
* @returns An object containing the transaction's from address, to address, amount, and mint address (if applicable).
|
|
472
|
+
*/
|
|
473
|
+
|
|
474
|
+
export async function parseSolTx(
|
|
475
|
+
transaction: Transaction | VersionedTransaction,
|
|
476
|
+
connection: Connection,
|
|
477
|
+
): Promise<{
|
|
478
|
+
from: string;
|
|
479
|
+
to: string;
|
|
480
|
+
amount: number;
|
|
481
|
+
tokenAddress?: string;
|
|
482
|
+
}> {
|
|
483
|
+
try {
|
|
484
|
+
const txData = parseSolVersionedTx(transaction as VersionedTransaction);
|
|
485
|
+
if (txData && txData.from) {
|
|
486
|
+
return txData;
|
|
487
|
+
}
|
|
488
|
+
return null as unknown as { from: string; to: string; amount: number; tokenAddress?: string };
|
|
489
|
+
} catch (error) {
|
|
490
|
+
return parseSolLegacyTx(transaction as Transaction, connection);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export async function parseSolLegacyTx(
|
|
495
|
+
transaction: Transaction,
|
|
496
|
+
connection: Connection,
|
|
497
|
+
): Promise<{
|
|
498
|
+
from: string;
|
|
499
|
+
to: string;
|
|
500
|
+
amount: number;
|
|
501
|
+
tokenAddress?: string;
|
|
502
|
+
}> {
|
|
503
|
+
let from = "";
|
|
504
|
+
let to = "";
|
|
505
|
+
let value = 0;
|
|
506
|
+
let mintAddress: string | undefined;
|
|
507
|
+
|
|
508
|
+
// token 1st
|
|
509
|
+
for (const instruction of transaction.instructions) {
|
|
510
|
+
if (instruction.programId.equals(TOKEN_PROGRAM_ID) && instruction.data[0] === 3) {
|
|
511
|
+
const amountData = instruction.data.slice(1, 9);
|
|
512
|
+
value = Number(new BN(amountData, "le"));
|
|
513
|
+
|
|
514
|
+
// ata accounts
|
|
515
|
+
const fromTokenAccount = instruction.keys[0]?.pubkey;
|
|
516
|
+
const toTokenAccount = instruction.keys[1]?.pubkey;
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
// to account check
|
|
520
|
+
const toAccount = await getAccount(connection as any, toTokenAccount!);
|
|
521
|
+
mintAddress = toAccount.mint.toString();
|
|
522
|
+
to = toAccount.owner.toString(); // to
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.warn("Failed to get token account:", error);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
//token account
|
|
528
|
+
if (!from && fromTokenAccount) {
|
|
529
|
+
try {
|
|
530
|
+
const fromAccount = await getAccount(connection as any, fromTokenAccount);
|
|
531
|
+
from = fromAccount.owner.toString();
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.warn("Failed to get source token account:", error);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ata account
|
|
540
|
+
for (const instruction of transaction.instructions) {
|
|
541
|
+
if (instruction.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
|
|
542
|
+
const mintKey = instruction.keys[3]?.pubkey;
|
|
543
|
+
const ownerKey = instruction.keys[2]?.pubkey;
|
|
544
|
+
if (mintKey && ownerKey) {
|
|
545
|
+
mintAddress = mintKey.toString();
|
|
546
|
+
to = ownerKey.toString();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// sol transfer
|
|
552
|
+
for (const instruction of transaction.instructions) {
|
|
553
|
+
if (instruction.programId.equals(SystemProgram.programId) && instruction.data[0] === 2) {
|
|
554
|
+
const fromPubkey = instruction.keys[0]?.pubkey.toString();
|
|
555
|
+
const toPubkey = instruction.keys[1]?.pubkey.toString();
|
|
556
|
+
const lamportsData = instruction.data.slice(4);
|
|
557
|
+
value = Number(new BN(lamportsData, "le"));
|
|
558
|
+
from = fromPubkey!;
|
|
559
|
+
to = toPubkey!;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return { from, to, amount: value, tokenAddress: mintAddress };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const parseSolVersionedTx = (versionedTx: VersionedTransaction): any => {
|
|
567
|
+
try {
|
|
568
|
+
const message = TransactionMessage.decompile(versionedTx.message);
|
|
569
|
+
|
|
570
|
+
for (const instruction of message.instructions) {
|
|
571
|
+
const programId = instruction.programId.toString();
|
|
572
|
+
|
|
573
|
+
// SPL Token
|
|
574
|
+
if (programId === TOKEN_PROGRAM_ID.toString()) {
|
|
575
|
+
// 3 = Transfer instruction
|
|
576
|
+
// 8 bytes = amount (u64)
|
|
577
|
+
if (instruction.data[0] === 3) {
|
|
578
|
+
const amount = instruction.data.slice(1, 9).readBigUInt64LE();
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
from: instruction.keys[0].pubkey.toString(),
|
|
582
|
+
to: instruction.keys[1].pubkey.toString(),
|
|
583
|
+
amount: Number(amount),
|
|
584
|
+
tokenAddress: instruction.keys[1].pubkey.toString(), // Token mint address
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
} else if (
|
|
588
|
+
programId === "System11111111111111111111111111111111" ||
|
|
589
|
+
programId === "11111111111111111111111111111111"
|
|
590
|
+
) {
|
|
591
|
+
// Transfer instruction = 2
|
|
592
|
+
if (instruction.data[0] === 2) {
|
|
593
|
+
const amount = instruction.data.slice(4).readBigUInt64LE();
|
|
594
|
+
|
|
595
|
+
return {
|
|
596
|
+
from: instruction.keys[0].pubkey.toString(),
|
|
597
|
+
to: instruction.keys[1].pubkey.toString(),
|
|
598
|
+
amount: Number(amount) / LAMPORTS_PER_SOL,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return {};
|
|
605
|
+
} catch (error) {
|
|
606
|
+
console.error("Error parsing transaction:", error);
|
|
607
|
+
return {};
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
export async function buildSubmitTxParams(
|
|
612
|
+
txData: IntentTxData & { chainIndex: string },
|
|
613
|
+
callData: string,
|
|
614
|
+
): Promise<CenterSubmitParams> {
|
|
615
|
+
const { from, to, tokenAddress, chainIndex } = txData;
|
|
616
|
+
const centerParams: CenterSubmitParams = {
|
|
617
|
+
type: (txData.submitType as SubmitParamsType) || SubmitParamsType.TRANSFER,
|
|
618
|
+
userId: "",
|
|
619
|
+
source: 0,
|
|
620
|
+
fromAddress: from,
|
|
621
|
+
fromAmount: txData.amount?.toString() || "",
|
|
622
|
+
toAddress: to,
|
|
623
|
+
fromChainIndex: chainIndex,
|
|
624
|
+
fromTokenAddress: tokenAddress || "",
|
|
625
|
+
toChainIndex: chainIndex,
|
|
626
|
+
toTokenAddress: tokenAddress || "",
|
|
627
|
+
toAmount: txData.amount?.toString() || "",
|
|
628
|
+
sourceDex: "",
|
|
629
|
+
estimatedGas: "",
|
|
630
|
+
failReason: "",
|
|
631
|
+
tx: "",
|
|
632
|
+
callData,
|
|
633
|
+
};
|
|
634
|
+
return centerParams;
|
|
635
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ChainTypes } from "@tomo-inc/wallet-utils";
|
|
2
|
+
|
|
3
|
+
export interface WalletAccountItem {
|
|
4
|
+
addresses: object[];
|
|
5
|
+
path: string;
|
|
6
|
+
publicKey: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AddressItem {
|
|
10
|
+
address: string;
|
|
11
|
+
path: string;
|
|
12
|
+
publicKey: string;
|
|
13
|
+
subType: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Account Types
|
|
17
|
+
export enum AccountType {
|
|
18
|
+
EOA = "EOA",
|
|
19
|
+
SOCIAL = "SOCIAL",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ChainAddress {
|
|
23
|
+
subType: string;
|
|
24
|
+
address: string;
|
|
25
|
+
path: string;
|
|
26
|
+
publicKey?: string;
|
|
27
|
+
privateKey?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Base Account Interface
|
|
31
|
+
export interface IAccount {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly userId?: string | number;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly type: AccountType;
|
|
36
|
+
readonly lastUsedTime: number;
|
|
37
|
+
readonly sortIndex?: number;
|
|
38
|
+
readonly addresses: Record<ChainTypes, ChainAddress[]>;
|
|
39
|
+
|
|
40
|
+
readonly mnemonic: string | null;
|
|
41
|
+
readonly addressIndex: number;
|
|
42
|
+
|
|
43
|
+
readonly isImported: boolean;
|
|
44
|
+
readonly isBackedUp: boolean;
|
|
45
|
+
readonly isLocked: boolean; // ?? -> TODO: remove this, use passwordManager instead
|
|
46
|
+
readonly isFrozen: boolean;
|
|
47
|
+
|
|
48
|
+
// External id
|
|
49
|
+
readonly externalId?: string | number;
|
|
50
|
+
readonly totalAssets?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface IaccountInfo {
|
|
54
|
+
getCurrent: () => Promise<any[]>;
|
|
55
|
+
signMessage: (message: string) => Promise<string>;
|
|
56
|
+
signTypedData: (message: any) => Promise<string>;
|
|
57
|
+
signTransaction: (transaction: any) => Promise<string>;
|
|
58
|
+
}
|