@sun-protocol/sun-sdk-protocols 0.1.0-beta.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/README.md +30 -0
- package/dist/chunk-5XDX5563.js +99 -0
- package/dist/chunk-5XDX5563.js.map +1 -0
- package/dist/chunk-C2YCVBPI.js +212 -0
- package/dist/chunk-C2YCVBPI.js.map +1 -0
- package/dist/chunk-ECCGOXNA.js +1317 -0
- package/dist/chunk-ECCGOXNA.js.map +1 -0
- package/dist/chunk-HD2S3Q7L.js +746 -0
- package/dist/chunk-HD2S3Q7L.js.map +1 -0
- package/dist/chunk-L5TZCDPH.js +561 -0
- package/dist/chunk-L5TZCDPH.js.map +1 -0
- package/dist/chunk-OM4OW4KB.js +224 -0
- package/dist/chunk-OM4OW4KB.js.map +1 -0
- package/dist/chunk-PEGREDLT.js +935 -0
- package/dist/chunk-PEGREDLT.js.map +1 -0
- package/dist/chunk-TSHXXRH2.js +103 -0
- package/dist/chunk-TSHXXRH2.js.map +1 -0
- package/dist/chunk-VFVKMC4H.js +521 -0
- package/dist/chunk-VFVKMC4H.js.map +1 -0
- package/dist/chunk-YMGZDXI4.js +1209 -0
- package/dist/chunk-YMGZDXI4.js.map +1 -0
- package/dist/common-8VQQ0FSU.d.cts +146 -0
- package/dist/common-8VQQ0FSU.d.ts +146 -0
- package/dist/execute-BNKh6dT6.d.cts +187 -0
- package/dist/execute-CVhcKZxF.d.ts +187 -0
- package/dist/index.cjs +5619 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +139 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.js +200 -0
- package/dist/index.js.map +1 -0
- package/dist/intents-BSXq1HvW.d.cts +200 -0
- package/dist/intents-CwOttD75.d.ts +200 -0
- package/dist/liquidity/index.cjs +2441 -0
- package/dist/liquidity/index.cjs.map +1 -0
- package/dist/liquidity/index.d.cts +83 -0
- package/dist/liquidity/index.d.ts +83 -0
- package/dist/liquidity/index.js +32 -0
- package/dist/liquidity/index.js.map +1 -0
- package/dist/positions/index.cjs +1544 -0
- package/dist/positions/index.cjs.map +1 -0
- package/dist/positions/index.d.cts +159 -0
- package/dist/positions/index.d.ts +159 -0
- package/dist/positions/index.js +25 -0
- package/dist/positions/index.js.map +1 -0
- package/dist/pump/index.cjs +1178 -0
- package/dist/pump/index.cjs.map +1 -0
- package/dist/pump/index.d.cts +16 -0
- package/dist/pump/index.d.ts +16 -0
- package/dist/pump/index.js +29 -0
- package/dist/pump/index.js.map +1 -0
- package/dist/swap/index.cjs +2259 -0
- package/dist/swap/index.cjs.map +1 -0
- package/dist/swap/index.d.cts +33 -0
- package/dist/swap/index.d.ts +33 -0
- package/dist/swap/index.js +61 -0
- package/dist/swap/index.js.map +1 -0
- package/dist/types-BTPdCC7k.d.cts +160 -0
- package/dist/types-BfrouFmO.d.ts +160 -0
- package/package.json +94 -0
|
@@ -0,0 +1,1317 @@
|
|
|
1
|
+
import {
|
|
2
|
+
planRouterPermit2Authorization
|
|
3
|
+
} from "./chunk-OM4OW4KB.js";
|
|
4
|
+
import {
|
|
5
|
+
assertUsdtTransfer
|
|
6
|
+
} from "./chunk-5XDX5563.js";
|
|
7
|
+
import {
|
|
8
|
+
normalizeTronAddress,
|
|
9
|
+
planApproval,
|
|
10
|
+
readRequiredContract
|
|
11
|
+
} from "./chunk-C2YCVBPI.js";
|
|
12
|
+
import {
|
|
13
|
+
createDeferredV2SwapAction,
|
|
14
|
+
createV2SwapExecutor
|
|
15
|
+
} from "./chunk-TSHXXRH2.js";
|
|
16
|
+
import {
|
|
17
|
+
applySlippageMax,
|
|
18
|
+
applySlippageMin,
|
|
19
|
+
attachApprovalIntent,
|
|
20
|
+
createContextExecutor,
|
|
21
|
+
createProtocolsContext,
|
|
22
|
+
executePlanAction,
|
|
23
|
+
executePlanTransactions,
|
|
24
|
+
executeTransactionPlan,
|
|
25
|
+
isTransactionPlan,
|
|
26
|
+
nextPlanId,
|
|
27
|
+
normalizeBaseUnitAmount,
|
|
28
|
+
normalizeFeeLimit,
|
|
29
|
+
normalizeRecipient,
|
|
30
|
+
normalizeTransactionValidForSeconds,
|
|
31
|
+
requireRuntime,
|
|
32
|
+
requireWalletSnapshot,
|
|
33
|
+
resolveProtocolContract,
|
|
34
|
+
resolveTransactionDeadline,
|
|
35
|
+
slippageToBips
|
|
36
|
+
} from "./chunk-VFVKMC4H.js";
|
|
37
|
+
|
|
38
|
+
// src/swap/intents.ts
|
|
39
|
+
import {
|
|
40
|
+
SDKError
|
|
41
|
+
} from "@sun-protocol/sun-sdk-core";
|
|
42
|
+
var UNIVERSAL_ROUTER_PROTOCOL_ID = "universal-router";
|
|
43
|
+
var PSM_PROTOCOL_ID = "psm";
|
|
44
|
+
var WTRX_PROTOCOL_ID = "wtrx";
|
|
45
|
+
function swapIntentScope(protocol, operation) {
|
|
46
|
+
return {
|
|
47
|
+
domain: "swap",
|
|
48
|
+
protocol,
|
|
49
|
+
...operation === void 0 ? {} : { operation }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function routerRouteSymbols(route) {
|
|
53
|
+
const symbols = route.symbols;
|
|
54
|
+
if (!Array.isArray(symbols) || symbols.length < 2 || symbols.some((symbol) => typeof symbol !== "string" || symbol.length === 0)) {
|
|
55
|
+
throw new SDKError("SDK_API_ERROR", "Selected Router route is missing token symbols");
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
from: symbols[0],
|
|
59
|
+
to: symbols[symbols.length - 1]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/swap/psm.ts
|
|
64
|
+
import {
|
|
65
|
+
buildPsmBuyGemAction,
|
|
66
|
+
buildPsmInfoRead,
|
|
67
|
+
buildPsmSellGemAction,
|
|
68
|
+
createPsmSnapshot,
|
|
69
|
+
normalizePsmInfoResult,
|
|
70
|
+
quotePsm
|
|
71
|
+
} from "@sun-protocol/sun-sdk-psm";
|
|
72
|
+
import {
|
|
73
|
+
SDKError as SDKError3,
|
|
74
|
+
assertTronAddress,
|
|
75
|
+
formatUnits
|
|
76
|
+
} from "@sun-protocol/sun-sdk-core";
|
|
77
|
+
|
|
78
|
+
// src/swap/fee-limit.ts
|
|
79
|
+
var DEFAULT_WTRX_SWAP_FEE_LIMIT = 1e9;
|
|
80
|
+
var DEFAULT_UNIVERSAL_ROUTER_SWAP_FEE_LIMIT = 5e8;
|
|
81
|
+
var DEFAULT_PSM_SWAP_FEE_LIMIT = 25e7;
|
|
82
|
+
var DEFAULT_PSM_APPROVAL_FEE_LIMIT = 1e9;
|
|
83
|
+
|
|
84
|
+
// src/swap/workflow.ts
|
|
85
|
+
import { SDKError as SDKError2 } from "@sun-protocol/sun-sdk-core";
|
|
86
|
+
function assertSwapPlan(plan, protocols, clientName) {
|
|
87
|
+
const protocol = plan.metadata.protocol;
|
|
88
|
+
if (plan.kind !== "swap" || typeof protocol !== "string" || !protocols.includes(protocol) || plan.transactions.length === 0) {
|
|
89
|
+
throw new SDKError2("SDK_INVALID_ARGUMENT", `Expected a ${clientName} swap plan`, {
|
|
90
|
+
planId: plan.id,
|
|
91
|
+
kind: plan.kind,
|
|
92
|
+
protocol,
|
|
93
|
+
transactionCount: plan.transactions.length
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/swap/psm.ts
|
|
99
|
+
function normalizePsmApprovalMode(value) {
|
|
100
|
+
if (value === void 0) return "auto";
|
|
101
|
+
if (value === "auto" || value === "approve" || value === "none") return value;
|
|
102
|
+
throw new SDKError3("SDK_INVALID_ARGUMENT", "unsupported PSM approval mode", {
|
|
103
|
+
approval: value
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function resolvePsmContracts(context, overrides) {
|
|
107
|
+
return {
|
|
108
|
+
psmUsdt: resolveProtocolContract(context, overrides, "psmUsdt"),
|
|
109
|
+
psmUsdd: resolveProtocolContract(context, overrides, "psmUsdd"),
|
|
110
|
+
psm: resolveProtocolContract(context, overrides, "psm"),
|
|
111
|
+
psmGemJoin: resolveProtocolContract(context, overrides, "psmGemJoin"),
|
|
112
|
+
psmInfoReader: resolveProtocolContract(context, overrides, "psmInfoReader")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function invalidAmountShape(mode) {
|
|
116
|
+
throw new SDKError3(
|
|
117
|
+
"SDK_INVALID_ARGUMENT",
|
|
118
|
+
mode === "exact-in" ? "exact-in PSM swap requires only amountIn" : "exact-out PSM swap requires only amountOut",
|
|
119
|
+
{ mode }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
function normalizeSpecifiedAmount(value, label) {
|
|
123
|
+
if (typeof value !== "string") {
|
|
124
|
+
throw new SDKError3("SDK_INVALID_ARGUMENT", `${label} must be a string`, {
|
|
125
|
+
[label]: value
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return normalizeBaseUnitAmount(value, label);
|
|
129
|
+
}
|
|
130
|
+
function normalizeQuoteInput(context, input) {
|
|
131
|
+
const mode = input.mode ?? "exact-in";
|
|
132
|
+
let specifiedAmount;
|
|
133
|
+
if (mode === "exact-in") {
|
|
134
|
+
if (input.amountIn === void 0 || input.amountOut !== void 0) return invalidAmountShape(mode);
|
|
135
|
+
specifiedAmount = normalizeSpecifiedAmount(input.amountIn, "amountIn");
|
|
136
|
+
} else if (mode === "exact-out") {
|
|
137
|
+
if (input.amountOut === void 0 || input.amountIn !== void 0) return invalidAmountShape(mode);
|
|
138
|
+
specifiedAmount = normalizeSpecifiedAmount(input.amountOut, "amountOut");
|
|
139
|
+
} else {
|
|
140
|
+
throw new SDKError3("SDK_INVALID_ARGUMENT", "unsupported PSM quote mode", { mode });
|
|
141
|
+
}
|
|
142
|
+
const tokenIn = assertTronAddress(input.tokenIn, "tokenIn");
|
|
143
|
+
const tokenOut = assertTronAddress(input.tokenOut, "tokenOut");
|
|
144
|
+
const { psmUsdt, psmUsdd } = resolvePsmContracts(context, input.contracts);
|
|
145
|
+
let direction;
|
|
146
|
+
if (tokenIn === psmUsdt && tokenOut === psmUsdd) {
|
|
147
|
+
direction = "usdt-to-usdd";
|
|
148
|
+
} else if (tokenIn === psmUsdd && tokenOut === psmUsdt) {
|
|
149
|
+
direction = "usdd-to-usdt";
|
|
150
|
+
} else {
|
|
151
|
+
throw new SDKError3("SDK_INVALID_ARGUMENT", "PSM swap only supports its configured USDT/USDD pair", {
|
|
152
|
+
tokenIn,
|
|
153
|
+
tokenOut,
|
|
154
|
+
psmUsdt,
|
|
155
|
+
psmUsdd
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return { mode, direction, specifiedAmount, tokenIn, tokenOut };
|
|
159
|
+
}
|
|
160
|
+
async function readPsm(context, input = {}) {
|
|
161
|
+
const contracts = resolvePsmContracts(context, input.contracts);
|
|
162
|
+
const result = await readRequiredContract(
|
|
163
|
+
context,
|
|
164
|
+
buildPsmInfoRead({
|
|
165
|
+
network: context.network,
|
|
166
|
+
psm: contracts.psm,
|
|
167
|
+
infoReader: contracts.psmInfoReader
|
|
168
|
+
}),
|
|
169
|
+
"PSM info read"
|
|
170
|
+
);
|
|
171
|
+
return createPsmSnapshot(normalizePsmInfoResult(result));
|
|
172
|
+
}
|
|
173
|
+
async function quotePsmSwap(context, input) {
|
|
174
|
+
const normalized = normalizeQuoteInput(context, input);
|
|
175
|
+
const snapshot = await readPsm(context, input);
|
|
176
|
+
return {
|
|
177
|
+
...quotePsm({
|
|
178
|
+
mode: normalized.mode,
|
|
179
|
+
direction: normalized.direction,
|
|
180
|
+
specifiedAmount: normalized.specifiedAmount,
|
|
181
|
+
snapshot
|
|
182
|
+
}),
|
|
183
|
+
kind: "psm",
|
|
184
|
+
source: "psm",
|
|
185
|
+
tokenIn: normalized.tokenIn,
|
|
186
|
+
tokenOut: normalized.tokenOut
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function planPsmSwap(context, input) {
|
|
190
|
+
const { address: owner } = await requireWalletSnapshot(context, "PSM swap plan");
|
|
191
|
+
const feeLimit = normalizeFeeLimit(input.feeLimit, DEFAULT_PSM_SWAP_FEE_LIMIT);
|
|
192
|
+
const quote = await quotePsmSwap(context, input);
|
|
193
|
+
const recipient = input.recipient !== void 0 ? assertTronAddress(input.recipient, "PSM recipient") : owner;
|
|
194
|
+
const contracts = resolvePsmContracts(context, input.contracts);
|
|
195
|
+
const tokenIn = quote.direction === "usdt-to-usdd" ? contracts.psmUsdt : contracts.psmUsdd;
|
|
196
|
+
const spender = quote.direction === "usdt-to-usdd" ? contracts.psmGemJoin : contracts.psm;
|
|
197
|
+
const approvalMode = normalizePsmApprovalMode(input.approval);
|
|
198
|
+
const approval = await planApproval(context, {
|
|
199
|
+
asset: { type: "token", token: tokenIn },
|
|
200
|
+
owner,
|
|
201
|
+
spender,
|
|
202
|
+
amount: quote.amountIn
|
|
203
|
+
}, {
|
|
204
|
+
mode: approvalMode,
|
|
205
|
+
amount: "max",
|
|
206
|
+
feeLimit: DEFAULT_PSM_APPROVAL_FEE_LIMIT
|
|
207
|
+
});
|
|
208
|
+
const inputSymbol = quote.direction === "usdt-to-usdd" ? "USDT" : "USDD";
|
|
209
|
+
const outputSymbol = quote.direction === "usdt-to-usdd" ? "USDD" : "USDT";
|
|
210
|
+
const inputDecimals = quote.direction === "usdt-to-usdd" ? 6 : 18;
|
|
211
|
+
const outputDecimals = quote.direction === "usdt-to-usdd" ? 18 : 6;
|
|
212
|
+
const intentScope = swapIntentScope(PSM_PROTOCOL_ID, quote.direction);
|
|
213
|
+
const planId = nextPlanId(context, "swap-psm");
|
|
214
|
+
const actionOptions = {
|
|
215
|
+
network: context.network,
|
|
216
|
+
psm: contracts.psm,
|
|
217
|
+
recipient,
|
|
218
|
+
gemAmount: quote.gemAmount,
|
|
219
|
+
feeLimit,
|
|
220
|
+
id: `${planId}-swap`
|
|
221
|
+
};
|
|
222
|
+
const transactionAction = quote.direction === "usdt-to-usdd" ? buildPsmSellGemAction(actionOptions) : buildPsmBuyGemAction(actionOptions);
|
|
223
|
+
const transaction = {
|
|
224
|
+
...transactionAction,
|
|
225
|
+
intent: {
|
|
226
|
+
kind: "swap",
|
|
227
|
+
scope: intentScope,
|
|
228
|
+
from: { value: formatUnits(BigInt(quote.amountIn), inputDecimals), symbol: inputSymbol },
|
|
229
|
+
to: { value: formatUnits(BigInt(quote.amountOut), outputDecimals), symbol: outputSymbol },
|
|
230
|
+
...input.recipient === void 0 ? {} : { recipient }
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
id: planId,
|
|
235
|
+
kind: "swap",
|
|
236
|
+
network: context.network,
|
|
237
|
+
owner,
|
|
238
|
+
summary: {
|
|
239
|
+
title: "Swap with PSM",
|
|
240
|
+
lines: [
|
|
241
|
+
`Spend ${quote.amountIn} token units`,
|
|
242
|
+
`Receive ${quote.amountOut} token units`
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
approvals: attachApprovalIntent(approval.actions, intentScope, inputSymbol),
|
|
246
|
+
transactions: [transaction],
|
|
247
|
+
metadata: {
|
|
248
|
+
protocol: "psm",
|
|
249
|
+
mode: quote.mode,
|
|
250
|
+
direction: quote.direction,
|
|
251
|
+
amountIn: quote.amountIn,
|
|
252
|
+
amountOut: quote.amountOut,
|
|
253
|
+
specifiedAmount: quote.specifiedAmount,
|
|
254
|
+
gemAmount: quote.gemAmount,
|
|
255
|
+
feeAmount: quote.feeAmount,
|
|
256
|
+
feeRate: quote.feeRate,
|
|
257
|
+
availableAmountOut: quote.availableAmountOut,
|
|
258
|
+
owner,
|
|
259
|
+
recipient,
|
|
260
|
+
spender,
|
|
261
|
+
approvalMode
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
var PsmSwapClient = class {
|
|
266
|
+
constructor(context) {
|
|
267
|
+
this.context = context;
|
|
268
|
+
}
|
|
269
|
+
context;
|
|
270
|
+
read(input = {}) {
|
|
271
|
+
return readPsm(this.context, input);
|
|
272
|
+
}
|
|
273
|
+
quote(input) {
|
|
274
|
+
return quotePsmSwap(this.context, input);
|
|
275
|
+
}
|
|
276
|
+
plan(input) {
|
|
277
|
+
return planPsmSwap(this.context, input);
|
|
278
|
+
}
|
|
279
|
+
executeAction(action) {
|
|
280
|
+
return executePlanAction(action, createContextExecutor(this.context));
|
|
281
|
+
}
|
|
282
|
+
submit(plan) {
|
|
283
|
+
assertSwapPlan(plan, ["psm"], "PSM");
|
|
284
|
+
return executePlanTransactions(plan, createContextExecutor(this.context));
|
|
285
|
+
}
|
|
286
|
+
async execute(input) {
|
|
287
|
+
const plan = isTransactionPlan(input) ? input : await planPsmSwap(this.context, input);
|
|
288
|
+
assertSwapPlan(plan, ["psm"], "PSM");
|
|
289
|
+
return executeTransactionPlan(plan, createContextExecutor(this.context));
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
function createPsmSwapClient(context) {
|
|
293
|
+
return new PsmSwapClient(context);
|
|
294
|
+
}
|
|
295
|
+
function createPsmClient(options) {
|
|
296
|
+
return createPsmSwapClient(createProtocolsContext(options));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/swap/router-execute.ts
|
|
300
|
+
import {
|
|
301
|
+
SDKError as SDKError5
|
|
302
|
+
} from "@sun-protocol/sun-sdk-core";
|
|
303
|
+
|
|
304
|
+
// src/internal/router-action.ts
|
|
305
|
+
import {
|
|
306
|
+
createDeferredContractCallAction
|
|
307
|
+
} from "@sun-protocol/sun-sdk-core";
|
|
308
|
+
var UNIVERSAL_ROUTER_MATERIALIZATION_KIND = "universal-router-swap";
|
|
309
|
+
function toJsonValue(value) {
|
|
310
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
313
|
+
if (Array.isArray(value)) {
|
|
314
|
+
return value.map(toJsonValue);
|
|
315
|
+
}
|
|
316
|
+
if (typeof value === "object") {
|
|
317
|
+
const objectValue = value;
|
|
318
|
+
const result = {};
|
|
319
|
+
for (const [key, entry] of Object.entries(objectValue)) {
|
|
320
|
+
if (entry !== void 0) {
|
|
321
|
+
result[key] = toJsonValue(entry);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
throw new TypeError(`Value is not JSON-serializable: ${typeof value}`);
|
|
327
|
+
}
|
|
328
|
+
function toJsonObject(value) {
|
|
329
|
+
const normalized = toJsonValue(value);
|
|
330
|
+
if (normalized === null || Array.isArray(normalized) || typeof normalized !== "object") {
|
|
331
|
+
throw new TypeError("Value is not a JSON object");
|
|
332
|
+
}
|
|
333
|
+
return normalized;
|
|
334
|
+
}
|
|
335
|
+
function createUniversalRouterDeferredAction(input) {
|
|
336
|
+
return createDeferredContractCallAction({
|
|
337
|
+
id: input.id,
|
|
338
|
+
materialization: {
|
|
339
|
+
kind: UNIVERSAL_ROUTER_MATERIALIZATION_KIND,
|
|
340
|
+
data: toJsonObject(input.data)
|
|
341
|
+
},
|
|
342
|
+
...input.intent !== void 0 ? { intent: input.intent } : {}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/internal/universal-router-adapter.ts
|
|
347
|
+
import {
|
|
348
|
+
SDKError as SDKError4,
|
|
349
|
+
createContractCallAction
|
|
350
|
+
} from "@sun-protocol/sun-sdk-core";
|
|
351
|
+
import {
|
|
352
|
+
Address,
|
|
353
|
+
TradePlanner,
|
|
354
|
+
parseRouteAPIResponse,
|
|
355
|
+
PoolType,
|
|
356
|
+
PoolVersion
|
|
357
|
+
} from "@sun-protocol/universal-router-sdk";
|
|
358
|
+
import { computeV3PoolAddress } from "@sun-protocol/sun-sdk-sunswap-v3";
|
|
359
|
+
function requireString(route, key) {
|
|
360
|
+
const value = route[key];
|
|
361
|
+
if (typeof value !== "string") {
|
|
362
|
+
throw new SDKError4("SDK_INVALID_ARGUMENT", `Router route is missing ${String(key)}`, { key, route });
|
|
363
|
+
}
|
|
364
|
+
return value;
|
|
365
|
+
}
|
|
366
|
+
function requireStringArray(route, key) {
|
|
367
|
+
const value = route[key];
|
|
368
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
369
|
+
throw new SDKError4("SDK_INVALID_ARGUMENT", `Router route is missing ${String(key)}`, { key, route });
|
|
370
|
+
}
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
function normalizePoolKey(poolKey) {
|
|
374
|
+
if (poolKey === null || poolKey === void 0) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
if (typeof poolKey !== "object") {
|
|
378
|
+
throw new SDKError4("SDK_INVALID_ARGUMENT", "Router route poolKey must be an object or null", { poolKey });
|
|
379
|
+
}
|
|
380
|
+
const value = poolKey;
|
|
381
|
+
const token0 = value.token0 ?? value.currency0;
|
|
382
|
+
const token1 = value.token1 ?? value.currency1;
|
|
383
|
+
if (typeof token0 !== "string" || typeof token1 !== "string" || typeof value.hooks !== "string" || typeof value.fee !== "number" || typeof value.parameters !== "string") {
|
|
384
|
+
throw new SDKError4("SDK_INVALID_ARGUMENT", "Router route poolKey has invalid fields", { poolKey });
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
token0,
|
|
388
|
+
token1,
|
|
389
|
+
hooks: value.hooks,
|
|
390
|
+
fee: value.fee,
|
|
391
|
+
parameters: value.parameters
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function normalizeRouteData(route) {
|
|
395
|
+
const poolKeysRaw = route.poolKeys;
|
|
396
|
+
if (!Array.isArray(poolKeysRaw)) {
|
|
397
|
+
throw new SDKError4("SDK_INVALID_ARGUMENT", "Router route is missing poolKeys", { route });
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
amountIn: requireString(route, "amountIn"),
|
|
401
|
+
amountInRaw: requireString(route, "amountInRaw"),
|
|
402
|
+
amountOut: requireString(route, "amountOut"),
|
|
403
|
+
amountOutRaw: requireString(route, "amountOutRaw"),
|
|
404
|
+
amountOutMinimum: typeof route.amountOutMinimum === "string" ? route.amountOutMinimum : "0",
|
|
405
|
+
amountOutMinimumRaw: typeof route.amountOutMinimumRaw === "string" ? route.amountOutMinimumRaw : "0",
|
|
406
|
+
inUsd: typeof route.inUsd === "string" ? route.inUsd : "0",
|
|
407
|
+
outUsd: typeof route.outUsd === "string" ? route.outUsd : "0",
|
|
408
|
+
impact: typeof route.impact === "string" ? route.impact : "0",
|
|
409
|
+
fee: typeof route.fee === "string" ? route.fee : "0",
|
|
410
|
+
containsUnverifiedHook: route.containsUnverifiedHook === true,
|
|
411
|
+
tokens: requireStringArray(route, "tokens"),
|
|
412
|
+
symbols: requireStringArray(route, "symbols"),
|
|
413
|
+
poolFees: requireStringArray(route, "poolFees"),
|
|
414
|
+
poolVersions: requireStringArray(route, "poolVersions"),
|
|
415
|
+
poolKeys: poolKeysRaw.map(normalizePoolKey),
|
|
416
|
+
stepAmountsOut: requireStringArray(route, "stepAmountsOut"),
|
|
417
|
+
...typeof route.amountInReferralBips === "number" ? { amountInReferralBips: route.amountInReferralBips } : {},
|
|
418
|
+
...typeof route.amountOutReferralBips === "number" ? { amountOutReferralBips: route.amountOutReferralBips } : {}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function routeStartsWithNativeTrx(chain, route) {
|
|
422
|
+
return route.tokens[0] === chain.contracts.trx;
|
|
423
|
+
}
|
|
424
|
+
function buildUniversalRouterSwapAction(input) {
|
|
425
|
+
const routeData = normalizeRouteData(input.route);
|
|
426
|
+
const hops = routeData.poolVersions.length;
|
|
427
|
+
if (hops === 0 || routeData.tokens.length !== hops + 1 || routeData.poolFees.length < hops || routeData.poolVersions.some((version) => !Object.values(PoolVersion).includes(version))) {
|
|
428
|
+
throw new SDKError4("SDK_INVALID_ARGUMENT", "Router route has inconsistent hop data");
|
|
429
|
+
}
|
|
430
|
+
routeData.tokens = routeData.tokens.map((token) => normalizeTronAddress(token));
|
|
431
|
+
const swapTradeRoute = parseRouteAPIResponse(routeData, input.network === "nile", {
|
|
432
|
+
slippageBips: input.slippageBips
|
|
433
|
+
});
|
|
434
|
+
if (input.recipient) {
|
|
435
|
+
swapTradeRoute.recipient = new Address(input.recipient);
|
|
436
|
+
}
|
|
437
|
+
const tradePlanner = new TradePlanner([swapTradeRoute], false, {
|
|
438
|
+
permitOptions: input.permit ? { permitEnabled: true, permit: input.permit } : { permitEnabled: false }
|
|
439
|
+
});
|
|
440
|
+
tradePlanner.encode();
|
|
441
|
+
const usdtAddress = input.chain.contracts.usdt;
|
|
442
|
+
const sources = /* @__PURE__ */ new Map();
|
|
443
|
+
swapTradeRoute.pools.forEach((pool, index) => {
|
|
444
|
+
if (pool.type !== PoolType.V3 || routeData.tokens[index + 1] !== usdtAddress) return;
|
|
445
|
+
const address = computeV3PoolAddress({
|
|
446
|
+
network: input.network,
|
|
447
|
+
tokenA: routeData.tokens[index],
|
|
448
|
+
tokenB: usdtAddress,
|
|
449
|
+
fee: pool.fee,
|
|
450
|
+
factory: input.chain.contracts.sunswapV3Factory,
|
|
451
|
+
wrappedNative: input.chain.contracts.wtrx
|
|
452
|
+
});
|
|
453
|
+
sources.set(address, { address, role: "v3-pool" });
|
|
454
|
+
});
|
|
455
|
+
const usdtTransfer = {
|
|
456
|
+
network: input.network,
|
|
457
|
+
transfersUsdt: sources.size > 0,
|
|
458
|
+
sources: [...sources.values()]
|
|
459
|
+
};
|
|
460
|
+
return createContractCallAction({
|
|
461
|
+
id: "universal-router-swap",
|
|
462
|
+
target: input.chain.contracts.universalRouter,
|
|
463
|
+
functionSelector: "execute(bytes,bytes[],uint256)",
|
|
464
|
+
parameters: [
|
|
465
|
+
{ type: "bytes", value: tradePlanner.commands },
|
|
466
|
+
{ type: "bytes[]", value: tradePlanner.inputs },
|
|
467
|
+
{ type: "uint256", value: input.deadline }
|
|
468
|
+
],
|
|
469
|
+
...routeStartsWithNativeTrx(input.chain, routeData) ? { callValue: BigInt(routeData.amountInRaw) } : {},
|
|
470
|
+
feeLimit: normalizeFeeLimit(input.feeLimit, DEFAULT_UNIVERSAL_ROUTER_SWAP_FEE_LIMIT),
|
|
471
|
+
metadata: { usdtTransfer }
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/swap/router-execute.ts
|
|
476
|
+
function signatureFromResults(actionId, completed) {
|
|
477
|
+
if (!actionId) return void 0;
|
|
478
|
+
const result = completed.find((item) => item.action.id === actionId)?.result;
|
|
479
|
+
return typeof result === "string" ? result.startsWith("0x") ? result : `0x${result}` : void 0;
|
|
480
|
+
}
|
|
481
|
+
async function materializeUniversalRouterActionWithSignature(context, action, permitSignature) {
|
|
482
|
+
if (action.materialization.kind !== UNIVERSAL_ROUTER_MATERIALIZATION_KIND) {
|
|
483
|
+
throw new SDKError5("SDK_RUNTIME_ERROR", `Unsupported deferred action kind: ${action.materialization.kind}`);
|
|
484
|
+
}
|
|
485
|
+
const data = action.materialization.data;
|
|
486
|
+
if (data.network !== context.network) {
|
|
487
|
+
throw new SDKError5("SDK_USDT_TRANSFER_UNKNOWN", "Network changed; rebuild the swap plan", { reason: "CHECK_FAILED" });
|
|
488
|
+
}
|
|
489
|
+
if (data.signatureActionId && !permitSignature) {
|
|
490
|
+
throw new SDKError5("SDK_INVALID_ARGUMENT", "Router swap requires the planned Permit2 signature", {
|
|
491
|
+
actionId: action.id,
|
|
492
|
+
signatureActionId: data.signatureActionId
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
if (data.signatureActionId && !data.permitSingle) {
|
|
496
|
+
throw new SDKError5("SDK_RUNTIME_ERROR", "Router swap plan is missing Permit2 permit data", {
|
|
497
|
+
actionId: action.id,
|
|
498
|
+
signatureActionId: data.signatureActionId
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const normalizedSignature = permitSignature ? permitSignature.startsWith("0x") ? permitSignature : `0x${permitSignature}` : void 0;
|
|
502
|
+
const permit = normalizedSignature && data.permitSingle ? { ...data.permitSingle, signature: normalizedSignature } : void 0;
|
|
503
|
+
return buildUniversalRouterSwapAction({
|
|
504
|
+
network: context.network,
|
|
505
|
+
chain: context.chain,
|
|
506
|
+
route: data.route,
|
|
507
|
+
slippageBips: BigInt(data.slippageBips),
|
|
508
|
+
deadline: await resolveTransactionDeadline(context, data.transactionValidForSeconds),
|
|
509
|
+
...data.recipient ? { recipient: data.recipient } : {},
|
|
510
|
+
...permit ? { permit } : {},
|
|
511
|
+
...data.feeLimit !== void 0 ? { feeLimit: data.feeLimit } : {}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
async function materializeUniversalRouterAction(context, action, completedActions) {
|
|
515
|
+
const data = action.materialization.data;
|
|
516
|
+
const signature = signatureFromResults(data.signatureActionId, completedActions);
|
|
517
|
+
return materializeUniversalRouterActionWithSignature(context, action, signature);
|
|
518
|
+
}
|
|
519
|
+
function createRouterSwapExecutor(context, options) {
|
|
520
|
+
const executor = createContextExecutor(context);
|
|
521
|
+
return {
|
|
522
|
+
...executor,
|
|
523
|
+
sendAction: async (action) => {
|
|
524
|
+
if (action.target === context.chain.contracts.universalRouter || action.intent?.scope.protocol === "universal-router" && action.intent.kind === "swap") {
|
|
525
|
+
await assertUsdtTransfer(context, action);
|
|
526
|
+
}
|
|
527
|
+
if (!executor.sendAction) throw new SDKError5("SDK_RUNTIME_ERROR", "Swap execution requires a runtime");
|
|
528
|
+
return executor.sendAction(action);
|
|
529
|
+
},
|
|
530
|
+
materializeAction: (action, completed) => options ? materializeUniversalRouterActionWithSignature(context, action, options.permitSignature) : materializeUniversalRouterAction(context, action, completed)
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/swap/tokens.ts
|
|
535
|
+
import { SDKError as SDKError6, assertTronAddress as assertTronAddress2 } from "@sun-protocol/sun-sdk-core";
|
|
536
|
+
function isNativeTrxToken(context, token) {
|
|
537
|
+
return token.toUpperCase() === "TRX" || token === context.chain.contracts.trx;
|
|
538
|
+
}
|
|
539
|
+
function normalizeRouterToken(context, token) {
|
|
540
|
+
return isNativeTrxToken(context, token) ? context.chain.contracts.trx : assertTronAddress2(token, "token");
|
|
541
|
+
}
|
|
542
|
+
function normalizeV2TrxPairPath(context, input) {
|
|
543
|
+
const tokenInIsTrx = isNativeTrxToken(context, input.tokenIn);
|
|
544
|
+
const tokenOutIsTrx = isNativeTrxToken(context, input.tokenOut);
|
|
545
|
+
if (tokenInIsTrx === tokenOutIsTrx) {
|
|
546
|
+
throw new SDKError6("SDK_INVALID_ARGUMENT", "V2 swap currently supports exactly one TRX side", {
|
|
547
|
+
tokenIn: input.tokenIn,
|
|
548
|
+
tokenOut: input.tokenOut
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
const token = assertTronAddress2(tokenInIsTrx ? input.tokenOut : input.tokenIn, "token");
|
|
552
|
+
return tokenInIsTrx ? { direction: "trx-to-token", token, path: [context.chain.contracts.wtrx, token] } : { direction: "token-to-trx", token, path: [token, context.chain.contracts.wtrx] };
|
|
553
|
+
}
|
|
554
|
+
function normalizeV2PathToken(context, token, label) {
|
|
555
|
+
if (isNativeTrxToken(context, token) || token === context.chain.contracts.wtrx) {
|
|
556
|
+
return context.chain.contracts.wtrx;
|
|
557
|
+
}
|
|
558
|
+
return assertTronAddress2(token, label);
|
|
559
|
+
}
|
|
560
|
+
function isWrappedTrxToken(context, token) {
|
|
561
|
+
return token === context.chain.contracts.wtrx;
|
|
562
|
+
}
|
|
563
|
+
function detectWtrxSwap(context, input) {
|
|
564
|
+
const amountIn = normalizeBaseUnitAmount(input.amountIn, "amountIn");
|
|
565
|
+
const tokenInIsTrx = isNativeTrxToken(context, input.tokenIn);
|
|
566
|
+
const tokenOutIsTrx = isNativeTrxToken(context, input.tokenOut);
|
|
567
|
+
const tokenInIsWtrx = isWrappedTrxToken(context, input.tokenIn);
|
|
568
|
+
const tokenOutIsWtrx = isWrappedTrxToken(context, input.tokenOut);
|
|
569
|
+
let action;
|
|
570
|
+
if (tokenInIsTrx && tokenOutIsWtrx) action = "wrap";
|
|
571
|
+
if (tokenInIsWtrx && tokenOutIsTrx) action = "unwrap";
|
|
572
|
+
if (!action) return void 0;
|
|
573
|
+
return {
|
|
574
|
+
kind: "wtrx",
|
|
575
|
+
source: "wtrx",
|
|
576
|
+
action,
|
|
577
|
+
tokenIn: input.tokenIn,
|
|
578
|
+
tokenOut: input.tokenOut,
|
|
579
|
+
amountIn,
|
|
580
|
+
amountOut: amountIn,
|
|
581
|
+
route: {
|
|
582
|
+
type: action === "wrap" ? "WTRX_WRAP" : "WTRX_UNWRAP",
|
|
583
|
+
amountIn,
|
|
584
|
+
amountOut: amountIn
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function normalizeV2SwapPath(context, input) {
|
|
589
|
+
const tokenInIsTrx = isNativeTrxToken(context, input.tokenIn);
|
|
590
|
+
const tokenOutIsTrx = isNativeTrxToken(context, input.tokenOut);
|
|
591
|
+
const tokenInIsWtrx = isWrappedTrxToken(context, input.tokenIn);
|
|
592
|
+
const tokenOutIsWtrx = isWrappedTrxToken(context, input.tokenOut);
|
|
593
|
+
if (tokenInIsTrx && (tokenOutIsTrx || tokenOutIsWtrx) || tokenInIsWtrx && tokenOutIsTrx) {
|
|
594
|
+
throw new SDKError6("SDK_INVALID_ARGUMENT", "V2 swap does not handle TRX wrap or unwrap", {
|
|
595
|
+
tokenIn: input.tokenIn,
|
|
596
|
+
tokenOut: input.tokenOut
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
const inputToken = normalizeV2PathToken(context, input.tokenIn, "tokenIn");
|
|
600
|
+
const outputToken = normalizeV2PathToken(context, input.tokenOut, "tokenOut");
|
|
601
|
+
const path = (input.path ?? [input.tokenIn, input.tokenOut]).map(
|
|
602
|
+
(token, index) => normalizeV2PathToken(context, token, `path[${index}]`)
|
|
603
|
+
);
|
|
604
|
+
const originalPath = input.path ?? [input.tokenIn, input.tokenOut];
|
|
605
|
+
if (inputToken === outputToken) {
|
|
606
|
+
throw new SDKError6("SDK_INVALID_ARGUMENT", "V2 swap tokenIn and tokenOut must differ", {
|
|
607
|
+
tokenIn: input.tokenIn,
|
|
608
|
+
tokenOut: input.tokenOut
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
if (path.length < 2 || path[0] !== inputToken || path[path.length - 1] !== outputToken) {
|
|
612
|
+
throw new SDKError6("SDK_INVALID_ARGUMENT", "V2 swap path must start with tokenIn and end with tokenOut", {
|
|
613
|
+
tokenIn: input.tokenIn,
|
|
614
|
+
tokenOut: input.tokenOut,
|
|
615
|
+
path: originalPath
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
if (path.some((token, index) => index > 0 && token === path[index - 1])) {
|
|
619
|
+
throw new SDKError6("SDK_INVALID_ARGUMENT", "V2 swap path must not contain duplicate adjacent tokens", {
|
|
620
|
+
tokenIn: input.tokenIn,
|
|
621
|
+
tokenOut: input.tokenOut,
|
|
622
|
+
path: originalPath
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
const direction = tokenInIsTrx ? "trx-to-token" : tokenOutIsTrx ? "token-to-trx" : "token-to-token";
|
|
626
|
+
return { direction, token: tokenInIsTrx ? outputToken : inputToken, path };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/swap/plan.ts
|
|
630
|
+
import {
|
|
631
|
+
SDKError as SDKError7,
|
|
632
|
+
assertTronAddress as assertTronAddress3,
|
|
633
|
+
createContractCallAction as createContractCallAction2,
|
|
634
|
+
formatUnits as formatUnits2
|
|
635
|
+
} from "@sun-protocol/sun-sdk-core";
|
|
636
|
+
function stringifyRoute(route) {
|
|
637
|
+
try {
|
|
638
|
+
return JSON.stringify(route);
|
|
639
|
+
} catch {
|
|
640
|
+
return void 0;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function findSelectedRouteIndex(quote, route) {
|
|
644
|
+
const serializedRoute = stringifyRoute(route);
|
|
645
|
+
return quote.routes.findIndex((candidate) => {
|
|
646
|
+
if (candidate === route) return true;
|
|
647
|
+
if (serializedRoute === void 0) return false;
|
|
648
|
+
return stringifyRoute(candidate) === serializedRoute;
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
function isWtrxRoute(route) {
|
|
652
|
+
return typeof route === "object" && route !== null && (route.type === "WTRX_WRAP" || route.type === "WTRX_UNWRAP");
|
|
653
|
+
}
|
|
654
|
+
function buildWtrxSwapPlan(context, quote, route, owner, feeLimit) {
|
|
655
|
+
if (stringifyRoute(route) !== stringifyRoute(quote.route)) {
|
|
656
|
+
throw new SDKError7("SDK_INVALID_ARGUMENT", "Selected WTRX route must come from the quote", {
|
|
657
|
+
quoteRoute: quote.route,
|
|
658
|
+
route
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
const resolvedFeeLimit = normalizeFeeLimit(feeLimit, DEFAULT_WTRX_SWAP_FEE_LIMIT);
|
|
662
|
+
const intent = {
|
|
663
|
+
kind: quote.action,
|
|
664
|
+
scope: swapIntentScope(WTRX_PROTOCOL_ID, quote.action),
|
|
665
|
+
amount: formatUnits2(BigInt(quote.amountIn), context.chain.nativeCurrency.decimals)
|
|
666
|
+
};
|
|
667
|
+
const transaction = quote.action === "wrap" ? createContractCallAction2({
|
|
668
|
+
id: `${nextPlanId(context, "swap-wtrx")}-wrap`,
|
|
669
|
+
target: context.chain.contracts.wtrx,
|
|
670
|
+
functionSelector: "deposit()",
|
|
671
|
+
parameters: [],
|
|
672
|
+
callValue: BigInt(quote.amountIn),
|
|
673
|
+
feeLimit: resolvedFeeLimit,
|
|
674
|
+
intent
|
|
675
|
+
}) : createContractCallAction2({
|
|
676
|
+
id: `${nextPlanId(context, "swap-wtrx")}-unwrap`,
|
|
677
|
+
target: context.chain.contracts.wtrx,
|
|
678
|
+
functionSelector: "withdraw(uint256)",
|
|
679
|
+
parameters: [{ type: "uint256", value: quote.amountIn }],
|
|
680
|
+
feeLimit: resolvedFeeLimit,
|
|
681
|
+
intent
|
|
682
|
+
});
|
|
683
|
+
return {
|
|
684
|
+
id: nextPlanId(context, "swap-wtrx"),
|
|
685
|
+
kind: "swap",
|
|
686
|
+
network: context.network,
|
|
687
|
+
owner,
|
|
688
|
+
summary: {
|
|
689
|
+
title: quote.action === "wrap" ? "Wrap TRX" : "Unwrap WTRX",
|
|
690
|
+
lines: [`Amount ${quote.amountIn}`]
|
|
691
|
+
},
|
|
692
|
+
approvals: [],
|
|
693
|
+
transactions: [transaction],
|
|
694
|
+
metadata: {
|
|
695
|
+
protocol: "wtrx",
|
|
696
|
+
action: quote.action,
|
|
697
|
+
amountIn: quote.amountIn,
|
|
698
|
+
amountOut: quote.amountOut,
|
|
699
|
+
tokenIn: quote.tokenIn,
|
|
700
|
+
tokenOut: quote.tokenOut,
|
|
701
|
+
feeLimit: resolvedFeeLimit
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
async function planRouterSwap(context, input) {
|
|
706
|
+
const { wallet, address: owner } = await requireWalletSnapshot(context, "Router swap plan");
|
|
707
|
+
if (input.quote.kind === "wtrx") {
|
|
708
|
+
if (!isWtrxRoute(input.route)) {
|
|
709
|
+
throw new SDKError7("SDK_INVALID_ARGUMENT", "WTRX swap planning requires a WTRX route");
|
|
710
|
+
}
|
|
711
|
+
return buildWtrxSwapPlan(context, input.quote, input.route, owner, input.feeLimit);
|
|
712
|
+
}
|
|
713
|
+
const selectedRouteIndex = findSelectedRouteIndex(input.quote, input.route);
|
|
714
|
+
if (selectedRouteIndex < 0) {
|
|
715
|
+
throw new SDKError7("SDK_INVALID_ARGUMENT", "Selected route must come from quote.routes", {
|
|
716
|
+
routeCount: input.quote.routes.length,
|
|
717
|
+
tokenIn: input.quote.tokenIn,
|
|
718
|
+
tokenOut: input.quote.tokenOut,
|
|
719
|
+
amountIn: input.quote.amountIn
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
const selectedRoute = input.quote.routes[selectedRouteIndex];
|
|
723
|
+
if (!selectedRoute) {
|
|
724
|
+
throw new SDKError7("SDK_RUNTIME_ERROR", "Selected router route could not be resolved after validation", {
|
|
725
|
+
selectedRouteIndex,
|
|
726
|
+
routeCount: input.quote.routes.length
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
const symbols = routerRouteSymbols(selectedRoute);
|
|
730
|
+
const intentScope = swapIntentScope(UNIVERSAL_ROUTER_PROTOCOL_ID);
|
|
731
|
+
const recipient = await normalizeRecipient(context, input.recipient, "Router swap recipient", owner);
|
|
732
|
+
const transactionValidForSeconds = normalizeTransactionValidForSeconds(input.transactionValidForSeconds);
|
|
733
|
+
const slippageBips = slippageToBips(input.slippage ?? "0.5%");
|
|
734
|
+
const feeLimit = normalizeFeeLimit(input.feeLimit, DEFAULT_UNIVERSAL_ROUTER_SWAP_FEE_LIMIT);
|
|
735
|
+
const tokenIn = input.quote.tokenIn;
|
|
736
|
+
const approvals = [];
|
|
737
|
+
let signatureActionId;
|
|
738
|
+
let permitSingle;
|
|
739
|
+
if (!isNativeTrxToken(context, tokenIn)) {
|
|
740
|
+
const authorization = await planRouterPermit2Authorization(context, {
|
|
741
|
+
owner,
|
|
742
|
+
token: assertTronAddress3(tokenIn, "tokenIn"),
|
|
743
|
+
amount: input.quote.amountIn,
|
|
744
|
+
approval: input.approval ?? "auto",
|
|
745
|
+
approveAmountType: "max",
|
|
746
|
+
walletCapabilities: wallet.capabilities
|
|
747
|
+
});
|
|
748
|
+
approvals.push(...attachApprovalIntent(authorization.actions, intentScope, symbols.from));
|
|
749
|
+
signatureActionId = authorization.signatureActionId;
|
|
750
|
+
permitSingle = authorization.permitSingle;
|
|
751
|
+
}
|
|
752
|
+
const transaction = createUniversalRouterDeferredAction({
|
|
753
|
+
id: `${nextPlanId(context, "swap-router")}-execute`,
|
|
754
|
+
data: {
|
|
755
|
+
network: context.network,
|
|
756
|
+
route: selectedRoute,
|
|
757
|
+
slippageBips: slippageBips.toString(),
|
|
758
|
+
transactionValidForSeconds,
|
|
759
|
+
recipient,
|
|
760
|
+
feeLimit,
|
|
761
|
+
...signatureActionId ? { signatureActionId } : {},
|
|
762
|
+
...permitSingle ? { permitSingle } : {}
|
|
763
|
+
},
|
|
764
|
+
intent: {
|
|
765
|
+
kind: "swap",
|
|
766
|
+
scope: intentScope,
|
|
767
|
+
from: { value: selectedRoute.amountIn, symbol: symbols.from },
|
|
768
|
+
to: { value: selectedRoute.amountOut, symbol: symbols.to },
|
|
769
|
+
...input.recipient === void 0 ? {} : { recipient }
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
return {
|
|
773
|
+
id: nextPlanId(context, "swap-router"),
|
|
774
|
+
kind: "swap",
|
|
775
|
+
network: context.network,
|
|
776
|
+
owner,
|
|
777
|
+
summary: {
|
|
778
|
+
title: "Swap with Universal Router",
|
|
779
|
+
lines: [`Spend ${selectedRoute.amountIn}`, `Expected output ${selectedRoute.amountOut}`]
|
|
780
|
+
},
|
|
781
|
+
approvals,
|
|
782
|
+
transactions: [transaction],
|
|
783
|
+
metadata: {
|
|
784
|
+
protocol: "universal-router",
|
|
785
|
+
selectedRouteIndex,
|
|
786
|
+
tokenIn: input.quote.tokenIn,
|
|
787
|
+
tokenOut: input.quote.tokenOut,
|
|
788
|
+
amountIn: selectedRoute.amountInRaw,
|
|
789
|
+
amountOut: selectedRoute.amountOutRaw,
|
|
790
|
+
amountInFormatted: selectedRoute.amountIn,
|
|
791
|
+
amountOutFormatted: selectedRoute.amountOut,
|
|
792
|
+
slippageBips,
|
|
793
|
+
recipient,
|
|
794
|
+
transactionValidForSeconds,
|
|
795
|
+
feeLimit
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/swap/quote.ts
|
|
801
|
+
import { SDKError as SDKError8 } from "@sun-protocol/sun-sdk-core";
|
|
802
|
+
function requireRoutes(response) {
|
|
803
|
+
if (response.code !== 0) {
|
|
804
|
+
throw new SDKError8("SDK_NO_ROUTE", response.message ?? "Router API returned no route", {
|
|
805
|
+
code: response.code,
|
|
806
|
+
message: response.message
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
if (!Array.isArray(response.data) || response.data.length === 0) {
|
|
810
|
+
throw new SDKError8("SDK_NO_ROUTE", "Router API returned no route", {
|
|
811
|
+
code: response.code
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
return response.data;
|
|
815
|
+
}
|
|
816
|
+
function normalizeRouteTypeList(input) {
|
|
817
|
+
if (input.routerRouteTypes !== void 0 && input.typeList !== void 0) {
|
|
818
|
+
throw new SDKError8("SDK_INVALID_ARGUMENT", "Use routerRouteTypes or typeList, not both", {
|
|
819
|
+
routerRouteTypes: input.routerRouteTypes,
|
|
820
|
+
typeList: input.typeList
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
return input.routerRouteTypes !== void 0 ? input.routerRouteTypes.join(",") : input.typeList;
|
|
824
|
+
}
|
|
825
|
+
function requireRouterAmountString(route, field, routeIndex) {
|
|
826
|
+
const value = route[field];
|
|
827
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
828
|
+
throw new SDKError8("SDK_API_ERROR", `Router route has invalid ${field}`, {
|
|
829
|
+
routeIndex,
|
|
830
|
+
field,
|
|
831
|
+
value
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
return value;
|
|
835
|
+
}
|
|
836
|
+
function normalizeRouterRawAmount(value, field, routeIndex) {
|
|
837
|
+
try {
|
|
838
|
+
return normalizeBaseUnitAmount(value, `Router route ${field}`);
|
|
839
|
+
} catch {
|
|
840
|
+
throw new SDKError8("SDK_API_ERROR", `Router route has invalid ${field}`, {
|
|
841
|
+
routeIndex,
|
|
842
|
+
field,
|
|
843
|
+
value
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function validateRouterRoute(route, routeIndex, requestedAmountIn) {
|
|
848
|
+
if (typeof route !== "object" || route === null || Array.isArray(route)) {
|
|
849
|
+
throw new SDKError8("SDK_API_ERROR", "Router route must be an object", {
|
|
850
|
+
routeIndex,
|
|
851
|
+
route
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
const routeRecord = route;
|
|
855
|
+
const amountIn = requireRouterAmountString(routeRecord, "amountIn", routeIndex);
|
|
856
|
+
const amountOut = requireRouterAmountString(routeRecord, "amountOut", routeIndex);
|
|
857
|
+
const amountInRaw = normalizeRouterRawAmount(
|
|
858
|
+
requireRouterAmountString(routeRecord, "amountInRaw", routeIndex),
|
|
859
|
+
"amountInRaw",
|
|
860
|
+
routeIndex
|
|
861
|
+
);
|
|
862
|
+
const amountOutRaw = normalizeRouterRawAmount(
|
|
863
|
+
requireRouterAmountString(routeRecord, "amountOutRaw", routeIndex),
|
|
864
|
+
"amountOutRaw",
|
|
865
|
+
routeIndex
|
|
866
|
+
);
|
|
867
|
+
if (amountInRaw !== requestedAmountIn) {
|
|
868
|
+
throw new SDKError8("SDK_API_ERROR", "Router route input amount does not match the request", {
|
|
869
|
+
routeIndex,
|
|
870
|
+
requestedAmountIn,
|
|
871
|
+
routeAmountInRaw: amountInRaw
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
return {
|
|
875
|
+
...routeRecord,
|
|
876
|
+
amountIn,
|
|
877
|
+
amountInRaw,
|
|
878
|
+
amountOut,
|
|
879
|
+
amountOutRaw
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
async function quoteRouterSwap(context, input) {
|
|
883
|
+
const wtrxQuote = detectWtrxSwap(context, input);
|
|
884
|
+
if (wtrxQuote) return wtrxQuote;
|
|
885
|
+
const amountIn = normalizeBaseUnitAmount(input.amountIn, "amountIn");
|
|
886
|
+
const typeList = normalizeRouteTypeList(input);
|
|
887
|
+
const routerEndpoint = context.endpoints.router;
|
|
888
|
+
if (!routerEndpoint) {
|
|
889
|
+
throw new SDKError8("SDK_UNSUPPORTED_NETWORK", "Router API endpoint is not configured for this network", {
|
|
890
|
+
network: context.network
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
const routesResponse = await context.routerApi.getRoutes(routerEndpoint, {
|
|
894
|
+
fromToken: normalizeRouterToken(context, input.tokenIn),
|
|
895
|
+
toToken: normalizeRouterToken(context, input.tokenOut),
|
|
896
|
+
amountIn,
|
|
897
|
+
...typeList !== void 0 ? { typeList } : {},
|
|
898
|
+
...input.includeUnverifiedV4Hook !== void 0 ? { includeUnverifiedV4Hook: input.includeUnverifiedV4Hook } : {}
|
|
899
|
+
});
|
|
900
|
+
const routes = requireRoutes(routesResponse).map((route, routeIndex) => validateRouterRoute(route, routeIndex, amountIn));
|
|
901
|
+
const bestRoute = routes[0];
|
|
902
|
+
if (!bestRoute) {
|
|
903
|
+
throw new SDKError8("SDK_NO_ROUTE", "Router API returned no route");
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
kind: "router",
|
|
907
|
+
source: "router",
|
|
908
|
+
tokenIn: input.tokenIn,
|
|
909
|
+
tokenOut: input.tokenOut,
|
|
910
|
+
amountIn: bestRoute.amountInRaw,
|
|
911
|
+
amountOut: bestRoute.amountOutRaw,
|
|
912
|
+
amountInFormatted: bestRoute.amountIn,
|
|
913
|
+
amountOutFormatted: bestRoute.amountOut,
|
|
914
|
+
routes,
|
|
915
|
+
bestRoute
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
var quoteSwap = quoteRouterSwap;
|
|
919
|
+
|
|
920
|
+
// src/swap/router.ts
|
|
921
|
+
var SwapRouterClient = class {
|
|
922
|
+
constructor(context) {
|
|
923
|
+
this.context = context;
|
|
924
|
+
}
|
|
925
|
+
context;
|
|
926
|
+
quote(input) {
|
|
927
|
+
return quoteRouterSwap(this.context, input);
|
|
928
|
+
}
|
|
929
|
+
plan(input) {
|
|
930
|
+
return planRouterSwap(this.context, input);
|
|
931
|
+
}
|
|
932
|
+
executeAction(action) {
|
|
933
|
+
return executePlanAction(action, createRouterSwapExecutor(this.context));
|
|
934
|
+
}
|
|
935
|
+
async submit(plan, options = {}) {
|
|
936
|
+
assertSwapPlan(plan, ["universal-router", "wtrx"], "Router");
|
|
937
|
+
return executePlanTransactions(
|
|
938
|
+
plan,
|
|
939
|
+
createRouterSwapExecutor(this.context, options)
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
async execute(input) {
|
|
943
|
+
const plan = isTransactionPlan(input) ? input : await planRouterSwap(this.context, input);
|
|
944
|
+
assertSwapPlan(plan, ["universal-router", "wtrx"], "Router");
|
|
945
|
+
return executeTransactionPlan(plan, createRouterSwapExecutor(this.context));
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
function createSwapRouterClient(options) {
|
|
949
|
+
return new SwapRouterClient(createProtocolsContext(options));
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/swap/v2.ts
|
|
953
|
+
import {
|
|
954
|
+
buildV2RouterAllowedTradingPairRead,
|
|
955
|
+
buildV2RouterAmountsInRead,
|
|
956
|
+
buildV2RouterAmountsOutRead,
|
|
957
|
+
normalizeV2AmountsResult
|
|
958
|
+
} from "@sun-protocol/sun-sdk-sunswap-v2";
|
|
959
|
+
import {
|
|
960
|
+
SDKError as SDKError9,
|
|
961
|
+
assertTronAddress as assertTronAddress4
|
|
962
|
+
} from "@sun-protocol/sun-sdk-core";
|
|
963
|
+
function normalizeV2Mode(input) {
|
|
964
|
+
const mode = input.mode ?? "exact-in";
|
|
965
|
+
if (mode === "exact-in") {
|
|
966
|
+
normalizeBaseUnitAmount(input.amountIn ?? "", "amountIn");
|
|
967
|
+
if (input.amountOut !== void 0) {
|
|
968
|
+
throw new SDKError9("SDK_INVALID_ARGUMENT", "exact-in V2 swap does not accept amountOut");
|
|
969
|
+
}
|
|
970
|
+
return mode;
|
|
971
|
+
}
|
|
972
|
+
normalizeBaseUnitAmount(input.amountOut ?? "", "amountOut");
|
|
973
|
+
if (input.amountIn !== void 0) {
|
|
974
|
+
throw new SDKError9("SDK_INVALID_ARGUMENT", "exact-out V2 swap does not accept amountIn");
|
|
975
|
+
}
|
|
976
|
+
return mode;
|
|
977
|
+
}
|
|
978
|
+
function requireAmount(amounts, index, operation) {
|
|
979
|
+
const amount = amounts[index];
|
|
980
|
+
if (amount === void 0) {
|
|
981
|
+
throw new SDKError9("SDK_CONTRACT_ERROR", "V2 router returned an incomplete amount path", {
|
|
982
|
+
operation,
|
|
983
|
+
amounts
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
return amount;
|
|
987
|
+
}
|
|
988
|
+
function requireAmountPath(amounts, path, operation) {
|
|
989
|
+
if (amounts.length !== path.length) {
|
|
990
|
+
throw new SDKError9("SDK_CONTRACT_ERROR", "V2 router returned an incomplete amount path", {
|
|
991
|
+
operation,
|
|
992
|
+
amounts,
|
|
993
|
+
path
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function normalizeAllowedTradingPairResult(result) {
|
|
998
|
+
if (typeof result === "boolean") return result;
|
|
999
|
+
if (Array.isArray(result) && typeof result[0] === "boolean") return result[0];
|
|
1000
|
+
return Boolean(result);
|
|
1001
|
+
}
|
|
1002
|
+
async function assertV2PathAllowed(context, path) {
|
|
1003
|
+
const runtime = requireRuntime(context, "V2 allowed trading pair check");
|
|
1004
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
1005
|
+
const tokenA = assertTronAddress4(path[index] ?? "", `path[${index}]`);
|
|
1006
|
+
const tokenB = assertTronAddress4(path[index + 1] ?? "", `path[${index + 1}]`);
|
|
1007
|
+
const allowed = normalizeAllowedTradingPairResult(await runtime.readContract(buildV2RouterAllowedTradingPairRead({
|
|
1008
|
+
network: context.network,
|
|
1009
|
+
tokenA,
|
|
1010
|
+
tokenB
|
|
1011
|
+
})));
|
|
1012
|
+
if (!allowed) {
|
|
1013
|
+
throw new SDKError9("SDK_NO_ROUTE", "SunSwap V2 router does not allow this trading pair", {
|
|
1014
|
+
operation: "v2-allowed-trading-pair-check",
|
|
1015
|
+
blockedPair: [tokenA, tokenB],
|
|
1016
|
+
path
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
async function quoteV2Swap(context, input) {
|
|
1022
|
+
const runtime = requireRuntime(context, "V2 swap quote");
|
|
1023
|
+
const mode = normalizeV2Mode(input);
|
|
1024
|
+
const pathInfo = normalizeV2SwapPath(context, input);
|
|
1025
|
+
await assertV2PathAllowed(context, pathInfo.path);
|
|
1026
|
+
if (mode === "exact-in") {
|
|
1027
|
+
const amountIn = normalizeBaseUnitAmount(input.amountIn ?? "", "amountIn");
|
|
1028
|
+
const amounts2 = normalizeV2AmountsResult(await runtime.readContract(buildV2RouterAmountsOutRead({
|
|
1029
|
+
network: context.network,
|
|
1030
|
+
amountIn,
|
|
1031
|
+
path: pathInfo.path
|
|
1032
|
+
})));
|
|
1033
|
+
requireAmountPath(amounts2, pathInfo.path, "v2-exact-in-quote");
|
|
1034
|
+
return {
|
|
1035
|
+
kind: "v2",
|
|
1036
|
+
source: "v2",
|
|
1037
|
+
mode,
|
|
1038
|
+
direction: pathInfo.direction,
|
|
1039
|
+
tokenIn: input.tokenIn,
|
|
1040
|
+
tokenOut: input.tokenOut,
|
|
1041
|
+
amountIn,
|
|
1042
|
+
amountOut: requireAmount(amounts2, amounts2.length - 1, "v2-exact-in-quote"),
|
|
1043
|
+
path: pathInfo.path
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
const amountOut = normalizeBaseUnitAmount(input.amountOut ?? "", "amountOut");
|
|
1047
|
+
const amounts = normalizeV2AmountsResult(await runtime.readContract(buildV2RouterAmountsInRead({
|
|
1048
|
+
network: context.network,
|
|
1049
|
+
amountOut,
|
|
1050
|
+
path: pathInfo.path
|
|
1051
|
+
})));
|
|
1052
|
+
requireAmountPath(amounts, pathInfo.path, "v2-exact-out-quote");
|
|
1053
|
+
return {
|
|
1054
|
+
kind: "v2",
|
|
1055
|
+
source: "v2",
|
|
1056
|
+
mode,
|
|
1057
|
+
direction: pathInfo.direction,
|
|
1058
|
+
tokenIn: input.tokenIn,
|
|
1059
|
+
tokenOut: input.tokenOut,
|
|
1060
|
+
amountIn: requireAmount(amounts, 0, "v2-exact-out-quote"),
|
|
1061
|
+
amountOut,
|
|
1062
|
+
path: pathInfo.path
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
async function planV2Swap(context, input) {
|
|
1066
|
+
const { address: owner } = await requireWalletSnapshot(context, "V2 swap plan");
|
|
1067
|
+
const quote = await quoteV2Swap(context, input);
|
|
1068
|
+
const recipient = await normalizeRecipient(context, input.recipient, "V2 swap recipient", owner);
|
|
1069
|
+
const transactionValidForSeconds = normalizeTransactionValidForSeconds(input.transactionValidForSeconds);
|
|
1070
|
+
const slippageBips = slippageToBips(input.slippage ?? "0.5%");
|
|
1071
|
+
const approvals = [];
|
|
1072
|
+
if (quote.mode === "exact-in" && quote.direction === "trx-to-token") {
|
|
1073
|
+
const minAmountOut = applySlippageMin(BigInt(quote.amountOut), slippageBips).toString();
|
|
1074
|
+
const transaction2 = createDeferredV2SwapAction({
|
|
1075
|
+
data: {
|
|
1076
|
+
operation: "swap-exact-eth-for-tokens",
|
|
1077
|
+
amountIn: quote.amountIn,
|
|
1078
|
+
amountOutMin: minAmountOut,
|
|
1079
|
+
path: quote.path,
|
|
1080
|
+
recipient,
|
|
1081
|
+
transactionValidForSeconds
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
return {
|
|
1085
|
+
id: nextPlanId(context, "swap-v2"),
|
|
1086
|
+
kind: "swap",
|
|
1087
|
+
network: context.network,
|
|
1088
|
+
owner,
|
|
1089
|
+
summary: {
|
|
1090
|
+
title: "Swap with SunSwap V2",
|
|
1091
|
+
lines: [`Spend ${quote.amountIn} sun`, `Receive at least ${minAmountOut} token units`]
|
|
1092
|
+
},
|
|
1093
|
+
approvals,
|
|
1094
|
+
transactions: [transaction2],
|
|
1095
|
+
metadata: {
|
|
1096
|
+
protocol: "sunswap-v2",
|
|
1097
|
+
mode: quote.mode,
|
|
1098
|
+
direction: quote.direction,
|
|
1099
|
+
amountIn: quote.amountIn,
|
|
1100
|
+
quotedAmountOut: quote.amountOut,
|
|
1101
|
+
minAmountOut,
|
|
1102
|
+
slippageBips,
|
|
1103
|
+
path: quote.path,
|
|
1104
|
+
recipient,
|
|
1105
|
+
transactionValidForSeconds
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
if (quote.mode === "exact-out" && quote.direction === "trx-to-token") {
|
|
1110
|
+
const maxAmountIn2 = applySlippageMax(BigInt(quote.amountIn), slippageBips).toString();
|
|
1111
|
+
const transaction2 = createDeferredV2SwapAction({
|
|
1112
|
+
data: {
|
|
1113
|
+
operation: "swap-eth-for-exact-tokens",
|
|
1114
|
+
amountOut: quote.amountOut,
|
|
1115
|
+
amountInMax: maxAmountIn2,
|
|
1116
|
+
path: quote.path,
|
|
1117
|
+
recipient,
|
|
1118
|
+
transactionValidForSeconds
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
return {
|
|
1122
|
+
id: nextPlanId(context, "swap-v2"),
|
|
1123
|
+
kind: "swap",
|
|
1124
|
+
network: context.network,
|
|
1125
|
+
owner,
|
|
1126
|
+
summary: {
|
|
1127
|
+
title: "Swap with SunSwap V2",
|
|
1128
|
+
lines: [`Spend at most ${maxAmountIn2} sun`, `Receive ${quote.amountOut} token units`]
|
|
1129
|
+
},
|
|
1130
|
+
approvals,
|
|
1131
|
+
transactions: [transaction2],
|
|
1132
|
+
metadata: {
|
|
1133
|
+
protocol: "sunswap-v2",
|
|
1134
|
+
mode: quote.mode,
|
|
1135
|
+
direction: quote.direction,
|
|
1136
|
+
amountOut: quote.amountOut,
|
|
1137
|
+
quotedAmountIn: quote.amountIn,
|
|
1138
|
+
maxAmountIn: maxAmountIn2,
|
|
1139
|
+
slippageBips,
|
|
1140
|
+
path: quote.path,
|
|
1141
|
+
recipient,
|
|
1142
|
+
transactionValidForSeconds
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
const tokenInputAmount = quote.mode === "exact-in" ? quote.amountIn : applySlippageMax(BigInt(quote.amountIn), slippageBips).toString();
|
|
1147
|
+
const approval = await planApproval(context, {
|
|
1148
|
+
asset: { type: "token", token: assertTronAddress4(input.tokenIn, "tokenIn") },
|
|
1149
|
+
owner,
|
|
1150
|
+
spender: context.chain.contracts.sunswapV2Router,
|
|
1151
|
+
amount: tokenInputAmount
|
|
1152
|
+
}, {
|
|
1153
|
+
mode: input.approval ?? "auto",
|
|
1154
|
+
amount: "exact"
|
|
1155
|
+
});
|
|
1156
|
+
approvals.push(...approval.actions);
|
|
1157
|
+
if (quote.mode === "exact-in") {
|
|
1158
|
+
const minAmountOut = applySlippageMin(BigInt(quote.amountOut), slippageBips).toString();
|
|
1159
|
+
const transaction2 = quote.direction === "token-to-trx" ? createDeferredV2SwapAction({
|
|
1160
|
+
data: {
|
|
1161
|
+
operation: "swap-exact-tokens-for-eth",
|
|
1162
|
+
amountIn: quote.amountIn,
|
|
1163
|
+
amountOutMin: minAmountOut,
|
|
1164
|
+
path: quote.path,
|
|
1165
|
+
recipient,
|
|
1166
|
+
transactionValidForSeconds
|
|
1167
|
+
}
|
|
1168
|
+
}) : createDeferredV2SwapAction({
|
|
1169
|
+
data: {
|
|
1170
|
+
operation: "swap-exact-tokens-for-tokens",
|
|
1171
|
+
amountIn: quote.amountIn,
|
|
1172
|
+
amountOutMin: minAmountOut,
|
|
1173
|
+
path: quote.path,
|
|
1174
|
+
recipient,
|
|
1175
|
+
transactionValidForSeconds
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
return {
|
|
1179
|
+
id: nextPlanId(context, "swap-v2"),
|
|
1180
|
+
kind: "swap",
|
|
1181
|
+
network: context.network,
|
|
1182
|
+
owner,
|
|
1183
|
+
summary: {
|
|
1184
|
+
title: "Swap with SunSwap V2",
|
|
1185
|
+
lines: [
|
|
1186
|
+
`Sell ${quote.amountIn} token units`,
|
|
1187
|
+
`Receive at least ${minAmountOut} ${quote.direction === "token-to-trx" ? "sun" : "token units"}`
|
|
1188
|
+
]
|
|
1189
|
+
},
|
|
1190
|
+
approvals,
|
|
1191
|
+
transactions: [transaction2],
|
|
1192
|
+
metadata: {
|
|
1193
|
+
protocol: "sunswap-v2",
|
|
1194
|
+
mode: quote.mode,
|
|
1195
|
+
direction: quote.direction,
|
|
1196
|
+
amountIn: quote.amountIn,
|
|
1197
|
+
quotedAmountOut: quote.amountOut,
|
|
1198
|
+
minAmountOut,
|
|
1199
|
+
slippageBips,
|
|
1200
|
+
approvalMode: input.approval ?? "auto",
|
|
1201
|
+
path: quote.path,
|
|
1202
|
+
recipient,
|
|
1203
|
+
transactionValidForSeconds,
|
|
1204
|
+
owner,
|
|
1205
|
+
spender: context.chain.contracts.sunswapV2Router
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
const maxAmountIn = tokenInputAmount;
|
|
1210
|
+
const transaction = quote.direction === "token-to-trx" ? createDeferredV2SwapAction({
|
|
1211
|
+
data: {
|
|
1212
|
+
operation: "swap-tokens-for-exact-eth",
|
|
1213
|
+
amountOut: quote.amountOut,
|
|
1214
|
+
amountInMax: maxAmountIn,
|
|
1215
|
+
path: quote.path,
|
|
1216
|
+
recipient,
|
|
1217
|
+
transactionValidForSeconds
|
|
1218
|
+
}
|
|
1219
|
+
}) : createDeferredV2SwapAction({
|
|
1220
|
+
data: {
|
|
1221
|
+
operation: "swap-tokens-for-exact-tokens",
|
|
1222
|
+
amountOut: quote.amountOut,
|
|
1223
|
+
amountInMax: maxAmountIn,
|
|
1224
|
+
path: quote.path,
|
|
1225
|
+
recipient,
|
|
1226
|
+
transactionValidForSeconds
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
return {
|
|
1230
|
+
id: nextPlanId(context, "swap-v2"),
|
|
1231
|
+
kind: "swap",
|
|
1232
|
+
network: context.network,
|
|
1233
|
+
owner,
|
|
1234
|
+
summary: {
|
|
1235
|
+
title: "Swap with SunSwap V2",
|
|
1236
|
+
lines: [
|
|
1237
|
+
`Spend at most ${maxAmountIn} token units`,
|
|
1238
|
+
`Receive ${quote.amountOut} ${quote.direction === "token-to-trx" ? "sun" : "token units"}`
|
|
1239
|
+
]
|
|
1240
|
+
},
|
|
1241
|
+
approvals,
|
|
1242
|
+
transactions: [transaction],
|
|
1243
|
+
metadata: {
|
|
1244
|
+
protocol: "sunswap-v2",
|
|
1245
|
+
mode: quote.mode,
|
|
1246
|
+
direction: quote.direction,
|
|
1247
|
+
amountOut: quote.amountOut,
|
|
1248
|
+
quotedAmountIn: quote.amountIn,
|
|
1249
|
+
maxAmountIn,
|
|
1250
|
+
slippageBips,
|
|
1251
|
+
approvalMode: input.approval ?? "auto",
|
|
1252
|
+
path: quote.path,
|
|
1253
|
+
recipient,
|
|
1254
|
+
transactionValidForSeconds,
|
|
1255
|
+
owner,
|
|
1256
|
+
spender: context.chain.contracts.sunswapV2Router
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
var V2SwapClient = class {
|
|
1261
|
+
constructor(context) {
|
|
1262
|
+
this.context = context;
|
|
1263
|
+
}
|
|
1264
|
+
context;
|
|
1265
|
+
quote(_input) {
|
|
1266
|
+
return quoteV2Swap(this.context, _input);
|
|
1267
|
+
}
|
|
1268
|
+
plan(input) {
|
|
1269
|
+
return planV2Swap(this.context, input);
|
|
1270
|
+
}
|
|
1271
|
+
executeAction(action) {
|
|
1272
|
+
return executePlanAction(action, createV2SwapExecutor(this.context));
|
|
1273
|
+
}
|
|
1274
|
+
submit(plan) {
|
|
1275
|
+
assertSwapPlan(plan, ["sunswap-v2"], "SunSwap V2");
|
|
1276
|
+
return executePlanTransactions(plan, createV2SwapExecutor(this.context));
|
|
1277
|
+
}
|
|
1278
|
+
async execute(input) {
|
|
1279
|
+
const plan = isTransactionPlan(input) ? input : await planV2Swap(this.context, input);
|
|
1280
|
+
assertSwapPlan(plan, ["sunswap-v2"], "SunSwap V2");
|
|
1281
|
+
return executeTransactionPlan(plan, createV2SwapExecutor(this.context));
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
function createV2SwapClient(context) {
|
|
1285
|
+
return new V2SwapClient(context);
|
|
1286
|
+
}
|
|
1287
|
+
function createV2Client(options) {
|
|
1288
|
+
return createV2SwapClient(createProtocolsContext(options));
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
export {
|
|
1292
|
+
UNIVERSAL_ROUTER_PROTOCOL_ID,
|
|
1293
|
+
PSM_PROTOCOL_ID,
|
|
1294
|
+
WTRX_PROTOCOL_ID,
|
|
1295
|
+
readPsm,
|
|
1296
|
+
quotePsmSwap,
|
|
1297
|
+
planPsmSwap,
|
|
1298
|
+
PsmSwapClient,
|
|
1299
|
+
createPsmSwapClient,
|
|
1300
|
+
createPsmClient,
|
|
1301
|
+
materializeUniversalRouterAction,
|
|
1302
|
+
createRouterSwapExecutor,
|
|
1303
|
+
isNativeTrxToken,
|
|
1304
|
+
normalizeRouterToken,
|
|
1305
|
+
normalizeV2TrxPairPath,
|
|
1306
|
+
isWrappedTrxToken,
|
|
1307
|
+
detectWtrxSwap,
|
|
1308
|
+
planRouterSwap,
|
|
1309
|
+
quoteRouterSwap,
|
|
1310
|
+
quoteSwap,
|
|
1311
|
+
SwapRouterClient,
|
|
1312
|
+
createSwapRouterClient,
|
|
1313
|
+
V2SwapClient,
|
|
1314
|
+
createV2SwapClient,
|
|
1315
|
+
createV2Client
|
|
1316
|
+
};
|
|
1317
|
+
//# sourceMappingURL=chunk-ECCGOXNA.js.map
|