Skip to content

Commit

Permalink
feat: swap params with stonfi
Browse files Browse the repository at this point in the history
  • Loading branch information
Ho3einWave committed Oct 1, 2024
1 parent 6339538 commit 234c513
Show file tree
Hide file tree
Showing 13 changed files with 306 additions and 9 deletions.
Binary file modified bun.lockb
Binary file not shown.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"tsup": "^8.3.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
"@ton/ton": "^15.0.0",
"typescript": "^5.0.0",
"@ston-fi/sdk": "2.0.0-rc.5"
},
"dependencies": {
"@lifeomic/attempt": "^3.1.0",
Expand Down
11 changes: 11 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
export const defaultBaseUrl = 'https://app.mytonswap.com/api/';
export const TON_ADDRESS = 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c';
export const supportedMintlessTokens = [
'EQD6Z9DHc5Mx-8PI8I4BjGX0d2NhapaRAK12CgstweNoMint',
'EQAJ8uWd7EBqsmpSWaRdf_I-8R8-XHwh3gsNKhy-UrdrPcUo',
];
export const feeWallet = '0:3729d9b29010b40af162fc28a6b0917385b59ea54af3fd411c9f5c91d12c31b5';
export const STON_ROUTER_V1 = 'EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt';
export const PTON_V1 = 'EQCM3B12QK1e4yZSf8GtBRT0aLMNyEsBc_DhVfRRtOEffLez';
export const PTON_V2 = 'EQBnGWMCf3-FZZq1W4IWcWiGAc3PHuZ0_H-7sad2oY00o83S';
export const DEDUST_TRANSFER = 0xf8a7ea5;
export const DEDUST_SWAP = 0xea06185d;
12 changes: 12 additions & 0 deletions src/core/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ import { defaultBaseUrl } from '../constants';
import { Request } from './request';
import { Assets } from '../services/assets/assets.service';
import { Router } from '../services/router/router.service';
import { TonClient } from '@ton/ton';
import { TonApi } from '../services/tonapi/tonapi.service';
import { Swap } from '../services/swap/swap.service';
export class MyTonSwapClient {
public options: { apiKey: string | undefined; baseUrl: string | undefined } | undefined;
public request = new Request(this);
public assets = new Assets(this);
public router = new Router(this);
public tonapi = new TonApi(this);
public swap = new Swap(this);

public tonClient: TonClient;

constructor(options?: MyTonSwapClientOptions) {
this.options = options;
this.tonClient = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: options?.tonCenterApiKey,
});
}
}
8 changes: 6 additions & 2 deletions src/core/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export class Request {
'x-api-key': this.client.options?.apiKey ?? '',
},
method: 'GET',
validateStatus: () => true,
} satisfies AxiosRequestConfig;

