@deserialize/multi-vm-wallet 1.0.0
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/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/IChainWallet.d.ts +16 -0
- package/dist/utils/IChainWallet.js +22 -0
- package/dist/utils/bip32.d.ts +11 -0
- package/dist/utils/bip32.js +99 -0
- package/dist/utils/evm/evm.d.ts +30 -0
- package/dist/utils/evm/evm.js +72 -0
- package/dist/utils/evm/index.d.ts +2 -0
- package/dist/utils/evm/index.js +18 -0
- package/dist/utils/evm/utils.d.ts +92 -0
- package/dist/utils/evm/utils.js +346 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +22 -0
- package/dist/utils/svm/index.d.ts +1 -0
- package/dist/utils/svm/index.js +17 -0
- package/dist/utils/svm/svm.d.ts +24 -0
- package/dist/utils/svm/svm.js +71 -0
- package/dist/utils/svm/transactionSender.d.ts +8 -0
- package/dist/utils/svm/transactionSender.js +83 -0
- package/dist/utils/svm/utils.d.ts +26 -0
- package/dist/utils/svm/utils.js +161 -0
- package/dist/utils/types.d.ts +44 -0
- package/dist/utils/types.js +9 -0
- package/dist/utils/vm.d.ts +13 -0
- package/dist/utils/vm.js +49 -0
- package/package.json +42 -0
- package/tsconfig.json +115 -0
- package/utils/IChainWallet.ts +36 -0
- package/utils/bip32.ts +66 -0
- package/utils/evm/evm.ts +84 -0
- package/utils/evm/index.ts +2 -0
- package/utils/evm/utils.ts +504 -0
- package/utils/index.ts +6 -0
- package/utils/svm/index.js +17 -0
- package/utils/svm/index.ts +1 -0
- package/utils/svm/svm.js +71 -0
- package/utils/svm/svm.ts +75 -0
- package/utils/svm/transactionSender.js +83 -0
- package/utils/svm/transactionSender.ts +108 -0
- package/utils/svm/utils.js +161 -0
- package/utils/svm/utils.ts +203 -0
- package/utils/types.ts +53 -0
- package/utils/vm.ts +26 -0
package/utils/svm/svm.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
|
2
|
+
import { SVMDeriveChildPrivateKey } from "../bip32";
|
|
3
|
+
import { VM } from "../vm";
|
|
4
|
+
import { ChainWallet } from "../IChainWallet";
|
|
5
|
+
import { Balance, ChainWalletConfig, TokenInfo, TransactionResult } from "../types";
|
|
6
|
+
import { getSvmNativeBalance, getTokenBalance, getTransferNativeTransaction, getTransferTokenTransaction, signAndSendTransaction } from "./utils";
|
|
7
|
+
import BN from "bn.js";
|
|
8
|
+
|
|
9
|
+
export class SVMVM extends VM<PublicKey, Keypair, Connection> {
|
|
10
|
+
derivationPath = "m/44'/501'/"; // Default EVM derivation path
|
|
11
|
+
|
|
12
|
+
constructor(mnemonic: string) {
|
|
13
|
+
super(mnemonic, "SVM");
|
|
14
|
+
}
|
|
15
|
+
static validateAddress(address: PublicKey): boolean {
|
|
16
|
+
return PublicKey.isOnCurve(address.toBuffer());
|
|
17
|
+
}
|
|
18
|
+
static getNativeBalance(address: PublicKey, connection: Connection): Promise<Balance> {
|
|
19
|
+
return getSvmNativeBalance(address, connection);
|
|
20
|
+
}
|
|
21
|
+
static async getTokenBalance(address: PublicKey, tokenAddress: PublicKey, connection: Connection): Promise<Balance> {
|
|
22
|
+
const balance = await getTokenBalance(address, tokenAddress, connection);
|
|
23
|
+
if (balance === 0) {
|
|
24
|
+
return { balance: new BN(0), formatted: 0, decimal: 0 };
|
|
25
|
+
}
|
|
26
|
+
return { balance: new BN(balance.amount), formatted: balance.uiAmount || parseInt(balance.amount) / 10 ** balance.decimals, decimal: balance.decimals };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static signAndSendTransaction = signAndSendTransaction
|
|
30
|
+
generatePrivateKey(index: number, seedPhrase = this.mnemonic, derivationPath = this.derivationPath) {
|
|
31
|
+
const seed = this.mnemonicToSeed(seedPhrase);
|
|
32
|
+
const privateKey = SVMDeriveChildPrivateKey(seed, index, derivationPath);
|
|
33
|
+
return { privateKey, index };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class SVMChainWallet extends ChainWallet<PublicKey, Keypair, Connection> {
|
|
39
|
+
constructor(config: ChainWalletConfig, privateKey: Keypair, index: number) {
|
|
40
|
+
super(config, privateKey, index);
|
|
41
|
+
this.address = privateKey.publicKey;
|
|
42
|
+
this.privateKey = privateKey;
|
|
43
|
+
this.connection = new Connection(config.rpcUrl)
|
|
44
|
+
}
|
|
45
|
+
generateAddress() {
|
|
46
|
+
return this.address;
|
|
47
|
+
}
|
|
48
|
+
async getNativeBalance(): Promise<Balance> {
|
|
49
|
+
// Implement native balance retrieval logic here
|
|
50
|
+
return await SVMVM.getNativeBalance(this.address, this.connection!)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getTokenBalance(tokenAddress: PublicKey): Promise<Balance> {
|
|
54
|
+
// Implement token balance retrieval logic here
|
|
55
|
+
return await SVMVM.getTokenBalance(this.address, (tokenAddress), this.connection!);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async transferNative(to: PublicKey, amount: number): Promise<TransactionResult> {
|
|
59
|
+
// Implement native transfer logic here
|
|
60
|
+
const transaction = await getTransferNativeTransaction(this.privateKey, to, amount, this.connection!)
|
|
61
|
+
const hash = await SVMVM.signAndSendTransaction(transaction, this.connection!, [this.privateKey]);
|
|
62
|
+
return { success: true, hash } // Placeholder
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async transferToken(token: TokenInfo, to: PublicKey, amount: number): Promise<TransactionResult> {
|
|
66
|
+
// Implement token transfer logic here
|
|
67
|
+
const transaction = await getTransferTokenTransaction(this.privateKey, new PublicKey(to), token, (amount), this.connection!);
|
|
68
|
+
const hash = await SVMVM.signAndSendTransaction(transaction, this.connection!, [this.privateKey]);
|
|
69
|
+
return { success: true, hash }; // Placeholder
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
//swaps jupiter swap here
|
|
75
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.transactionSenderAndConfirmationWaiter = transactionSenderAndConfirmationWaiter;
|
|
7
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
8
|
+
const promise_retry_1 = __importDefault(require("promise-retry"));
|
|
9
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
const SEND_OPTIONS = {
|
|
11
|
+
skipPreflight: false,
|
|
12
|
+
};
|
|
13
|
+
async function transactionSenderAndConfirmationWaiter({ connection, serializedTransaction, blockhashWithExpiryBlockHeight, }) {
|
|
14
|
+
const txid = await connection.sendRawTransaction(serializedTransaction, SEND_OPTIONS);
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const abortSignal = controller.signal;
|
|
17
|
+
const abortableResender = async () => {
|
|
18
|
+
while (true) {
|
|
19
|
+
await wait(2000);
|
|
20
|
+
if (abortSignal.aborted)
|
|
21
|
+
return;
|
|
22
|
+
try {
|
|
23
|
+
await connection.sendRawTransaction(serializedTransaction, SEND_OPTIONS);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
console.warn(`Failed to resend transaction: ${e}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
abortableResender();
|
|
32
|
+
const lastValidBlockHeight = blockhashWithExpiryBlockHeight.lastValidBlockHeight - 150;
|
|
33
|
+
// this would throw TransactionExpiredBlockheightExceededError
|
|
34
|
+
await Promise.race([
|
|
35
|
+
connection.confirmTransaction({
|
|
36
|
+
...blockhashWithExpiryBlockHeight,
|
|
37
|
+
lastValidBlockHeight,
|
|
38
|
+
signature: txid,
|
|
39
|
+
abortSignal,
|
|
40
|
+
}, "confirmed"),
|
|
41
|
+
new Promise(async (resolve) => {
|
|
42
|
+
// in case ws socket died
|
|
43
|
+
while (!abortSignal.aborted) {
|
|
44
|
+
await wait(2000);
|
|
45
|
+
const tx = await connection.getSignatureStatus(txid, {
|
|
46
|
+
searchTransactionHistory: false,
|
|
47
|
+
});
|
|
48
|
+
if (tx?.value?.confirmationStatus === "confirmed") {
|
|
49
|
+
resolve(tx);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
if (e instanceof web3_js_1.TransactionExpiredBlockheightExceededError) {
|
|
57
|
+
// we consume this error and getTransaction would return null
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// invalid state from web3.js
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
controller.abort();
|
|
67
|
+
}
|
|
68
|
+
// in case rpc is not synced yet, we add some retries
|
|
69
|
+
const response = (0, promise_retry_1.default)(async (retry) => {
|
|
70
|
+
const response = await connection.getTransaction(txid, {
|
|
71
|
+
commitment: "confirmed",
|
|
72
|
+
maxSupportedTransactionVersion: 0,
|
|
73
|
+
});
|
|
74
|
+
if (!response) {
|
|
75
|
+
retry(response);
|
|
76
|
+
}
|
|
77
|
+
return response;
|
|
78
|
+
}, {
|
|
79
|
+
retries: 5,
|
|
80
|
+
minTimeout: 1e3,
|
|
81
|
+
});
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlockhashWithExpiryBlockHeight,
|
|
3
|
+
Connection,
|
|
4
|
+
TransactionExpiredBlockheightExceededError,
|
|
5
|
+
VersionedTransactionResponse,
|
|
6
|
+
} from "@solana/web3.js";
|
|
7
|
+
import promiseRetry from "promise-retry";
|
|
8
|
+
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
9
|
+
|
|
10
|
+
type TransactionSenderAndConfirmationWaiterArgs = {
|
|
11
|
+
connection: Connection;
|
|
12
|
+
serializedTransaction: Buffer;
|
|
13
|
+
blockhashWithExpiryBlockHeight: BlockhashWithExpiryBlockHeight;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const SEND_OPTIONS = {
|
|
17
|
+
skipPreflight: false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function transactionSenderAndConfirmationWaiter({
|
|
21
|
+
connection,
|
|
22
|
+
serializedTransaction,
|
|
23
|
+
blockhashWithExpiryBlockHeight,
|
|
24
|
+
}: TransactionSenderAndConfirmationWaiterArgs): Promise<VersionedTransactionResponse | null> {
|
|
25
|
+
const txid = await connection.sendRawTransaction(
|
|
26
|
+
serializedTransaction,
|
|
27
|
+
SEND_OPTIONS
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const abortSignal = controller.signal;
|
|
32
|
+
|
|
33
|
+
const abortableResender = async () => {
|
|
34
|
+
while (true) {
|
|
35
|
+
await wait(2_000);
|
|
36
|
+
if (abortSignal.aborted) return;
|
|
37
|
+
try {
|
|
38
|
+
await connection.sendRawTransaction(
|
|
39
|
+
serializedTransaction,
|
|
40
|
+
SEND_OPTIONS
|
|
41
|
+
);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn(`Failed to resend transaction: ${e}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
abortableResender();
|
|
50
|
+
const lastValidBlockHeight =
|
|
51
|
+
blockhashWithExpiryBlockHeight.lastValidBlockHeight - 150;
|
|
52
|
+
|
|
53
|
+
// this would throw TransactionExpiredBlockheightExceededError
|
|
54
|
+
await Promise.race([
|
|
55
|
+
connection.confirmTransaction(
|
|
56
|
+
{
|
|
57
|
+
...blockhashWithExpiryBlockHeight,
|
|
58
|
+
lastValidBlockHeight,
|
|
59
|
+
signature: txid,
|
|
60
|
+
abortSignal,
|
|
61
|
+
},
|
|
62
|
+
"confirmed"
|
|
63
|
+
),
|
|
64
|
+
new Promise(async (resolve) => {
|
|
65
|
+
// in case ws socket died
|
|
66
|
+
while (!abortSignal.aborted) {
|
|
67
|
+
await wait(2_000);
|
|
68
|
+
const tx = await connection.getSignatureStatus(txid, {
|
|
69
|
+
searchTransactionHistory: false,
|
|
70
|
+
});
|
|
71
|
+
if (tx?.value?.confirmationStatus === "confirmed") {
|
|
72
|
+
resolve(tx);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
]);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
if (e instanceof TransactionExpiredBlockheightExceededError) {
|
|
79
|
+
// we consume this error and getTransaction would return null
|
|
80
|
+
return null;
|
|
81
|
+
} else {
|
|
82
|
+
// invalid state from web3.js
|
|
83
|
+
throw e;
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
controller.abort();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// in case rpc is not synced yet, we add some retries
|
|
90
|
+
const response = promiseRetry(
|
|
91
|
+
async (retry) => {
|
|
92
|
+
const response = await connection.getTransaction(txid, {
|
|
93
|
+
commitment: "confirmed",
|
|
94
|
+
maxSupportedTransactionVersion: 0,
|
|
95
|
+
});
|
|
96
|
+
if (!response) {
|
|
97
|
+
retry(response);
|
|
98
|
+
}
|
|
99
|
+
return response;
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
retries: 5,
|
|
103
|
+
minTimeout: 1e3,
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return response;
|
|
108
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//we will write all the svm utils function here
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.signAndSendTransaction = exports.getTransferTokenTransaction = exports.getTransferTokenInx = exports.getTransferNativeTransaction = exports.getTransferNativeInx = exports.getTokenAccountAccount = exports.getTokenBalance = exports.getSvmNativeBalance = exports.getProgramIdOfToken = exports.getSureAssociatedTokenAddressAndAccount = exports.createAtaAndIx = exports.createV0Transaction = void 0;
|
|
5
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
6
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
7
|
+
const transactionSender_1 = require("./transactionSender");
|
|
8
|
+
const bn_js_1 = require("bn.js");
|
|
9
|
+
const createV0Transaction = async (connection, inX, signers, payerPubKey, blockHash) => {
|
|
10
|
+
const blockhash = blockHash || (await connection.getLatestBlockhash()).blockhash;
|
|
11
|
+
const message = new web3_js_1.TransactionMessage({
|
|
12
|
+
payerKey: payerPubKey,
|
|
13
|
+
instructions: inX,
|
|
14
|
+
recentBlockhash: blockhash,
|
|
15
|
+
}).compileToV0Message();
|
|
16
|
+
const transaction = new web3_js_1.VersionedTransaction(message);
|
|
17
|
+
transaction.message.staticAccountKeys;
|
|
18
|
+
if (signers.length < 1) {
|
|
19
|
+
transaction.sign(signers);
|
|
20
|
+
}
|
|
21
|
+
return transaction;
|
|
22
|
+
};
|
|
23
|
+
exports.createV0Transaction = createV0Transaction;
|
|
24
|
+
const createAtaAndIx = async (token, ownerPublicKey, tokenProgramId, connection) => {
|
|
25
|
+
let AtaTokenIx;
|
|
26
|
+
const associatedToken = (0, spl_token_1.getAssociatedTokenAddressSync)(token, ownerPublicKey, false, tokenProgramId);
|
|
27
|
+
const accountExist = await connection.getAccountInfo(associatedToken);
|
|
28
|
+
if (!accountExist) {
|
|
29
|
+
AtaTokenIx = (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(ownerPublicKey, associatedToken, ownerPublicKey, token, tokenProgramId);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
AtaTokenIx,
|
|
33
|
+
associatedToken,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
exports.createAtaAndIx = createAtaAndIx;
|
|
37
|
+
const getSureAssociatedTokenAddressAndAccount = async (connection, token, owner) => {
|
|
38
|
+
let ATA;
|
|
39
|
+
let programId;
|
|
40
|
+
let tokenAccount;
|
|
41
|
+
try {
|
|
42
|
+
programId = token.equals(spl_token_1.NATIVE_MINT)
|
|
43
|
+
? spl_token_1.TOKEN_PROGRAM_ID
|
|
44
|
+
: spl_token_1.TOKEN_2022_PROGRAM_ID;
|
|
45
|
+
ATA = (0, spl_token_1.getAssociatedTokenAddressSync)(token, owner, true, programId);
|
|
46
|
+
tokenAccount = await (0, spl_token_1.getAccount)(connection, ATA, "confirmed", programId);
|
|
47
|
+
return { ATA, programId, tokenAccount };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
programId = spl_token_1.TOKEN_PROGRAM_ID;
|
|
51
|
+
ATA = (0, spl_token_1.getAssociatedTokenAddressSync)(token, owner, true, programId);
|
|
52
|
+
tokenAccount = await (0, spl_token_1.getAccount)(connection, ATA, "confirmed", programId);
|
|
53
|
+
return { ATA, programId, tokenAccount };
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
exports.getSureAssociatedTokenAddressAndAccount = getSureAssociatedTokenAddressAndAccount;
|
|
57
|
+
const getProgramIdOfToken = async (owner, token, connection) => {
|
|
58
|
+
if (token.equals(spl_token_1.NATIVE_MINT)) {
|
|
59
|
+
return spl_token_1.TOKEN_PROGRAM_ID;
|
|
60
|
+
}
|
|
61
|
+
let ATA;
|
|
62
|
+
let programId = spl_token_1.TOKEN_PROGRAM_ID;
|
|
63
|
+
let tokenAccount;
|
|
64
|
+
try {
|
|
65
|
+
ATA = (0, spl_token_1.getAssociatedTokenAddressSync)(token, owner, true, programId);
|
|
66
|
+
tokenAccount = await (0, spl_token_1.getAccount)(connection, ATA, "confirmed", programId);
|
|
67
|
+
return spl_token_1.TOKEN_PROGRAM_ID;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return spl_token_1.TOKEN_2022_PROGRAM_ID;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
exports.getProgramIdOfToken = getProgramIdOfToken;
|
|
74
|
+
//get native balance
|
|
75
|
+
const getSvmNativeBalance = async (address, connection) => {
|
|
76
|
+
const balance = await connection.getBalance(address);
|
|
77
|
+
return { balance: new bn_js_1.BN(balance), formatted: balance / web3_js_1.LAMPORTS_PER_SOL, decimal: 9 };
|
|
78
|
+
};
|
|
79
|
+
exports.getSvmNativeBalance = getSvmNativeBalance;
|
|
80
|
+
const getTokenBalance = async (address, token, connection) => {
|
|
81
|
+
try {
|
|
82
|
+
// Get the balance from the token account
|
|
83
|
+
const tokenAccount = await (0, exports.getTokenAccountAccount)(token, address, connection);
|
|
84
|
+
if (!tokenAccount) {
|
|
85
|
+
console.error("Token account not found");
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
const tokenBalance = await connection.getTokenAccountBalance(tokenAccount.address);
|
|
89
|
+
if (!tokenBalance) {
|
|
90
|
+
console.error("Token balance not found");
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
return tokenBalance.value;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
exports.getTokenBalance = getTokenBalance;
|
|
100
|
+
const getTokenAccountAccount = async (token, address, connection) => {
|
|
101
|
+
try {
|
|
102
|
+
// Get the associated token account address for the user and the token mint
|
|
103
|
+
const associatedTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(token, // The token mint address
|
|
104
|
+
address // The user's public key
|
|
105
|
+
);
|
|
106
|
+
// Fetch the token account information
|
|
107
|
+
const tokenAccount = await (0, spl_token_1.getAccount)(connection, associatedTokenAccount);
|
|
108
|
+
return tokenAccount;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error("Error getting token balance:");
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
exports.getTokenAccountAccount = getTokenAccountAccount;
|
|
116
|
+
const getTransferNativeInx = async (from, to, amount) => {
|
|
117
|
+
return web3_js_1.SystemProgram.transfer({
|
|
118
|
+
fromPubkey: from,
|
|
119
|
+
toPubkey: to,
|
|
120
|
+
lamports: amount * web3_js_1.LAMPORTS_PER_SOL, // Convert SOL to lamports
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
exports.getTransferNativeInx = getTransferNativeInx;
|
|
124
|
+
const getTransferNativeTransaction = async (from, to, amount, connection) => {
|
|
125
|
+
const instruction = await (0, exports.getTransferNativeInx)(from.publicKey, to, amount);
|
|
126
|
+
const transaction = await (0, exports.createV0Transaction)(connection, [instruction], [from], from.publicKey);
|
|
127
|
+
return transaction;
|
|
128
|
+
};
|
|
129
|
+
exports.getTransferNativeTransaction = getTransferNativeTransaction;
|
|
130
|
+
const getTransferTokenInx = async (from, to, token, amount, connection) => {
|
|
131
|
+
const inx = [];
|
|
132
|
+
const tokenToSend = new web3_js_1.PublicKey(token.address);
|
|
133
|
+
const { ATA: source, programId, tokenAccount } = await (0, exports.getSureAssociatedTokenAddressAndAccount)(connection, from, tokenToSend);
|
|
134
|
+
const { associatedToken: destination, AtaTokenIx } = await (0, exports.createAtaAndIx)(tokenToSend, to, programId, connection);
|
|
135
|
+
if (!tokenAccount) {
|
|
136
|
+
throw new Error("Token account not found");
|
|
137
|
+
}
|
|
138
|
+
if (AtaTokenIx) {
|
|
139
|
+
inx.push(AtaTokenIx);
|
|
140
|
+
}
|
|
141
|
+
const tInx = (0, spl_token_1.createTransferCheckedInstruction)(source, tokenToSend, destination, from, amount, token.decimals, undefined, programId);
|
|
142
|
+
inx.push(tInx);
|
|
143
|
+
return inx;
|
|
144
|
+
};
|
|
145
|
+
exports.getTransferTokenInx = getTransferTokenInx;
|
|
146
|
+
const getTransferTokenTransaction = async (from, to, token, amount, connection) => {
|
|
147
|
+
const instruction = await (0, exports.getTransferTokenInx)(from.publicKey, to, token, amount, connection);
|
|
148
|
+
const transaction = await (0, exports.createV0Transaction)(connection, instruction, [from], from.publicKey);
|
|
149
|
+
return transaction;
|
|
150
|
+
};
|
|
151
|
+
exports.getTransferTokenTransaction = getTransferTokenTransaction;
|
|
152
|
+
const signAndSendTransaction = async (transaction, connection, signers) => {
|
|
153
|
+
transaction.sign(signers);
|
|
154
|
+
const blockhash = await connection.getLatestBlockhash();
|
|
155
|
+
const res = await (0, transactionSender_1.transactionSenderAndConfirmationWaiter)({ connection, serializedTransaction: Buffer.from(transaction.serialize()), blockhashWithExpiryBlockHeight: { blockhash: blockhash.blockhash, lastValidBlockHeight: blockhash.lastValidBlockHeight } });
|
|
156
|
+
if (!res) {
|
|
157
|
+
throw new Error("Transaction failed to send or confirm");
|
|
158
|
+
}
|
|
159
|
+
return res.transaction.signatures[0];
|
|
160
|
+
};
|
|
161
|
+
exports.signAndSendTransaction = signAndSendTransaction;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
//we will write all the svm utils function here
|
|
2
|
+
|
|
3
|
+
import { Account, createAssociatedTokenAccountIdempotentInstruction, createTransferCheckedInstruction, getAccount, getAssociatedTokenAddress, getAssociatedTokenAddressSync, NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
4
|
+
import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
|
|
5
|
+
import { TokenInfo } from "../types";
|
|
6
|
+
import { transactionSenderAndConfirmationWaiter } from "./transactionSender";
|
|
7
|
+
import { BN } from "bn.js";
|
|
8
|
+
export const createV0Transaction = async (
|
|
9
|
+
connection: Connection,
|
|
10
|
+
inX: TransactionInstruction[],
|
|
11
|
+
signers: Keypair[],
|
|
12
|
+
payerPubKey: PublicKey,
|
|
13
|
+
blockHash?: string
|
|
14
|
+
) => {
|
|
15
|
+
const blockhash =
|
|
16
|
+
blockHash || (await connection.getLatestBlockhash()).blockhash;
|
|
17
|
+
const message = new TransactionMessage({
|
|
18
|
+
payerKey: payerPubKey,
|
|
19
|
+
instructions: inX,
|
|
20
|
+
recentBlockhash: blockhash,
|
|
21
|
+
}).compileToV0Message();
|
|
22
|
+
|
|
23
|
+
const transaction = new VersionedTransaction(message);
|
|
24
|
+
transaction.message.staticAccountKeys;
|
|
25
|
+
if (signers.length < 1) {
|
|
26
|
+
transaction.sign(signers);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return transaction;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const createAtaAndIx = async (
|
|
33
|
+
token: PublicKey,
|
|
34
|
+
ownerPublicKey: PublicKey,
|
|
35
|
+
tokenProgramId: PublicKey,
|
|
36
|
+
connection: Connection,
|
|
37
|
+
) => {
|
|
38
|
+
let AtaTokenIx;
|
|
39
|
+
const associatedToken = getAssociatedTokenAddressSync(
|
|
40
|
+
token,
|
|
41
|
+
ownerPublicKey,
|
|
42
|
+
false,
|
|
43
|
+
tokenProgramId
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const accountExist = await connection.getAccountInfo(associatedToken);
|
|
47
|
+
if (!accountExist) {
|
|
48
|
+
AtaTokenIx = createAssociatedTokenAccountIdempotentInstruction(
|
|
49
|
+
ownerPublicKey,
|
|
50
|
+
associatedToken,
|
|
51
|
+
ownerPublicKey,
|
|
52
|
+
token,
|
|
53
|
+
tokenProgramId
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
AtaTokenIx,
|
|
58
|
+
associatedToken,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
export const getSureAssociatedTokenAddressAndAccount = async (
|
|
62
|
+
connection: Connection,
|
|
63
|
+
token: PublicKey,
|
|
64
|
+
owner: PublicKey,
|
|
65
|
+
) => {
|
|
66
|
+
let ATA: PublicKey;
|
|
67
|
+
let programId: PublicKey;
|
|
68
|
+
let tokenAccount: Account;
|
|
69
|
+
try {
|
|
70
|
+
programId = token.equals(NATIVE_MINT)
|
|
71
|
+
? TOKEN_PROGRAM_ID
|
|
72
|
+
: TOKEN_2022_PROGRAM_ID;
|
|
73
|
+
ATA = getAssociatedTokenAddressSync(token, owner, true, programId);
|
|
74
|
+
tokenAccount = await getAccount(connection, ATA, "confirmed", programId);
|
|
75
|
+
return { ATA, programId, tokenAccount };
|
|
76
|
+
} catch (error) {
|
|
77
|
+
programId = TOKEN_PROGRAM_ID;
|
|
78
|
+
ATA = getAssociatedTokenAddressSync(token, owner, true, programId);
|
|
79
|
+
tokenAccount = await getAccount(connection, ATA, "confirmed", programId);
|
|
80
|
+
return { ATA, programId, tokenAccount };
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const getProgramIdOfToken = async (owner: PublicKey, token: PublicKey, connection: Connection) => {
|
|
85
|
+
if (token.equals(NATIVE_MINT)) {
|
|
86
|
+
return TOKEN_PROGRAM_ID;
|
|
87
|
+
}
|
|
88
|
+
let ATA: PublicKey;
|
|
89
|
+
let programId: PublicKey = TOKEN_PROGRAM_ID
|
|
90
|
+
let tokenAccount: Account;
|
|
91
|
+
try {
|
|
92
|
+
ATA = getAssociatedTokenAddressSync(token, owner, true, programId);
|
|
93
|
+
tokenAccount = await getAccount(connection, ATA, "confirmed", programId);
|
|
94
|
+
return TOKEN_PROGRAM_ID
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return TOKEN_2022_PROGRAM_ID;
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
//get native balance
|
|
104
|
+
|
|
105
|
+
export const getSvmNativeBalance = async (address: PublicKey, connection: Connection,) => {
|
|
106
|
+
const balance = await connection.getBalance(address);
|
|
107
|
+
return { balance: new BN(balance), formatted: balance / LAMPORTS_PER_SOL, decimal: 9 };
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const getTokenBalance = async (address: PublicKey, token: PublicKey, connection: Connection) => {
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Get the balance from the token account
|
|
114
|
+
const tokenAccount = await getTokenAccountAccount(token, address, connection);
|
|
115
|
+
if (!tokenAccount) {
|
|
116
|
+
console.error("Token account not found");
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
const tokenBalance = await connection.getTokenAccountBalance(
|
|
120
|
+
tokenAccount.address
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (!tokenBalance) {
|
|
124
|
+
console.error("Token balance not found");
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return tokenBalance.value;
|
|
129
|
+
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const getTokenAccountAccount = async (token: PublicKey, address: PublicKey, connection: Connection): Promise<Account | null> => {
|
|
136
|
+
try {
|
|
137
|
+
|
|
138
|
+
// Get the associated token account address for the user and the token mint
|
|
139
|
+
const associatedTokenAccount = await getAssociatedTokenAddress(
|
|
140
|
+
token, // The token mint address
|
|
141
|
+
address // The user's public key
|
|
142
|
+
);
|
|
143
|
+
// Fetch the token account information
|
|
144
|
+
const tokenAccount = await getAccount(
|
|
145
|
+
connection,
|
|
146
|
+
associatedTokenAccount
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return tokenAccount;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("Error getting token balance:");
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const getTransferNativeInx = async (from: PublicKey, to: PublicKey, amount: number): Promise<TransactionInstruction> => {
|
|
157
|
+
return SystemProgram.transfer({
|
|
158
|
+
fromPubkey: from,
|
|
159
|
+
toPubkey: to,
|
|
160
|
+
lamports: amount * LAMPORTS_PER_SOL, // Convert SOL to lamports
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const getTransferNativeTransaction = async (from: Keypair, to: PublicKey, amount: number, connection: Connection) => {
|
|
165
|
+
const instruction = await getTransferNativeInx(from.publicKey, to, amount);
|
|
166
|
+
const transaction = await createV0Transaction(connection, [instruction], [from], from.publicKey);
|
|
167
|
+
return transaction;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const getTransferTokenInx = async (from: PublicKey, to: PublicKey, token: TokenInfo, amount: number, connection: Connection): Promise<TransactionInstruction[]> => {
|
|
171
|
+
const inx: TransactionInstruction[] = []
|
|
172
|
+
|
|
173
|
+
const tokenToSend = new PublicKey(token.address);
|
|
174
|
+
const { ATA: source, programId, tokenAccount } = await getSureAssociatedTokenAddressAndAccount(connection, from, tokenToSend);
|
|
175
|
+
const { associatedToken: destination, AtaTokenIx } = await createAtaAndIx(tokenToSend, to, programId, connection);
|
|
176
|
+
|
|
177
|
+
if (!tokenAccount) {
|
|
178
|
+
throw new Error("Token account not found");
|
|
179
|
+
}
|
|
180
|
+
if (AtaTokenIx) {
|
|
181
|
+
inx.push(AtaTokenIx);
|
|
182
|
+
}
|
|
183
|
+
const tInx = createTransferCheckedInstruction(source, tokenToSend, destination, from, amount, token.decimals, undefined, programId)
|
|
184
|
+
inx.push(tInx);
|
|
185
|
+
|
|
186
|
+
return inx;
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
export const getTransferTokenTransaction = async (from: Keypair, to: PublicKey, token: TokenInfo, amount: number, connection: Connection): Promise<VersionedTransaction> => {
|
|
190
|
+
const instruction = await getTransferTokenInx(from.publicKey, to, token, amount, connection);
|
|
191
|
+
const transaction = await createV0Transaction(connection, instruction, [from], from.publicKey);
|
|
192
|
+
return transaction;
|
|
193
|
+
}
|
|
194
|
+
export const signAndSendTransaction = async (transaction: VersionedTransaction, connection: Connection, signers: Keypair[]) => {
|
|
195
|
+
transaction.sign(signers)
|
|
196
|
+
const blockhash = await connection.getLatestBlockhash()
|
|
197
|
+
const res = await transactionSenderAndConfirmationWaiter({ connection, serializedTransaction: Buffer.from(transaction.serialize()), blockhashWithExpiryBlockHeight: { blockhash: blockhash.blockhash, lastValidBlockHeight: blockhash.lastValidBlockHeight } })
|
|
198
|
+
if (!res) {
|
|
199
|
+
throw new Error("Transaction failed to send or confirm");
|
|
200
|
+
}
|
|
201
|
+
return res.transaction.signatures[0];
|
|
202
|
+
|
|
203
|
+
}
|
package/utils/types.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import BN from "bn.js"
|
|
2
|
+
import { EVMVM } from "./evm";
|
|
3
|
+
import { SVMVM } from "./svm";
|
|
4
|
+
|
|
5
|
+
export interface ChainWalletConfig {
|
|
6
|
+
chainId: string | number;
|
|
7
|
+
name: string;
|
|
8
|
+
rpcUrl: string;
|
|
9
|
+
explorerUrl: string;
|
|
10
|
+
|
|
11
|
+
nativeToken: {
|
|
12
|
+
name: string;
|
|
13
|
+
symbol: string;
|
|
14
|
+
decimals: number;
|
|
15
|
+
};
|
|
16
|
+
confirmationNo?: number
|
|
17
|
+
testnet?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TokenInfo {
|
|
21
|
+
address: string;
|
|
22
|
+
name: string;
|
|
23
|
+
symbol: string;
|
|
24
|
+
decimals: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface NFTInfo {
|
|
28
|
+
tokenId: string;
|
|
29
|
+
contractAddress: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
image?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TransactionResult {
|
|
36
|
+
hash: string;
|
|
37
|
+
success: boolean;
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Balance {
|
|
42
|
+
balance: BN;
|
|
43
|
+
formatted: number;
|
|
44
|
+
decimal: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export const SUPPORTED_VM = {
|
|
49
|
+
'EVM': EVMVM,
|
|
50
|
+
'SVM': SVMVM
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
export type vmTypes = keyof typeof SUPPORTED_VM;
|