@clonegod/ttd-sol-common 2.0.11 → 2.0.13
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/common/get_wallet_token_account.d.ts +5 -0
- package/dist/common/get_wallet_token_account.js +69 -0
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +1 -0
- package/dist/common/subscribe_account_update.d.ts +4 -0
- package/dist/common/subscribe_account_update.js +22 -0
- package/dist/quote/pricing/token_price_cache.js +15 -26
- package/dist/trade/SolanaTradeAppConfig.d.ts +8 -0
- package/dist/trade/SolanaTradeAppConfig.js +13 -0
- package/dist/trade/index.d.ts +2 -0
- package/dist/trade/index.js +2 -0
- package/dist/trade/tx_builder.d.ts +18 -0
- package/dist/trade/tx_builder.js +71 -0
- package/dist/trade/tx_result_check.js +45 -58
- package/dist/types/index.d.ts +15 -0
- package/package.json +1 -1
- package/src/common/get_wallet_token_account.ts +90 -0
- package/src/common/index.ts +1 -0
- package/src/common/subscribe_account_update.ts +26 -0
- package/src/trade/SolanaTradeAppConfig.ts +25 -0
- package/src/trade/index.ts +3 -1
- package/src/trade/tx_builder.ts +113 -0
- package/src/types/index.ts +19 -0
- package/tsconfig.json +8 -5
- package/dist/quote/types.d.ts +0 -22
- package/dist/quote/types.js +0 -2
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { StandardPoolInfoType, StandardTokenInfoType } from "@clonegod/ttd-core";
|
|
2
|
+
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
|
3
|
+
export declare const get_token_program_id: (token: StandardTokenInfoType) => PublicKey;
|
|
4
|
+
export declare function get_wallet_token_account(wallet_pubkey: string, pool_list: StandardPoolInfoType[]): Promise<Map<string, string>>;
|
|
5
|
+
export declare const create_token_account_if_not_exist: (connection: Connection, owner: Keypair, token_list: StandardTokenInfoType[]) => Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.create_token_account_if_not_exist = exports.get_token_program_id = void 0;
|
|
4
|
+
exports.get_wallet_token_account = get_wallet_token_account;
|
|
5
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
6
|
+
const constants_1 = require("./constants");
|
|
7
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
8
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
9
|
+
const get_token_program_id = (token) => {
|
|
10
|
+
let programId = spl_token_1.TOKEN_PROGRAM_ID;
|
|
11
|
+
if (token.is_token2022) {
|
|
12
|
+
programId = spl_token_1.TOKEN_2022_PROGRAM_ID;
|
|
13
|
+
}
|
|
14
|
+
return programId;
|
|
15
|
+
};
|
|
16
|
+
exports.get_token_program_id = get_token_program_id;
|
|
17
|
+
async function get_wallet_token_account(wallet_pubkey, pool_list) {
|
|
18
|
+
let account_map = new Map();
|
|
19
|
+
for (let { tokenA, tokenB } of pool_list) {
|
|
20
|
+
account_map.set(tokenA.symbol, (await (0, spl_token_1.getAssociatedTokenAddress)(new web3_js_1.PublicKey(tokenA.address), new web3_js_1.PublicKey(wallet_pubkey), false, (0, exports.get_token_program_id)(tokenA))).toBase58());
|
|
21
|
+
account_map.set(tokenB.symbol, (await (0, spl_token_1.getAssociatedTokenAddress)(new web3_js_1.PublicKey(tokenB.address), new web3_js_1.PublicKey(wallet_pubkey), false, (0, exports.get_token_program_id)(tokenB))).toBase58());
|
|
22
|
+
}
|
|
23
|
+
return account_map;
|
|
24
|
+
}
|
|
25
|
+
const create_token_account_if_not_exist = async (connection, owner, token_list) => {
|
|
26
|
+
token_list.forEach(async (token) => {
|
|
27
|
+
var _a;
|
|
28
|
+
if (['SOL', 'WSOL', 'USDC', 'USDT'].includes((_a = token.symbol) === null || _a === void 0 ? void 0 : _a.toUpperCase())) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const mint = new web3_js_1.PublicKey(token.address);
|
|
33
|
+
let ata = await (0, spl_token_1.getAssociatedTokenAddress)(new web3_js_1.PublicKey(mint), owner.publicKey);
|
|
34
|
+
const account_data = await connection.getAccountInfo(ata);
|
|
35
|
+
if (account_data) {
|
|
36
|
+
(0, dist_1.log_info)(`token account already exist, skip!`, {
|
|
37
|
+
owner: owner.publicKey.toBase58(),
|
|
38
|
+
token,
|
|
39
|
+
ata: ata.toBase58()
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
(0, dist_1.log_info)(`token account not exist, create new one!`, {
|
|
44
|
+
owner: owner.publicKey.toBase58(),
|
|
45
|
+
token,
|
|
46
|
+
});
|
|
47
|
+
if (token.is_token2022) {
|
|
48
|
+
ata = await (0, spl_token_1.createAssociatedTokenAccountIdempotent)(connection, owner, mint, owner.publicKey, {
|
|
49
|
+
commitment: constants_1.COMMITMENT_LEVEL.CONFIRMED,
|
|
50
|
+
}, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
ata = await (0, spl_token_1.createAssociatedTokenAccountIdempotent)(connection, owner, mint, owner.publicKey, {
|
|
54
|
+
commitment: constants_1.COMMITMENT_LEVEL.CONFIRMED,
|
|
55
|
+
}, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
56
|
+
}
|
|
57
|
+
(0, dist_1.log_info)(`Create New Token Account`, {
|
|
58
|
+
owner: owner.publicKey.toBase58(),
|
|
59
|
+
token,
|
|
60
|
+
ata: ata.toBase58()
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
(0, dist_1.log_error)(`create_token_account_if_not_exist error!`, err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
exports.create_token_account_if_not_exist = create_token_account_if_not_exist;
|
package/dist/common/index.d.ts
CHANGED
package/dist/common/index.js
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { StandardPoolInfoType } from "@clonegod/ttd-core";
|
|
2
|
+
import { DEX_ID } from "@clonegod/ttd-core/dist";
|
|
3
|
+
import { SolanaPoolAccountUpdateEventData } from "../types";
|
|
4
|
+
export declare function subscribe_account_update(dex_id: DEX_ID, pool_list: StandardPoolInfoType[], callback: (eventData: SolanaPoolAccountUpdateEventData) => void): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.subscribe_account_update = subscribe_account_update;
|
|
4
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
5
|
+
function subscribe_account_update(dex_id, pool_list, callback) {
|
|
6
|
+
const ws_url = process.env.SUBSCIBE_MULTIPLE_PROVIDERS_WS_URL || 'ws://127.0.0.1:10000';
|
|
7
|
+
const ws_client = new dist_1.WebSocketClient(ws_url);
|
|
8
|
+
ws_client.onOpen(() => {
|
|
9
|
+
pool_list.forEach(({ pair, pool_address }, _) => {
|
|
10
|
+
ws_client.send(JSON.stringify({ dex_id, pair, pool_address }));
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
ws_client.onMessage(((eventData) => {
|
|
14
|
+
if (!eventData.data) {
|
|
15
|
+
(0, dist_1.log_warn)(`[SolanaTradeAppConfig] subscribe_pool_events_from_multiple_providers: no data`, eventData);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
callback(eventData);
|
|
19
|
+
}
|
|
20
|
+
}));
|
|
21
|
+
ws_client.connect();
|
|
22
|
+
}
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.TokenPriceCache = void 0;
|
|
13
4
|
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
@@ -17,23 +8,21 @@ class TokenPriceCache {
|
|
|
17
8
|
this.PRICE_CACHE_TIMEOUT_MILLS = parseInt(process.env.PRICE_CACHE_TIMEOUT_MILLS || Number(1000 * 60 * 60 * 1).toString());
|
|
18
9
|
(0, dist_1.log_info)(`代币价格缓存超时时间: ${this.PRICE_CACHE_TIMEOUT_MILLS} ms`);
|
|
19
10
|
}
|
|
20
|
-
getTokenPrice(tokenAddress) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return newPrice;
|
|
36
|
-
});
|
|
11
|
+
async getTokenPrice(tokenAddress) {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
const cachedData = this.tokenPriceCache.get(tokenAddress);
|
|
14
|
+
if (cachedData && (now - cachedData.timestamp) < this.PRICE_CACHE_TIMEOUT_MILLS) {
|
|
15
|
+
(0, dist_1.log_debug)(`use cached token price: ${tokenAddress}, price: ${cachedData.price}`, '');
|
|
16
|
+
return cachedData;
|
|
17
|
+
}
|
|
18
|
+
const priceMap = await (0, dist_1.get_solana_token_price_info)([tokenAddress]);
|
|
19
|
+
const tokenPrice = priceMap.get(tokenAddress);
|
|
20
|
+
if (!tokenPrice || !tokenPrice.price || Number(tokenPrice.price) <= 0) {
|
|
21
|
+
throw new Error(`无法获取代币 ${tokenAddress} 的有效USD价格`);
|
|
22
|
+
}
|
|
23
|
+
const newPrice = { price: tokenPrice.price, timestamp: now };
|
|
24
|
+
this.tokenPriceCache.set(tokenAddress, newPrice);
|
|
25
|
+
return newPrice;
|
|
37
26
|
}
|
|
38
27
|
}
|
|
39
28
|
exports.TokenPriceCache = TokenPriceCache;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AbstractTradeAppConfig } from "@clonegod/ttd-core/dist";
|
|
2
|
+
import { Connection, Keypair } from "@solana/web3.js";
|
|
3
|
+
export declare class SolanaTradeAppConfig extends AbstractTradeAppConfig {
|
|
4
|
+
connection: Connection;
|
|
5
|
+
keypair: Keypair;
|
|
6
|
+
constructor();
|
|
7
|
+
subscribe_wallet_raw_txn_event(): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SolanaTradeAppConfig = void 0;
|
|
4
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
5
|
+
class SolanaTradeAppConfig extends dist_1.AbstractTradeAppConfig {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
}
|
|
9
|
+
subscribe_wallet_raw_txn_event() {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.SolanaTradeAppConfig = SolanaTradeAppConfig;
|
package/dist/trade/index.d.ts
CHANGED
package/dist/trade/index.js
CHANGED
|
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./tx_result_check"), exports);
|
|
18
18
|
__exportStar(require("./tx_result_parse"), exports);
|
|
19
|
+
__exportStar(require("./SolanaTradeAppConfig"), exports);
|
|
20
|
+
__exportStar(require("./tx_builder"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TradeContext } from "@clonegod/ttd-core/dist";
|
|
2
|
+
import { Connection, Keypair, Transaction, TransactionInstruction } from "@solana/web3.js";
|
|
3
|
+
import { SolanaTradeAppConfig } from "./SolanaTradeAppConfig";
|
|
4
|
+
export declare class SolTransactionBuilder {
|
|
5
|
+
appConfig: SolanaTradeAppConfig;
|
|
6
|
+
protected connection: Connection;
|
|
7
|
+
protected keypair: Keypair;
|
|
8
|
+
protected recentBlockhash: string;
|
|
9
|
+
protected recentBlockheight: number;
|
|
10
|
+
constructor(appConfig: SolanaTradeAppConfig);
|
|
11
|
+
init(): Promise<void>;
|
|
12
|
+
private handleBlockUpdateEvent;
|
|
13
|
+
execute(context: TradeContext): Promise<string>;
|
|
14
|
+
getPreInstructions(context: TradeContext): TransactionInstruction[];
|
|
15
|
+
getPostInstructions(context: TradeContext): TransactionInstruction[];
|
|
16
|
+
buildTransactionForSwap(context: TradeContext, swapInstructions: TransactionInstruction[]): Transaction;
|
|
17
|
+
buildTransactionForTip(context: TradeContext, tipAccount: string, tipAmount: number, tipPayer: Keypair): Transaction;
|
|
18
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SolTransactionBuilder = void 0;
|
|
4
|
+
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
5
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
6
|
+
const LAMPORTS_PER_MICRO_LAMPORTS = 1000000;
|
|
7
|
+
class SolTransactionBuilder {
|
|
8
|
+
constructor(appConfig) {
|
|
9
|
+
this.appConfig = appConfig;
|
|
10
|
+
this.connection = appConfig.connection;
|
|
11
|
+
this.keypair = appConfig.keypair;
|
|
12
|
+
this.init();
|
|
13
|
+
}
|
|
14
|
+
async init() {
|
|
15
|
+
this.appConfig.arb_event_subscriber.subscribe_new_block(dist_1.CHAIN_ID.SOLANA, this.handleBlockUpdateEvent.bind(this));
|
|
16
|
+
}
|
|
17
|
+
async handleBlockUpdateEvent(eventData) {
|
|
18
|
+
let blockUpdateEvent = JSON.parse(eventData);
|
|
19
|
+
const { blockHash, blockHeight } = blockUpdateEvent;
|
|
20
|
+
this.recentBlockhash = blockHash;
|
|
21
|
+
this.recentBlockheight = blockHeight;
|
|
22
|
+
}
|
|
23
|
+
execute(context) {
|
|
24
|
+
throw new Error('not implemented!!!');
|
|
25
|
+
}
|
|
26
|
+
getPreInstructions(context) {
|
|
27
|
+
const cu_limit = context.pool_info.cu_limit || 600000;
|
|
28
|
+
const sol_priority_fee_lamports = context.trade_runtime.settings.strategy.sol_priority_fee;
|
|
29
|
+
const priorityFeeMicroLamports = Math.floor((sol_priority_fee_lamports * LAMPORTS_PER_MICRO_LAMPORTS) / cu_limit);
|
|
30
|
+
const computeBudgetInstruction = web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({
|
|
31
|
+
units: cu_limit,
|
|
32
|
+
});
|
|
33
|
+
const priorityFeeInstruction = web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({
|
|
34
|
+
microLamports: priorityFeeMicroLamports,
|
|
35
|
+
});
|
|
36
|
+
return [computeBudgetInstruction, priorityFeeInstruction];
|
|
37
|
+
}
|
|
38
|
+
getPostInstructions(context) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
buildTransactionForSwap(context, swapInstructions) {
|
|
42
|
+
const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset;
|
|
43
|
+
const swapTx = new web3_js_1.Transaction();
|
|
44
|
+
const preInstructions = this.getPreInstructions(context);
|
|
45
|
+
const postInstructions = this.getPostInstructions(context);
|
|
46
|
+
swapTx.add(...preInstructions, ...swapInstructions, ...postInstructions);
|
|
47
|
+
swapTx.recentBlockhash = this.recentBlockhash;
|
|
48
|
+
swapTx.lastValidBlockHeight = this.recentBlockheight + max_block_offset;
|
|
49
|
+
swapTx.feePayer = this.keypair.publicKey;
|
|
50
|
+
swapTx.sign(this.keypair);
|
|
51
|
+
return swapTx;
|
|
52
|
+
}
|
|
53
|
+
buildTransactionForTip(context, tipAccount, tipAmount, tipPayer) {
|
|
54
|
+
const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset;
|
|
55
|
+
if (!tipPayer) {
|
|
56
|
+
tipPayer = this.keypair;
|
|
57
|
+
}
|
|
58
|
+
const tipTx = new web3_js_1.Transaction();
|
|
59
|
+
tipTx.add(web3_js_1.SystemProgram.transfer({
|
|
60
|
+
fromPubkey: tipPayer.publicKey,
|
|
61
|
+
toPubkey: new web3_js_1.PublicKey(tipAccount),
|
|
62
|
+
lamports: tipAmount,
|
|
63
|
+
}));
|
|
64
|
+
tipTx.recentBlockhash = this.recentBlockhash;
|
|
65
|
+
tipTx.lastValidBlockHeight = this.recentBlockheight + max_block_offset;
|
|
66
|
+
tipTx.feePayer = tipPayer.publicKey;
|
|
67
|
+
tipTx.sign(tipPayer);
|
|
68
|
+
return tipTx;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.SolTransactionBuilder = SolTransactionBuilder;
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.TransactionResultChecker = void 0;
|
|
13
4
|
const dist_1 = require("@clonegod/ttd-core/dist");
|
|
@@ -39,11 +30,9 @@ class TransactionResultChecker extends dist_1.AbstractTransactionResultCheck {
|
|
|
39
30
|
this.event_emitter.emit(dist_1.TRANSACTION_STATE_PROCESSING, empty_trade_result);
|
|
40
31
|
return this;
|
|
41
32
|
}
|
|
42
|
-
check() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.on_subscibe_transaction();
|
|
46
|
-
});
|
|
33
|
+
async check() {
|
|
34
|
+
this.check_tx_result_interval();
|
|
35
|
+
this.on_subscibe_transaction();
|
|
47
36
|
}
|
|
48
37
|
on_subscibe_transaction() {
|
|
49
38
|
this.event_emitter.once(dist_1.LOCAL_EVENT_NAME.EVENT_WALLET_TRANSACTION + '#' + this.txid, (messageStr) => {
|
|
@@ -84,57 +73,55 @@ class TransactionResultChecker extends dist_1.AbstractTransactionResultCheck {
|
|
|
84
73
|
}
|
|
85
74
|
});
|
|
86
75
|
}
|
|
87
|
-
check_tx_result_interval() {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
76
|
+
async check_tx_result_interval() {
|
|
77
|
+
const check_start_time = Date.now();
|
|
78
|
+
const check_interval = parseInt(process.env.CHECK_TX_RESULT_INTERVAL_MILLS || '3000');
|
|
79
|
+
const check_timeout = parseInt(process.env.CHECK_TX_RESULT_TIMEOUT_MILLS || '10000');
|
|
80
|
+
(0, dist_1.log_info)(`check transaction start: check_interval=${check_interval}, check_timeout=${check_timeout}`);
|
|
81
|
+
if (check_interval >= check_timeout) {
|
|
82
|
+
(0, dist_1.log_warn)(`check_interval=${check_interval} >= check_timeout=${check_timeout}, check_tx_result_interval failed!`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const intervalId = setInterval(async () => {
|
|
86
|
+
this.check_count += 1;
|
|
87
|
+
(0, dist_1.log_info)(`check transaction: seq=[${this.check_count}], txhash= ${this.txid}, trace_id=${this.trace_id}`);
|
|
88
|
+
try {
|
|
89
|
+
if (Date.now() - check_start_time < check_timeout) {
|
|
90
|
+
let tx_result = await this.connection.getParsedTransaction(this.txid, {
|
|
91
|
+
commitment: common_1.COMMITMENT_LEVEL.CONFIRMED,
|
|
92
|
+
maxSupportedTransactionVersion: 0,
|
|
93
|
+
});
|
|
94
|
+
if ((0, dist_1.isEmpty)(tx_result)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
clearInterval(intervalId);
|
|
98
|
+
let swap_result = this.transactionParser.parse_transaction_data(tx_result, this.pool_info);
|
|
99
|
+
let trade_result = this.map_swap_result_to_tx_result(swap_result);
|
|
100
|
+
if (this.trade_result_already_processed) {
|
|
101
|
+
(0, dist_1.log_warn)(`trade_result_already_processed=${this.trade_result_already_processed}, ignore trade result fetch by interval check!`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if (trade_result.success) {
|
|
105
|
+
this.event_emitter.emit(dist_1.TRANSACTION_STATE_SUCCESS, trade_result);
|
|
114
106
|
}
|
|
115
107
|
else {
|
|
116
|
-
|
|
117
|
-
this.event_emitter.emit(dist_1.TRANSACTION_STATE_SUCCESS, trade_result);
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
this.event_emitter.emit(dist_1.TRANSACTION_STATE_FAILED, trade_result);
|
|
121
|
-
}
|
|
122
|
-
console.log('<====================================================');
|
|
123
|
-
console.dir(trade_result, { depth: 8 });
|
|
124
|
-
console.log('====================================================>');
|
|
108
|
+
this.event_emitter.emit(dist_1.TRANSACTION_STATE_FAILED, trade_result);
|
|
125
109
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
110
|
+
console.log('<====================================================');
|
|
111
|
+
console.dir(trade_result, { depth: 8 });
|
|
112
|
+
console.log('====================================================>');
|
|
129
113
|
}
|
|
130
114
|
}
|
|
131
|
-
|
|
115
|
+
else {
|
|
132
116
|
clearInterval(intervalId);
|
|
133
|
-
(0, dist_1.log_error)('parse transaction error!', err);
|
|
134
117
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
clearInterval(intervalId);
|
|
121
|
+
(0, dist_1.log_error)('parse transaction error!', err);
|
|
122
|
+
}
|
|
123
|
+
}, check_interval);
|
|
124
|
+
this.intervalId = intervalId;
|
|
138
125
|
}
|
|
139
126
|
map_swap_result_to_tx_result(swap_result) {
|
|
140
127
|
let { success, error_code, wallet, block_number, block_time: order_block_time, txid, tx_price, tokenA, tokenB, gas_fee } = swap_result;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -20,3 +20,18 @@ export interface SolanaBlockMetaUpdateEvent {
|
|
|
20
20
|
executedTransactionCount: number;
|
|
21
21
|
entriesCount: number;
|
|
22
22
|
}
|
|
23
|
+
export interface SolanaPoolAccountUpdateEventData {
|
|
24
|
+
type: string;
|
|
25
|
+
provider_id: string;
|
|
26
|
+
event_time: number;
|
|
27
|
+
dexId: string;
|
|
28
|
+
pair: string;
|
|
29
|
+
pool_address: string;
|
|
30
|
+
pool_name: string;
|
|
31
|
+
data: {
|
|
32
|
+
slot: number;
|
|
33
|
+
writeVersion: number;
|
|
34
|
+
transactionHash: string;
|
|
35
|
+
accountData: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { StandardPoolInfoType, StandardTokenInfoType } from "@clonegod/ttd-core";
|
|
2
|
+
import { log_info, log_error } from "@clonegod/ttd-core/dist";
|
|
3
|
+
import { COMMITMENT_LEVEL } from "./constants";
|
|
4
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotent, getAssociatedTokenAddress, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
5
|
+
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const get_token_program_id = (token: StandardTokenInfoType) => {
|
|
9
|
+
let programId: PublicKey = TOKEN_PROGRAM_ID
|
|
10
|
+
if(token.is_token2022) {
|
|
11
|
+
programId = TOKEN_2022_PROGRAM_ID
|
|
12
|
+
}
|
|
13
|
+
return programId
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function get_wallet_token_account(wallet_pubkey:string, pool_list:StandardPoolInfoType[]
|
|
17
|
+
) {
|
|
18
|
+
let account_map = new Map<string,string>()
|
|
19
|
+
|
|
20
|
+
for(let {tokenA, tokenB} of pool_list) {
|
|
21
|
+
account_map.set(tokenA.symbol, (await getAssociatedTokenAddress(new PublicKey(tokenA.address), new PublicKey(wallet_pubkey), false, get_token_program_id(tokenA))).toBase58())
|
|
22
|
+
account_map.set(tokenB.symbol, (await getAssociatedTokenAddress(new PublicKey(tokenB.address), new PublicKey(wallet_pubkey), false, get_token_program_id(tokenB))).toBase58())
|
|
23
|
+
}
|
|
24
|
+
return account_map
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 创建Token Account
|
|
31
|
+
*/
|
|
32
|
+
export const create_token_account_if_not_exist = async (connection:Connection, owner:Keypair, token_list: StandardTokenInfoType[]) => {
|
|
33
|
+
token_list.forEach(async token => {
|
|
34
|
+
if(['SOL','WSOL','USDC','USDT'].includes(token.symbol?.toUpperCase())) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const mint = new PublicKey(token.address)
|
|
40
|
+
// calculate ATA
|
|
41
|
+
let ata = await getAssociatedTokenAddress(new PublicKey(mint), owner.publicKey)
|
|
42
|
+
const account_data = await connection.getAccountInfo(ata)
|
|
43
|
+
if(account_data) {
|
|
44
|
+
log_info(`token account already exist, skip!`, {
|
|
45
|
+
owner: owner.publicKey.toBase58(),
|
|
46
|
+
token,
|
|
47
|
+
ata: ata.toBase58()
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
log_info(`token account not exist, create new one!`, {
|
|
51
|
+
owner: owner.publicKey.toBase58(),
|
|
52
|
+
token,
|
|
53
|
+
});
|
|
54
|
+
if(token.is_token2022) {
|
|
55
|
+
ata = await createAssociatedTokenAccountIdempotent(
|
|
56
|
+
connection,
|
|
57
|
+
owner,
|
|
58
|
+
mint,
|
|
59
|
+
owner.publicKey,
|
|
60
|
+
{
|
|
61
|
+
commitment: COMMITMENT_LEVEL.CONFIRMED,
|
|
62
|
+
},
|
|
63
|
+
TOKEN_2022_PROGRAM_ID,
|
|
64
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
65
|
+
)
|
|
66
|
+
} else {
|
|
67
|
+
ata = await createAssociatedTokenAccountIdempotent(
|
|
68
|
+
connection,
|
|
69
|
+
owner,
|
|
70
|
+
mint,
|
|
71
|
+
owner.publicKey,
|
|
72
|
+
{
|
|
73
|
+
commitment: COMMITMENT_LEVEL.CONFIRMED,
|
|
74
|
+
},
|
|
75
|
+
TOKEN_PROGRAM_ID,
|
|
76
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
log_info(`Create New Token Account`, {
|
|
80
|
+
owner: owner.publicKey.toBase58(),
|
|
81
|
+
token,
|
|
82
|
+
ata: ata.toBase58()
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
log_error(`create_token_account_if_not_exist error!`, err)
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
package/src/common/index.ts
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { StandardPoolInfoType } from "@clonegod/ttd-core";
|
|
2
|
+
import { DEX_ID, log_warn, WebSocketClient } from "@clonegod/ttd-core/dist";
|
|
3
|
+
import { SolanaPoolAccountUpdateEventData } from "../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 集中订阅方式,丛 solana-stream 获取池子变化事件,获得poolRawData
|
|
7
|
+
* - 询价:基于池子数据计算价格
|
|
8
|
+
* - 交易使用:交易中需要池子的最新状态
|
|
9
|
+
*/
|
|
10
|
+
export function subscribe_account_update(dex_id: DEX_ID, pool_list: StandardPoolInfoType[], callback: (eventData: SolanaPoolAccountUpdateEventData) => void) {
|
|
11
|
+
const ws_url = process.env.SUBSCIBE_MULTIPLE_PROVIDERS_WS_URL || 'ws://127.0.0.1:10000';
|
|
12
|
+
const ws_client = new WebSocketClient(ws_url)
|
|
13
|
+
ws_client.onOpen(() => {
|
|
14
|
+
pool_list.forEach(({ pair, pool_address }, _) => {
|
|
15
|
+
ws_client.send(JSON.stringify({ dex_id, pair, pool_address }))
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
ws_client.onMessage(((eventData: SolanaPoolAccountUpdateEventData): void => {
|
|
19
|
+
if (!eventData.data) {
|
|
20
|
+
log_warn(`[SolanaTradeAppConfig] subscribe_pool_events_from_multiple_providers: no data`, eventData);
|
|
21
|
+
} else {
|
|
22
|
+
callback(eventData);
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
ws_client.connect();
|
|
26
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AbstractTradeAppConfig } from "@clonegod/ttd-core/dist";
|
|
2
|
+
import { Connection, Keypair } from "@solana/web3.js";
|
|
3
|
+
|
|
4
|
+
export class SolanaTradeAppConfig extends AbstractTradeAppConfig {
|
|
5
|
+
|
|
6
|
+
// 子类需要设置的属性
|
|
7
|
+
public connection: Connection;
|
|
8
|
+
public keypair: Keypair;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
// 订阅 Wallet 在链上发生的tx data
|
|
16
|
+
subscribe_wallet_raw_txn_event(): void {
|
|
17
|
+
// not used
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
package/src/trade/index.ts
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { CHAIN_ID, TradeContext } from "@clonegod/ttd-core/dist";
|
|
2
|
+
import { SolanaBlockMetaUpdateEvent } from "../types";
|
|
3
|
+
import { ComputeBudgetProgram, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, TransactionInstruction } from "@solana/web3.js";
|
|
4
|
+
import { SolanaTradeAppConfig } from "./SolanaTradeAppConfig";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// There are 10^6 micro-lamports in one lamport
|
|
8
|
+
const LAMPORTS_PER_MICRO_LAMPORTS = 1000000;
|
|
9
|
+
|
|
10
|
+
export class SolTransactionBuilder {
|
|
11
|
+
appConfig: SolanaTradeAppConfig
|
|
12
|
+
protected connection: Connection
|
|
13
|
+
protected keypair: Keypair
|
|
14
|
+
|
|
15
|
+
protected recentBlockhash: string
|
|
16
|
+
protected recentBlockheight: number
|
|
17
|
+
|
|
18
|
+
constructor(appConfig: SolanaTradeAppConfig) {
|
|
19
|
+
this.appConfig = appConfig
|
|
20
|
+
this.connection = appConfig.connection
|
|
21
|
+
this.keypair = appConfig.keypair
|
|
22
|
+
this.init()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async init(): Promise<void> {
|
|
26
|
+
// 订阅区块更新事件
|
|
27
|
+
this.appConfig.arb_event_subscriber.subscribe_new_block(CHAIN_ID.SOLANA, this.handleBlockUpdateEvent.bind(this));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async handleBlockUpdateEvent(eventData: string): Promise<void> {
|
|
31
|
+
let blockUpdateEvent = JSON.parse(eventData) as SolanaBlockMetaUpdateEvent;
|
|
32
|
+
const { blockHash, blockHeight } = blockUpdateEvent;
|
|
33
|
+
this.recentBlockhash = blockHash
|
|
34
|
+
this.recentBlockheight = blockHeight
|
|
35
|
+
// console.log(`handleBlockUpdateEvent, blockHeight: ${blockHeight}, blockHash: ${blockHash}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
execute(context: TradeContext): Promise<string> {
|
|
39
|
+
throw new Error('not implemented!!!')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// pre-instructions
|
|
44
|
+
getPreInstructions(context: TradeContext): TransactionInstruction[] {
|
|
45
|
+
const cu_limit = context.pool_info.cu_limit || 600000
|
|
46
|
+
const sol_priority_fee_lamports = context.trade_runtime.settings.strategy.sol_priority_fee
|
|
47
|
+
const priorityFeeMicroLamports = Math.floor((sol_priority_fee_lamports * LAMPORTS_PER_MICRO_LAMPORTS) / cu_limit)
|
|
48
|
+
// console.log('getPreInstructions', { cu_limit, sol_priority_fee_lamports, priorityFeeMicroLamports, })
|
|
49
|
+
|
|
50
|
+
// set compute budget instruction
|
|
51
|
+
const computeBudgetInstruction = ComputeBudgetProgram.setComputeUnitLimit({
|
|
52
|
+
units: cu_limit,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// set priority fee instruction
|
|
56
|
+
const priorityFeeInstruction = ComputeBudgetProgram.setComputeUnitPrice({
|
|
57
|
+
microLamports: priorityFeeMicroLamports,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return [computeBudgetInstruction, priorityFeeInstruction]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// post-instructions
|
|
65
|
+
getPostInstructions(context: TradeContext): TransactionInstruction[] {
|
|
66
|
+
return []
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
buildTransactionForSwap(context: TradeContext, swapInstructions: TransactionInstruction[]): Transaction {
|
|
71
|
+
const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset
|
|
72
|
+
// console.log(`buildTransactionForSwap, max_block_offset: ${max_block_offset}`)
|
|
73
|
+
|
|
74
|
+
const swapTx = new Transaction();
|
|
75
|
+
|
|
76
|
+
const preInstructions = this.getPreInstructions(context)
|
|
77
|
+
const postInstructions = this.getPostInstructions(context)
|
|
78
|
+
|
|
79
|
+
swapTx.add(...preInstructions, ...swapInstructions, ...postInstructions)
|
|
80
|
+
swapTx.recentBlockhash = this.recentBlockhash
|
|
81
|
+
swapTx.lastValidBlockHeight = this.recentBlockheight + max_block_offset
|
|
82
|
+
swapTx.feePayer = this.keypair.publicKey
|
|
83
|
+
|
|
84
|
+
swapTx.sign(this.keypair)
|
|
85
|
+
|
|
86
|
+
return swapTx
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
buildTransactionForTip(context: TradeContext, tipAccount: string, tipAmount: number, tipPayer: Keypair): Transaction {
|
|
90
|
+
const max_block_offset = context.trade_runtime.settings.strategy.max_block_offset
|
|
91
|
+
|
|
92
|
+
if(!tipPayer) {
|
|
93
|
+
tipPayer = this.keypair
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const tipTx = new Transaction();
|
|
97
|
+
tipTx.add(
|
|
98
|
+
SystemProgram.transfer({
|
|
99
|
+
fromPubkey: tipPayer.publicKey,
|
|
100
|
+
toPubkey: new PublicKey(tipAccount),
|
|
101
|
+
lamports: tipAmount,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
tipTx.recentBlockhash = this.recentBlockhash
|
|
105
|
+
tipTx.lastValidBlockHeight = this.recentBlockheight + max_block_offset
|
|
106
|
+
tipTx.feePayer = tipPayer.publicKey;
|
|
107
|
+
|
|
108
|
+
tipTx.sign(tipPayer)
|
|
109
|
+
|
|
110
|
+
return tipTx
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -36,3 +36,22 @@ export interface SolanaBlockMetaUpdateEvent {
|
|
|
36
36
|
entriesCount: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 推送给询价、交易进程的Account Change Event 结构
|
|
41
|
+
*/
|
|
42
|
+
export interface SolanaPoolAccountUpdateEventData {
|
|
43
|
+
type: string
|
|
44
|
+
provider_id: string
|
|
45
|
+
event_time: number
|
|
46
|
+
dexId: string
|
|
47
|
+
pair: string
|
|
48
|
+
pool_address: string
|
|
49
|
+
pool_name: string
|
|
50
|
+
data: {
|
|
51
|
+
slot: number
|
|
52
|
+
writeVersion: number
|
|
53
|
+
transactionHash: string
|
|
54
|
+
accountData: string
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
package/tsconfig.json
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
"./types",
|
|
6
6
|
],
|
|
7
7
|
"lib": [
|
|
8
|
-
"
|
|
8
|
+
"es2017",
|
|
9
|
+
"dom"
|
|
9
10
|
],
|
|
10
11
|
"module": "CommonJS",
|
|
11
|
-
"target": "
|
|
12
|
+
"target": "es2017",
|
|
12
13
|
"rootDirs": [
|
|
13
14
|
"src"
|
|
14
15
|
],
|
|
@@ -17,6 +18,8 @@
|
|
|
17
18
|
"esModuleInterop": true,
|
|
18
19
|
"skipLibCheck": true,
|
|
19
20
|
"resolveJsonModule": true,
|
|
20
|
-
"removeComments": true
|
|
21
|
-
}
|
|
22
|
-
|
|
21
|
+
"removeComments": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*"],
|
|
24
|
+
"exclude": ["dist"]
|
|
25
|
+
}
|
package/dist/quote/types.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export interface SolanaAccountUpdateEvent {
|
|
2
|
-
recv_ms: number;
|
|
3
|
-
providerId: string;
|
|
4
|
-
slot: number;
|
|
5
|
-
writeVersion: number;
|
|
6
|
-
transactionHash: string;
|
|
7
|
-
dexId: string;
|
|
8
|
-
pair: string;
|
|
9
|
-
poolAddress: string;
|
|
10
|
-
poolName?: string;
|
|
11
|
-
accountData?: Buffer | string;
|
|
12
|
-
}
|
|
13
|
-
export interface SolanaBlockMetaUpdateEvent {
|
|
14
|
-
slot: number;
|
|
15
|
-
blockhash: string;
|
|
16
|
-
blockTime: number;
|
|
17
|
-
BlockHeight: number;
|
|
18
|
-
parentSlot: number;
|
|
19
|
-
parentBlockhash: string;
|
|
20
|
-
executedTransactionCount: number;
|
|
21
|
-
entriesCount: number;
|
|
22
|
-
}
|
package/dist/quote/types.js
DELETED