const options = defaultsDeep(userOptions, defaultOptions, {
Expand All @@ -28,7 +27,12 @@ export class Request {

const response = await this.faultTolerantRequest<MyTonSwapResponse<T>>(options);
this.handleErrors<T>(response);
const data = this.transformBody(response!.data);
let data;
if (userOptions.baseURL === defaultBaseUrl) {
data = this.transformBody(response!.data);
} else {
data = response!.data as T;
}
return data;
}

Expand Down
5 changes: 3 additions & 2 deletions src/services/router/router.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { toNano } from '@ton/ton';
import { Services } from '../../core/services';
import { BestRoute, Dex } from '../../types/router';

Expand All @@ -15,15 +16,15 @@ export class Router extends Services {
const body = {
token0: inputAssetAddress,
token1: outputAssetAddress,
amount: payAmount,
amount: toNano(payAmount).toString(),
slippage: slippage ?? 'auto',
token0_symbol: 'SDK',
token1_symbol: 'SDK',
init: true,
dex: forceDex,
};
const data = this.client.request.send<BestRoute>({
url: '/swap-process/data/pools',
url: '/swap-process/data/pools-v2',
method: 'POST',
data: body,
});
Expand Down
165 changes: 165 additions & 0 deletions src/services/swap/swap.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { address, Cell, SenderArguments, toNano } from '@ton/ton';
import {
feeWallet,
PTON_V2,
STON_ROUTER_V1,
supportedMintlessTokens,
TON_ADDRESS,
} from '../../constants';
import { Services } from '../../core/services';
import { BestRoute } from '../../types/router';
import { Balance, CustomPayload } from '../../types/swap';
import { DEX, pTON } from '@ston-fi/sdk';

export class Swap extends Services {
/**
* swap
*/
public async swap(userWalletAddress: string, bestRoute: BestRoute) {
switch (bestRoute.selected_pool.dex) {
case 'stonfi':
return await this.createStonSwap(userWalletAddress, bestRoute);
case 'dedust':
break;
default:
break;
}
}

/**
* createStonSwap
*
*/
private async createStonSwap(userWalletAddress: string, bestRoute: BestRoute) {
let jettonData: Balance | undefined;
let swapTxParams: SenderArguments | null = null;
const isMintless = supportedMintlessTokens.includes(bestRoute.selected_pool.token0_address);
if (isMintless) {
jettonData = await this.client.tonapi.getJettonData(
userWalletAddress,
bestRoute.selected_pool.token0_address,
);
}

let customPayload: Cell | undefined;
let stateInit: { code: Cell; data: Cell } | undefined;

if (isMintless && jettonData?.extensions?.includes('custom_payload')) {
const offerJettonCustomPayload = await this.client.tonapi.getCustomPayload(
userWalletAddress,
bestRoute.selected_pool.token0_address,
);

if (!offerJettonCustomPayload) {
throw new Error('Unable to retrieve custom payload. Please try again.');
}
customPayload = Cell.fromBoc(
Buffer.from(offerJettonCustomPayload.custom_payload, 'hex'),
)[0];
const stateInitCell = Cell.fromBoc(
Buffer.from(offerJettonCustomPayload.state_init, 'hex'),
)[0].beginParse();

stateInit = {
code: stateInitCell.loadRef(),
data: stateInitCell.loadRef(),
};
}

const router =
bestRoute.selected_pool.router_address === STON_ROUTER_V1
? this.client.tonClient.open(
DEX.v1.Router.create(bestRoute.selected_pool.router_address),
)
: this.client.tonClient.open(
DEX.v2_1.Router.create(bestRoute.selected_pool.router_address),
);

const pTon =
bestRoute.selected_pool.router_address === STON_ROUTER_V1
? new pTON.v1()
: new pTON.v2_1(PTON_V2);
const gasConstants =
bestRoute.selected_pool.router_address === STON_ROUTER_V1
? DEX.v1.Router.gasConstants
: DEX.v2_1.Router.gasConstants;

console.log(bestRoute.pool_data.route[0]);
if (bestRoute.pool_data.route[0] === TON_ADDRESS) {
swapTxParams = await router.getSwapTonToJettonTxParams({
userWalletAddress: userWalletAddress,
proxyTon: pTon,
offerAmount: bestRoute.pool_data.pay,
askJettonAddress: bestRoute.pool_data.route[1],
minAskAmount: BigInt(bestRoute.pool_data.minimumReceive),
referralAddress: address(feeWallet),
});
} else if (bestRoute.pool_data.route[1] === TON_ADDRESS) {
swapTxParams = await router.getSwapJettonToTonTxParams({
userWalletAddress: userWalletAddress,
offerJettonAddress: bestRoute.pool_data.route[0],
offerAmount: bestRoute.pool_data.pay,
proxyTon: pTon,
jettonCustomPayload: customPayload ? customPayload : undefined,
minAskAmount: BigInt(bestRoute.pool_data.minimumReceive),
referralAddress: feeWallet,
gasAmount: isMintless
? gasConstants.swapJettonToJetton.gasAmount + toNano(0.1)
: undefined,
});
} else {
swapTxParams = await router.getSwapJettonToJettonTxParams({
userWalletAddress: userWalletAddress,
offerJettonAddress: bestRoute.selected_pool.token0_address,
offerAmount: BigInt(bestRoute.pool_data.pay),
askJettonAddress: bestRoute.selected_pool.token1_address,
jettonCustomPayload: customPayload ? customPayload : undefined,

minAskAmount: BigInt(bestRoute.pool_data.minimumReceive),
referralAddress: address(feeWallet),
gasAmount: stateInit
? gasConstants.swapJettonToJetton.gasAmount + toNano(0.1)
: undefined,
});
}
return swapTxParams;
}

private async createDedustSwap(userWalletAddress: string, bestRoute: BestRoute) {
let [asset0, asset1, asset2] = bestRoute.pool_data.route;
let jettonData: Balance | undefined;
let swapTxParams: SenderArguments | null = null;
const isMintless = supportedMintlessTokens.includes(bestRoute.selected_pool.token0_address);
if (isMintless) {
jettonData = await this.client.tonapi.getJettonData(
userWalletAddress,
bestRoute.selected_pool.token0_address,
);
}

let customPayload: Cell | undefined;
let stateInit: { code: Cell; data: Cell } | undefined;

if (isMintless && jettonData?.extensions?.includes('custom_payload')) {
const offerJettonCustomPayload = await this.client.tonapi.getCustomPayload(
userWalletAddress,
bestRoute.selected_pool.token0_address,
);

if (!offerJettonCustomPayload) {
throw new Error('Unable to retrieve custom payload. Please try again.');
}
customPayload = Cell.fromBoc(
Buffer.from(offerJettonCustomPayload.custom_payload, 'hex'),
)[0];
const stateInitCell = Cell.fromBoc(
Buffer.from(offerJettonCustomPayload.state_init, 'hex'),
)[0].beginParse();

stateInit = {
code: stateInitCell.loadRef(),
data: stateInitCell.loadRef(),
};
}
}
}
19 changes: 19 additions & 0 deletions src/services/swap/swap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { beforeAll, beforeEach, expect, test } from 'bun:test';
import { MyTonSwapClient } from '../../core/client';
import { toNano } from '@ton/ton';
let client: MyTonSwapClient;
const userWallet = 'UQAaIQh7jVlOEylI8jM8OI1O-yYGZRqUfJRxuR-K57pskl9I';
const hmstrJetton = 'EQAJ8uWd7EBqsmpSWaRdf_I-8R8-XHwh3gsNKhy-UrdrPcUo';
beforeEach(() => {
client = new MyTonSwapClient();
});

test('it should get data for swap from stonfi', async () => {
const TON = await client.assets.getExactAsset('TON');
const NOT = await client.assets.getExactAsset('NOT');
const bestRoute = await client.router.findBestRoute(TON!.address, NOT!.address, 1, 1, 'stonfi');
const swap = await client.swap.swap(userWallet, bestRoute);
expect(swap).toBeObject();
expect(swap?.value).not.toBeUndefined();
expect(swap?.value).toBeGreaterThan(toNano(1));
});
24 changes: 24 additions & 0 deletions src/services/tonapi/tonapi.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Services } from '../../core/services';
import { Balance, CustomPayload } from '../../types/swap';

export class TonApi extends Services {
public async getJettonData(walletAddr: string, jettonAddress: string) {
const data = await this.client.request.send<Balance>({
baseURL: 'https://tonapi.io/v2',
maxBodyLength: Infinity,
url: `/accounts/${walletAddr}/jettons/${jettonAddress}?supported_extensions=custom_payload`,
});

return data;
}

public async getCustomPayload(walletAddr: string, jettonAddress: string) {
const data = await this.client.request.send<CustomPayload>({
baseURL: 'https://tonapi.io/v2',
maxBodyLength: Infinity,
url: `/jettons/${jettonAddress}/transfer/${walletAddr}/payload`,
});

return data;
}
}
23 changes: 23 additions & 0 deletions src/services/tonapi/tonapi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { beforeAll, beforeEach, expect, test } from 'bun:test';
import { MyTonSwapClient } from '../../core/client';
let client: MyTonSwapClient;
const userWallet = 'UQAaIQh7jVlOEylI8jM8OI1O-yYGZRqUfJRxuR-K57pskl9I';
const hmstrJetton = 'EQAJ8uWd7EBqsmpSWaRdf_I-8R8-XHwh3gsNKhy-UrdrPcUo';
beforeEach(() => {
client = new MyTonSwapClient();
});

test('it should get jetton with custom payload uri', async () => {
const data = await client.tonapi.getJettonData(userWallet, hmstrJetton);
expect(data.jetton.custom_payload_api_uri).not.toBeUndefined();
});

test('it should get fail on random address', async () => {
expect(async () => await client.tonapi.getJettonData(userWallet, 'hmstrJetton')).toThrow();
});

test('it should get custom payload for Hamster Kombat Token', async () => {
const customPayload = await client.tonapi.getCustomPayload(userWallet, hmstrJetton);
expect(customPayload.custom_payload).not.toBeUndefined();
expect(customPayload.state_init).not.toBeUndefined();
});
1 change: 1 addition & 0 deletions src/types/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type MyTonSwapClientOptions = {
apiKey: string;
tonCenterApiKey: string;
baseUrl: string;
};

Expand Down
8 changes: 4 additions & 4 deletions src/types/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export interface SelectedPool {

export interface PoolData {
router_address: string;
pay: number;
receive: number;
pay: string;
receive: string;
priceImpact: number;
minimumReceive: number;
innerMinimumReceive: number;
minimumReceive: string;
innerMinimumReceive: string;
blockchainFee: string;
status: boolean;
message: string;
Expand Down
35 changes: 35 additions & 0 deletions src/types/swap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export interface Balance {
balance: string;
wallet_address: WalletAddress;
jetton: Jetton;
extensions?: string[];
}

export interface WalletAddress {
address: string;
is_scam: boolean;
is_wallet: boolean;
}

export interface Jetton {
address: string;
name: string;
symbol: string;
decimals: number;
image: string;
verification: string;
prices?: Prices;
custom_payload_api_uri?: string;
}
export interface Prices {
USD: number;
}

export interface CustomPayload {
custom_payload: string;
state_init: string;
}
export interface CustomPayload {
custom_payload: string;
state_init: string;
}

0 comments on commit 234c513

Please sign in to comment.