Skip to content

Commit

Permalink
v4.0.0 - automatic updates / charge fees in sui
Browse files Browse the repository at this point in the history
  • Loading branch information
bonkman22 committed Oct 17, 2024
1 parent 7f54fec commit 7faa92c
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 59 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hop.ag/sdk",
"version": "3.3.6",
"version": "4.0.0",
"description": "Official Hop SDK to access Hop Aggregator",
"type": "module",
"main": "dist/cjs/index.js",
Expand All @@ -18,6 +18,7 @@
"test:tokens": "npx tsx src/tests/tokens.test.ts",
"test:base": "npx tsx src/tests/base.test.ts",
"test:price": "npx tsx src/tests/price.test.ts",
"test:schema": "npx tsx src/tests/schema.test.ts",
"build": "npm run build:esm && npm run build:cjs",
"build:esm": "tsc && echo '{\"type\":\"module\"}' > dist/esm/package.json",
"build:cjs": "tsc --project tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
Expand Down
13 changes: 9 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ const rpc_url = getFullNodeUrl("mainnet");
const hop_api_options: HopApiOptions = {
api_key: "",
fee_bps: 0,
fee_wallet: "0x2",
fee_wallet: "YOUR_SUI_ADDRESS_HERE",
charge_fees_in_sui: true,
};

const sdk = new HopApi(rpc_url, hop_api_options);
```

To use the Hop Aggregator API, please create an api key [here](https://hop.ag) first.
To use the Hop Aggregator API, please create an api key [here](https://t.me/HopAggregator) first.

#### Get a Swap Quote

Expand All @@ -41,9 +42,9 @@ Call this when a user clicks trade and wants to execute a transaction.
```typescript
const tx = await sdk.fetchTx({
trade: quote.trade,
sui_address: "0x123",
sui_address: "VALID_SUI_ADDRESS_HERE",

gas_budget: 2e8, // optional default is 2e8
gas_budget: 0.03e9, // optional default is 0.03 SUI
max_slippage_bps: 100, // optional default is 1%

return_output_coin_argument: false, // toggle to use the output coin in a ptb
Expand All @@ -58,6 +59,10 @@ endpoint returns a curated list - with ordering - for your application.
const tokens = await sdk.fetchTokens();
```

#### Automatic Updates
As soon as new liquidity sources become available, your
SDK will automatically aggregate them, without anything required on your end.

#### Attribution

Please link to and/or mention `Powered by Hop` if you are using this SDK.
2 changes: 2 additions & 0 deletions src/sdk/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { fetchPrice, GetPriceParams, GetPriceResponse } from "./routes/price.js"
export interface HopApiOptions {
api_key: string;
fee_bps: number; // fee to charge in bps (50% split with Hop / max fee of 5%)
charge_fees_in_sui?: boolean,

fee_wallet?: string; // sui address
hop_server_url?: string;
}
Expand Down
1 change: 0 additions & 1 deletion src/sdk/routes/price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export interface GetPriceResponse {
price_usd: number; // returns usd per token

sui_price: number; // returns usdc per token

}

export async function fetchPrice(
Expand Down
22 changes: 17 additions & 5 deletions src/sdk/routes/quote.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HopApi } from "../api.js";
import { swapAPIResponseSchema } from "../types/api.js";
import { Trade } from "../types/trade.js";
import { getAmountOutWithCommission, makeAPIRequest } from "../util.js";
import { getAmountOutWithCommission, isSuiType, makeAPIRequest } from "../util.js";

export interface GetQuoteParams {
token_in: string;
Expand All @@ -28,18 +28,30 @@ export async function fetchQuote(
token_out: params.token_out,
amount_in: params.amount_in.toString(),
use_alpha_router: client.use_v2,

api_fee_bps: client.options.fee_bps,
charge_fees_in_sui: client.options.charge_fees_in_sui,
},
method: "post",
},
responseSchema: swapAPIResponseSchema,
});

if (response?.trade) {
return {
amount_out_with_fee: getAmountOutWithCommission(
let amount_out_with_fee;

if(client.options.charge_fees_in_sui && isSuiType(params.token_in)) {
// fee already charged
amount_out_with_fee = response.trade.amount_out.amount;
} else {
amount_out_with_fee = getAmountOutWithCommission(
response.trade.amount_out.amount,
client.options.fee_bps,
),
client.options.fee_bps
);
}

return {
amount_out_with_fee,
trade: response.trade,
};
}
Expand Down
13 changes: 11 additions & 2 deletions src/sdk/routes/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export async function fetchTx(
user_input_coins = await fetchCoins(
client,
params.sui_address,
params.trade.amount_in.token,
params.trade.amount_in.token
);
if (user_input_coins.length == 0) {
throw new Error(
Expand All @@ -113,6 +113,7 @@ export async function fetchTx(
client,
params.sui_address,
"0x2::sui::SUI",
60
);
gas_coins = fetched_gas_coins.filter((struct) => Number(struct.amount) > 0).map((struct) => struct.object_id);
} else {
Expand Down Expand Up @@ -143,6 +144,7 @@ export async function fetchTx(

let input_coin_argument = undefined;
let input_coin_argument_nested = undefined;
let input_coin_argument_input = undefined;

// @ts-expect-error
if(params.input_coin_argument?.$kind === "Result" || params.input_coin_argument?.Result) {
Expand All @@ -152,6 +154,10 @@ export async function fetchTx(
} else if(params.input_coin_argument?.$kind === "NestedResult" || params.input_coin_argument?.NestedResult) {
// @ts-expect-error
input_coin_argument_nested = params?.input_coin_argument?.NestedResult;
// @ts-expect-error
} else if(params.input_coin_argument?.$kind === "Input" || params.input_coin_argument?.Input) {
// @ts-expect-error
input_coin_argument_input = params?.input_coin_argument?.Input;
}

let base_transaction = undefined;
Expand All @@ -172,17 +178,20 @@ export async function fetchTx(
user_input_coins,
gas_coins,

gas_budget: params.gas_budget ?? 2e8,
gas_budget: params.gas_budget ?? 0.03e9,
max_slippage_bps: params.max_slippage_bps,

api_fee_wallet: client.options.fee_wallet,
api_fee_bps: client.options.fee_bps,
charge_fees_in_sui: client.options.charge_fees_in_sui,

sponsored: params.sponsored,
base_transaction,

input_coin_argument,
input_coin_argument_nested,
input_coin_argument_input,

return_output_coin_argument: !!params.return_output_coin_argument,
},
});
Expand Down
23 changes: 15 additions & 8 deletions src/sdk/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,40 @@ export const builderRequestSchema = z.object({
amount: z.string(),
}),
),

sponsored: z.optional(z.boolean()),
gas_coins: z.array(coinIdSchema),

gas_budget: z.number(),

max_slippage_bps: z.optional(z.number()),

api_fee_bps: z.optional(z.number()),
api_fee_wallet: z.optional(z.string()),
charge_fees_in_sui: z.optional(z.boolean()),

base_transaction: z.optional(z.string()),
input_coin_argument: z.optional(z.number()),
input_coin_argument_nested: z.optional(z.array(z.number()).length(2)),
input_coin_argument_input: z.optional(z.number()),

return_output_coin_argument: z.optional(z.boolean())
});
}).passthrough();

export type BuilderRequest = z.infer<typeof builderRequestSchema>;

export const compileRequestSchema = z.object({
trade: tradeSchema,
builder_request: builderRequestSchema,
trade: tradeSchema.passthrough(),
builder_request: builderRequestSchema.passthrough(),
});

export type CompileRequest = z.infer<typeof compileRequestSchema>;

export const swapAPIResponseSchema = z.object({
total_tests: z.number(),
errors: z.number(),
trade: tradeSchema.nullable(),
});
trade: tradeSchema.passthrough().nullable(),
}).passthrough();

export type SwapAPIResponse = z.infer<typeof swapAPIResponseSchema>;

Expand All @@ -61,11 +68,11 @@ export const tokensResponseSchema = z.object({
icon_url: z.string(),
decimals: z.number(),
token_order: z.nullable(z.number())
}))
})
}).passthrough())
}).passthrough();

export const priceResponseSchema = z.object({
coin_type: z.string(),
price_sui: z.number(),
sui_price: z.number()
});
}).passthrough();
25 changes: 14 additions & 11 deletions src/sdk/types/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@ export enum SuiExchange {
SUISWAP = "SUISWAP",
}

const poolExtraSchema = z.union([
const suiExchangeSchema = z.nativeEnum(SuiExchange).or(z.string());

export const poolExtraSchema = z.union([
z.object({
AFTERMATH: z.object({
lp_coin_type: z.string(),
}),
}).passthrough(),
}),
z.object({
DEEPBOOK: z.object({
pool_type: z.string(),
lot_size: z.coerce.bigint(),
min_size: z.coerce.bigint()
}),
}).passthrough(),
}),
z.object({
TURBOS: z.object({
Expand All @@ -31,37 +33,38 @@ const poolExtraSchema = z.union([
fee_type: z.string(),
tick_spacing: z.number(),
tick_current_index: z.number(),
}),
}).passthrough(),
}),
z.object({
CETUS: z.object({
coin_type_a: z.string(),
coin_type_b: z.string(),
}),
}).passthrough(),
}),
z.object({
FLOWX: z.object({
is_v3: z.boolean(),
fee_rate: z.number().nullish(),
})
}).passthrough()
}),
z.object({
KRIYA: z.object({
is_v3: z.boolean()
})
})
}).passthrough()
}),
z.object({}).passthrough()
]);

export type PoolExtra = z.infer<typeof poolExtraSchema>;

const tradePoolSchema = z.object({
object_id: z.string(),
initial_shared_version: z.number().nullable(),
sui_exchange: z.nativeEnum(SuiExchange),
sui_exchange: suiExchangeSchema,
tokens: z.array(z.string()).nonempty(),
is_active: z.boolean(),
extra: poolExtraSchema.nullable(),
});
}).passthrough();

export type TradePool = z.infer<typeof tradePoolSchema>;

Expand All @@ -84,6 +87,6 @@ export const tradeSchema = z.object({
edges: z.record(z.array(z.string())),
amount_in: tokenAmountSchema,
amount_out: tokenAmountSchema,
});
}).passthrough();

export type Trade = z.infer<typeof tradeSchema>;
9 changes: 8 additions & 1 deletion src/sdk/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fetch from "cross-fetch";
import { z } from "zod";
import { API_SERVER_PREFIX, FEE_DENOMINATOR } from "./constants.js";
import { normalizeStructTag } from "@mysten/sui/utils";

export interface RequestParams {
hop_server_url?: string;
Expand All @@ -26,7 +27,7 @@ export async function makeAPIRequest<O>({
body: JSON.stringify(
{
...options.data,
api_key: options.api_key,
api_key: options.api_key
},
(_, v) => {
const isBigIntString = typeof v === 'string' && /^\d+n$/.test(v);
Expand Down Expand Up @@ -73,3 +74,9 @@ export function getAmountOutWithCommission(
(amount_out * (FEE_DENOMINATOR - BigInt(fee_bps))) / BigInt(FEE_DENOMINATOR)
);
}

const NORMALIZED_SUI_COIN_TYPE = normalizeStructTag("0x2::sui::SUI");

export function isSuiType(coin_type: string) {
return NORMALIZED_SUI_COIN_TYPE == normalizeStructTag(coin_type);
}
2 changes: 1 addition & 1 deletion src/tests/price.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async function priceTest() {
const api = new HopApi(getFullnodeUrl("mainnet"), {
api_key: "",
fee_bps: 0,
// hop_server_url: "http://localhost:3002/api/v2"
hop_server_url: "http://localhost:3002/api/v2"
});

const result = await api.fetchPrice({
Expand Down
5 changes: 2 additions & 3 deletions src/tests/quote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ async function quoteTest() {
const api = new HopApi(getFullnodeUrl("mainnet"), {
api_key: "",
fee_bps: 0,
hop_server_url: ""
hop_server_url: "http://localhost:3002/api/v2",
});

const result = await api.fetchQuote({
// @ts-ignore
amount_in: 1_000_000_000n,
token_in: "0x2::sui::SUI",
token_out:
"0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN",
token_out: "0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN",
});

console.log("result", result);
Expand Down
22 changes: 22 additions & 0 deletions src/tests/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import { z } from "zod";
import { poolExtraSchema } from "../sdk/types/trade";

// @ts-ignore
async function quoteTest() {
const result = poolExtraSchema.parse({
hi:
{
b: 1,
t: true,
c: "yolo!",
x: {
"d": "hello"
}
}
});

console.log(result);
}

quoteTest();
Loading

0 comments on commit 7faa92c

Please sign in to comment.