@xoxno/sdk-js 1.0.223 → 1.0.225
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/cjs/sdk/stellar/__tests__/admin.test.d.ts +0 -12
- package/dist/cjs/sdk/stellar/__tests__/builders-hubasset.test.d.ts +0 -11
- package/dist/cjs/sdk/stellar/__tests__/governance.test.d.ts +0 -4
- package/dist/cjs/sdk/stellar/__tests__/lending.test.d.ts +0 -20
- package/dist/cjs/sdk/stellar/__tests__/strategy-program.test.d.ts +0 -8
- package/dist/cjs/sdk/stellar/__tests__/swap-history.test.d.ts +1 -0
- package/dist/cjs/sdk/stellar/index.d.ts +1 -0
- package/dist/cjs/sdk/stellar/swap-history.d.ts +25 -0
- package/dist/cjs/sdk/stellar/swap-index.d.ts +2 -0
- package/dist/cjs/sdk/stellar/xdr-compat.d.ts +11 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/sdk/index.js +198 -0
- package/dist/esm/sdk/stellar/admin.js +385 -0
- package/dist/esm/sdk/stellar/auth-policy.js +310 -0
- package/dist/esm/sdk/stellar/cctp.js +23 -0
- package/dist/esm/sdk/stellar/contracts.js +49 -0
- package/dist/esm/sdk/stellar/events/decode.js +415 -0
- package/dist/esm/sdk/stellar/events/id.js +62 -0
- package/dist/esm/sdk/stellar/events/index.js +2 -0
- package/dist/esm/sdk/stellar/governance.js +403 -0
- package/dist/esm/sdk/stellar/index.js +25 -0
- package/dist/esm/sdk/stellar/lending-api-types.js +3 -0
- package/dist/esm/sdk/stellar/lending-read-types.js +1 -0
- package/dist/esm/sdk/stellar/lending-read.js +341 -0
- package/dist/esm/sdk/stellar/lending.js +358 -0
- package/dist/esm/sdk/stellar/math/account.js +256 -0
- package/dist/esm/sdk/stellar/math/index.js +11 -0
- package/dist/esm/sdk/stellar/math/limits.js +273 -0
- package/dist/esm/sdk/stellar/math/projection.js +130 -0
- package/dist/esm/sdk/stellar/math/rates.js +62 -0
- package/dist/esm/sdk/stellar/math/scaled.js +156 -0
- package/dist/esm/sdk/stellar/position-mode.js +11 -0
- package/dist/esm/sdk/stellar/prepare.js +47 -0
- package/dist/esm/sdk/stellar/quote.js +87 -0
- package/dist/esm/sdk/stellar/repay-swap.js +12 -0
- package/dist/esm/sdk/stellar/route-verify.js +285 -0
- package/dist/esm/sdk/stellar/scval-encode.js +464 -0
- package/dist/esm/sdk/stellar/swap-history.js +46 -0
- package/dist/esm/sdk/stellar/swap-index.js +1 -0
- package/dist/esm/sdk/stellar/swap.js +117 -0
- package/dist/esm/sdk/stellar/xdr-compat.js +17 -0
- package/dist/esm/sdk/stellar-schema.js +129 -0
- package/dist/esm/sdk/swagger.js +1957 -0
- package/dist/esm/sdk/types.js +1 -0
- package/dist/esm/sdk/utils.js +21 -0
- package/dist/esm/utils/api.js +169 -0
- package/dist/esm/utils/const.js +11 -0
- package/dist/esm/utils/errors.js +20 -0
- package/dist/esm/utils/guards.js +29 -0
- package/dist/esm/utils/helpers.js +20 -0
- package/dist/esm/utils/regex.js +14 -0
- package/dist/esm/utils/soroban-errors.js +149 -0
- package/dist/index.cjs +1 -1
- package/dist/index.esm.js +1 -2
- package/dist/sdk/stellar/__tests__/admin.test.d.ts +0 -12
- package/dist/sdk/stellar/__tests__/builders-hubasset.test.d.ts +0 -11
- package/dist/sdk/stellar/__tests__/governance.test.d.ts +0 -4
- package/dist/sdk/stellar/__tests__/lending.test.d.ts +0 -20
- package/dist/sdk/stellar/__tests__/strategy-program.test.d.ts +0 -8
- package/dist/sdk/stellar/__tests__/swap-history.test.d.ts +1 -0
- package/dist/sdk/stellar/index.cjs +1 -1
- package/dist/sdk/stellar/index.d.ts +1 -0
- package/dist/sdk/stellar/index.esm.js +1 -1
- package/dist/sdk/stellar/swap-history.d.ts +25 -0
- package/dist/sdk/stellar/swap-index.cjs +2 -0
- package/dist/sdk/stellar/swap-index.d.ts +2 -0
- package/dist/sdk/stellar/swap-index.esm.js +2 -0
- package/dist/sdk/stellar/swap-index.esm.js.LICENSE.txt +8 -0
- package/dist/sdk/stellar/xdr-compat.d.ts +11 -0
- package/package.json +16 -5
- /package/dist/{index.esm.js.LICENSE.txt → sdk/stellar/swap-index.cjs.LICENSE.txt} +0 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Soroban `ScVal` encoding primitives for the Stellar lending builders.
|
|
16
|
+
*
|
|
17
|
+
* i128/u128 values cross the boundary as decimal strings (`new ScInt(str)`).
|
|
18
|
+
* Addresses (Stellar `G...` accounts and Soroban `C...` contracts) encode via
|
|
19
|
+
* `new Address(str).toScVal()`.
|
|
20
|
+
*/ import { Address, ScInt, xdr } from "@stellar/stellar-sdk";
|
|
21
|
+
import { Buffer } from "buffer";
|
|
22
|
+
/**
|
|
23
|
+
* On-chain swap venues, validated at runtime for a caller-supplied venue.
|
|
24
|
+
* Mirrors the Soroban-only aggregator router's opcode table.
|
|
25
|
+
*/ export const STELLAR_SWAP_VENUES = [
|
|
26
|
+
'Soroswap',
|
|
27
|
+
'Aquarius',
|
|
28
|
+
'Phoenix',
|
|
29
|
+
'Sushi',
|
|
30
|
+
'CometDex'
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Instruction opcode per venue. Part of the on-chain ABI — see
|
|
34
|
+
* `contracts/swap-aggregator/src/program.rs`.
|
|
35
|
+
*/ export const STELLAR_SWAP_VENUE_OPCODE = {
|
|
36
|
+
Soroswap: 0,
|
|
37
|
+
Aquarius: 1,
|
|
38
|
+
Phoenix: 2,
|
|
39
|
+
Sushi: 3,
|
|
40
|
+
CometDex: 4
|
|
41
|
+
};
|
|
42
|
+
/** Wire version of the packed program. Must match the contract's `VERSION`. */ export const STELLAR_PROGRAM_VERSION = 1;
|
|
43
|
+
/** Split weights are parts per million. */ export const PPM_DENOMINATOR = 1000000;
|
|
44
|
+
export const OP_BURN = 5;
|
|
45
|
+
export const OP_MINT = 6;
|
|
46
|
+
export const MODE_ALL = 0;
|
|
47
|
+
export const MODE_PREV = 1;
|
|
48
|
+
export const MODE_FIXED_BASE = 2;
|
|
49
|
+
export const MODE_PPM_BASE = 128;
|
|
50
|
+
export const MAX_OPS = 48;
|
|
51
|
+
export const MAX_WEIGHTS = 32;
|
|
52
|
+
export const MAX_ASSETS = 256;
|
|
53
|
+
export const MAX_AMOUNTS = MODE_PPM_BASE - MODE_FIXED_BASE;
|
|
54
|
+
export const addr = (a)=>new Address(a).toScVal();
|
|
55
|
+
export const i128 = (s)=>new ScInt(s).toI128();
|
|
56
|
+
export const u32 = (n)=>xdr.ScVal.scvU32(n);
|
|
57
|
+
export const u64 = (n)=>{
|
|
58
|
+
if (typeof n === 'number' && !Number.isSafeInteger(n)) {
|
|
59
|
+
throw new Error('Stellar builder: u64 numbers must be safe integers; pass a decimal string for larger values');
|
|
60
|
+
}
|
|
61
|
+
return new ScInt(typeof n === 'string' ? n : n.toString()).toU64();
|
|
62
|
+
};
|
|
63
|
+
export const bool = (b)=>xdr.ScVal.scvBool(b);
|
|
64
|
+
export const str = (s)=>xdr.ScVal.scvString(s);
|
|
65
|
+
export const sym = (s)=>xdr.ScVal.scvSymbol(s);
|
|
66
|
+
export const voidVal = ()=>xdr.ScVal.scvVoid();
|
|
67
|
+
export const vec = (items)=>xdr.ScVal.scvVec(items);
|
|
68
|
+
export const bytes = (hex)=>{
|
|
69
|
+
const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
70
|
+
const buf = Buffer.from(cleanHex, 'hex');
|
|
71
|
+
return xdr.ScVal.scvBytes(buf);
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Encode a Soroban `BytesN<N>` (default 32) — a fixed-length byte array such as
|
|
75
|
+
* a WASM hash. Validates the decoded length so a malformed hash fails at the
|
|
76
|
+
* SDK boundary, not deep inside the host.
|
|
77
|
+
*/ export const bytesN = (hex, length = 32)=>{
|
|
78
|
+
const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
79
|
+
const buf = Buffer.from(cleanHex, 'hex');
|
|
80
|
+
if (buf.length !== length) {
|
|
81
|
+
throw new Error(`Stellar builder: expected a ${length}-byte BytesN (got ${buf.length} bytes from "${hex}")`);
|
|
82
|
+
}
|
|
83
|
+
return xdr.ScVal.scvBytes(buf);
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Encode a Soroban `Option<T>`: `None` is `scvVoid`, `Some(x)` is the bare
|
|
87
|
+
* encoded `x` (Soroban does not wrap `Some`). NOT for the contract's custom
|
|
88
|
+
* `OracleSourceConfigInputOption` enum, which is a tagged union — see
|
|
89
|
+
* `encodeOracleSourceConfigInputOption` in `admin.ts`.
|
|
90
|
+
*/ export const option = (value, encode)=>value === undefined || value === null ? voidVal() : encode(value);
|
|
91
|
+
/**
|
|
92
|
+
* Encode a single Soroban tuple `(Address, i128)` as a 2-element `scvVec`.
|
|
93
|
+
*/ export const tupleAddrAmount = (token, amount)=>xdr.ScVal.scvVec([
|
|
94
|
+
addr(token),
|
|
95
|
+
i128(amount)
|
|
96
|
+
]);
|
|
97
|
+
/**
|
|
98
|
+
* Encode `Vec<(Address, i128)>` — a Soroban tuple-vec of (asset, amount).
|
|
99
|
+
* Each tuple is a 2-element `scvVec`.
|
|
100
|
+
*/ export const tupleAddrAmountVec = (entries)=>xdr.ScVal.scvVec(entries.map((e)=>tupleAddrAmount(e.token, e.amount)));
|
|
101
|
+
/**
|
|
102
|
+
* Encode a `HubAssetKey` `#[contracttype]` struct — the `(hub_id, asset)`
|
|
103
|
+
* coordinate that keys liquidity and positions on the multi-hub controller.
|
|
104
|
+
* Emitted as an ScVal map with symbol keys in lexicographic order
|
|
105
|
+
* (`asset`, then `hub_id`).
|
|
106
|
+
*/ export const hubAsset = (hubId, asset)=>scStruct({
|
|
107
|
+
asset: addr(asset),
|
|
108
|
+
hub_id: u32(hubId)
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Encode a single Soroban tuple `(HubAssetKey, i128)` as a 2-element `scvVec`.
|
|
112
|
+
*/ export const tupleHubAssetAmount = (hubId, asset, amount)=>xdr.ScVal.scvVec([
|
|
113
|
+
hubAsset(hubId, asset),
|
|
114
|
+
i128(amount)
|
|
115
|
+
]);
|
|
116
|
+
/**
|
|
117
|
+
* Encode `Vec<(HubAssetKey, i128)>` — the multi-hub batch payload carried by
|
|
118
|
+
* `supply` / `borrow` / `withdraw` / `repay` / `liquidate`.
|
|
119
|
+
*/ export const tupleHubAssetAmountVec = (entries)=>xdr.ScVal.scvVec(entries.map((e)=>tupleHubAssetAmount(e.hubId, e.asset, e.amount)));
|
|
120
|
+
/**
|
|
121
|
+
* Encode `SeizeMode`. Soroban serializes a `#[contracttype]` enum as
|
|
122
|
+
* `scvVec([scvSymbol(Variant), ...payload])` — the void variant carries only
|
|
123
|
+
* the symbol, `Credit` appends its `u64`. Same shape as `OracleReadMode`
|
|
124
|
+
* (`Spot` / `Twap(u32)`) in `admin.ts`.
|
|
125
|
+
*/ export const seizeMode = (mode)=>{
|
|
126
|
+
if (mode === 'Transfer') return vec([
|
|
127
|
+
sym('Transfer')
|
|
128
|
+
]);
|
|
129
|
+
if (mode && typeof mode === 'object' && 'Credit' in mode) {
|
|
130
|
+
const id = mode.Credit;
|
|
131
|
+
// `0` is the "open a fresh account" sentinel, so only reject genuinely
|
|
132
|
+
// unrepresentable ids here; `u64()` rejects negatives and overflow.
|
|
133
|
+
if (typeof id === 'number' && !Number.isInteger(id)) {
|
|
134
|
+
throw new Error(`Stellar builder: SeizeMode Credit account id must be an integer u64, got ${id}`);
|
|
135
|
+
}
|
|
136
|
+
return vec([
|
|
137
|
+
sym('Credit'),
|
|
138
|
+
u64(id)
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`Stellar builder: SeizeMode must be 'Transfer' or { Credit: accountId }, got ${JSON.stringify(mode)}`);
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Encode a Soroban `struct` — at the XDR level, structs are maps with
|
|
145
|
+
* `Symbol` keys in **lexicographic order** of field names.
|
|
146
|
+
*/ export const scStruct = (fields)=>{
|
|
147
|
+
const entries = Object.keys(fields).sort().map((k)=>new xdr.ScMapEntry({
|
|
148
|
+
key: sym(k),
|
|
149
|
+
val: fields[k]
|
|
150
|
+
}));
|
|
151
|
+
return xdr.ScVal.scvMap(entries);
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Lowers a strategy plan into the router's two registries plus the packed
|
|
155
|
+
* instruction stream.
|
|
156
|
+
*
|
|
157
|
+
* Mirrors `contracts/swap-aggregator/src/program.rs` and the Rust builder in
|
|
158
|
+
* `arb-algo`. Instructions reference addresses and amounts by `u8` index, so an
|
|
159
|
+
* address several hops share costs 40 bytes once rather than once per hop.
|
|
160
|
+
*/ class ProgramLowering {
|
|
161
|
+
/** Index of `address` in the registry, appending it if new. */ asset(address) {
|
|
162
|
+
const found = this.assets.indexOf(address);
|
|
163
|
+
if (found !== -1) return found;
|
|
164
|
+
if (this.assets.length >= MAX_ASSETS) {
|
|
165
|
+
throw new Error('Stellar builder: strategy references more than 256 distinct addresses');
|
|
166
|
+
}
|
|
167
|
+
this.assets.push(address);
|
|
168
|
+
return this.assets.length - 1;
|
|
169
|
+
}
|
|
170
|
+
/** Index of a newly appended amount. */ amount(value) {
|
|
171
|
+
if (this.amounts.length >= MAX_AMOUNTS) {
|
|
172
|
+
throw new Error('Stellar builder: strategy references too many distinct amounts');
|
|
173
|
+
}
|
|
174
|
+
this.amounts.push(value);
|
|
175
|
+
return this.amounts.length - 1;
|
|
176
|
+
}
|
|
177
|
+
/** Index of a newly appended split weight. */ weight(ppm) {
|
|
178
|
+
if (this.weights.length >= MAX_WEIGHTS) {
|
|
179
|
+
throw new Error('Stellar builder: strategy uses more than 32 split weights');
|
|
180
|
+
}
|
|
181
|
+
if (!Number.isInteger(ppm) || ppm <= 0 || ppm > PPM_DENOMINATOR) {
|
|
182
|
+
throw new Error(`Stellar builder: split weight ${ppm} outside 1..=${PPM_DENOMINATOR}`);
|
|
183
|
+
}
|
|
184
|
+
this.weights.push(ppm);
|
|
185
|
+
return this.weights.length - 1;
|
|
186
|
+
}
|
|
187
|
+
emit(opcode, mode, a, b, c) {
|
|
188
|
+
this.ops.push([
|
|
189
|
+
opcode,
|
|
190
|
+
mode,
|
|
191
|
+
a,
|
|
192
|
+
b,
|
|
193
|
+
c
|
|
194
|
+
]);
|
|
195
|
+
}
|
|
196
|
+
/** Emit one path: the head instruction sizes the input, the rest chain. */ emitPath(path, headMode) {
|
|
197
|
+
path.hops.forEach((hop, n)=>{
|
|
198
|
+
if (hop.tokenIn === hop.tokenOut) {
|
|
199
|
+
throw new Error('Stellar builder: hop swaps a token for itself');
|
|
200
|
+
}
|
|
201
|
+
const previous = path.hops[n - 1];
|
|
202
|
+
if (previous && hop.tokenIn !== previous.tokenOut) {
|
|
203
|
+
throw new Error("Stellar builder: hop does not chain onto its predecessor's output");
|
|
204
|
+
}
|
|
205
|
+
this.emit(STELLAR_SWAP_VENUE_OPCODE[hop.venue], n === 0 ? headMode : MODE_PREV, this.asset(hop.pool), this.asset(hop.tokenIn), this.asset(hop.tokenOut));
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Lower paths one starting-token group at a time.
|
|
210
|
+
*
|
|
211
|
+
* The contract measures a ppm instruction against the vault balance *at that
|
|
212
|
+
* moment*, so absolute weights are rewritten as successive shares of the
|
|
213
|
+
* shrinking remainder. A group that routes its whole balance ends on `All`,
|
|
214
|
+
* which absorbs ppm rounding exactly.
|
|
215
|
+
*/ lowerPaths(paths) {
|
|
216
|
+
const live = paths.filter((p)=>p.hops.length > 0);
|
|
217
|
+
const done = Array.from({
|
|
218
|
+
length: live.length
|
|
219
|
+
}, ()=>false);
|
|
220
|
+
for(let i = 0; i < live.length; i += 1){
|
|
221
|
+
if (done[i]) continue;
|
|
222
|
+
const groupToken = live[i].hops[0]?.tokenIn;
|
|
223
|
+
const members = [];
|
|
224
|
+
for(let j = i; j < live.length; j += 1){
|
|
225
|
+
if (live[j].hops[0]?.tokenIn === groupToken) {
|
|
226
|
+
members.push(j);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const total = members.reduce((sum, j)=>sum + live[j].splitPpm, 0);
|
|
230
|
+
if (total > PPM_DENOMINATOR) {
|
|
231
|
+
throw new Error(`Stellar builder: split weights for one token sum to ${total}, above ${PPM_DENOMINATOR}`);
|
|
232
|
+
}
|
|
233
|
+
const sweeps = total === PPM_DENOMINATOR;
|
|
234
|
+
let remaining = PPM_DENOMINATOR;
|
|
235
|
+
members.forEach((j, n)=>{
|
|
236
|
+
done[j] = true;
|
|
237
|
+
const path = live[j];
|
|
238
|
+
let mode;
|
|
239
|
+
if (n + 1 === members.length && sweeps) {
|
|
240
|
+
mode = MODE_ALL;
|
|
241
|
+
} else {
|
|
242
|
+
if (remaining === 0) {
|
|
243
|
+
throw new Error('Stellar builder: split weights exhaust the group before its last path');
|
|
244
|
+
}
|
|
245
|
+
const relative = Math.floor(path.splitPpm * PPM_DENOMINATOR / remaining);
|
|
246
|
+
remaining -= Math.min(path.splitPpm, remaining);
|
|
247
|
+
mode = MODE_PPM_BASE + this.weight(relative);
|
|
248
|
+
}
|
|
249
|
+
this.emitPath(path, mode);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Serialize header, instructions, and weights. */ toBytes(tokenIn, tokenOut, referralId) {
|
|
254
|
+
if (this.ops.length === 0) {
|
|
255
|
+
throw new Error('Stellar builder: strategy lowered to zero instructions');
|
|
256
|
+
}
|
|
257
|
+
if (this.ops.length > MAX_OPS) {
|
|
258
|
+
throw new Error(`Stellar builder: strategy has ${this.ops.length} instructions, the router accepts ${MAX_OPS}`);
|
|
259
|
+
}
|
|
260
|
+
const out = [
|
|
261
|
+
STELLAR_PROGRAM_VERSION,
|
|
262
|
+
tokenIn,
|
|
263
|
+
tokenOut,
|
|
264
|
+
0,
|
|
265
|
+
referralId >>> 24 & 0xff,
|
|
266
|
+
referralId >>> 16 & 0xff,
|
|
267
|
+
referralId >>> 8 & 0xff,
|
|
268
|
+
referralId & 0xff,
|
|
269
|
+
this.ops.length,
|
|
270
|
+
this.weights.length
|
|
271
|
+
];
|
|
272
|
+
for (const op of this.ops)out.push(...op);
|
|
273
|
+
for (const ppm of this.weights){
|
|
274
|
+
out.push(ppm >>> 16 & 0xff, ppm >>> 8 & 0xff, ppm & 0xff);
|
|
275
|
+
}
|
|
276
|
+
return Buffer.from(out);
|
|
277
|
+
}
|
|
278
|
+
get assetList() {
|
|
279
|
+
return this.assets;
|
|
280
|
+
}
|
|
281
|
+
get amountList() {
|
|
282
|
+
return this.amounts;
|
|
283
|
+
}
|
|
284
|
+
constructor(totalMinOut){
|
|
285
|
+
_define_property(this, "assets", []);
|
|
286
|
+
_define_property(this, "amounts", []);
|
|
287
|
+
_define_property(this, "ops", []);
|
|
288
|
+
_define_property(this, "weights", []);
|
|
289
|
+
// Slot 0 is the strategy-wide minimum output.
|
|
290
|
+
this.amounts.push(totalMinOut);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Encode the aggregator router's `StrategyPayload`. The caller usually receives
|
|
295
|
+
* this already encoded as quote `routeXdr`; this helper is for tests and for
|
|
296
|
+
* callers that construct routes locally.
|
|
297
|
+
*/ export const encodeStrategyPayload = (payload)=>{
|
|
298
|
+
const low = new ProgramLowering(payload.totalMinOut);
|
|
299
|
+
const tokenIn = low.asset(payload.tokenIn);
|
|
300
|
+
const tokenOut = low.asset(payload.tokenOut);
|
|
301
|
+
if (tokenIn === tokenOut) {
|
|
302
|
+
throw new Error('Stellar builder: `tokenIn` and `tokenOut` are the same asset');
|
|
303
|
+
}
|
|
304
|
+
if (payload.burnPool) {
|
|
305
|
+
const pool = low.asset(payload.burnPool);
|
|
306
|
+
const start = low.amountList.length;
|
|
307
|
+
for (const min of payload.burnMinAmounts ?? [])low.amount(min);
|
|
308
|
+
// A burn consumes the whole LP balance, which is `tokenIn`.
|
|
309
|
+
low.emit(OP_BURN, MODE_ALL, pool, tokenIn, start);
|
|
310
|
+
}
|
|
311
|
+
low.lowerPaths(payload.paths);
|
|
312
|
+
if (payload.mintPool) {
|
|
313
|
+
const preSwap = payload.preSwapAmount;
|
|
314
|
+
if (preSwap && BigInt(preSwap) > 0n) {
|
|
315
|
+
const tokens = payload.mintPoolTokens;
|
|
316
|
+
if (!tokens || tokens.length !== 2) {
|
|
317
|
+
throw new Error('Stellar builder: `preSwapAmount` needs `mintPoolTokens` to name the pool’s two constituents');
|
|
318
|
+
}
|
|
319
|
+
const [tokenA, tokenB] = tokens;
|
|
320
|
+
const [from, to] = payload.preSwapFromA ? [
|
|
321
|
+
tokenA,
|
|
322
|
+
tokenB
|
|
323
|
+
] : [
|
|
324
|
+
tokenB,
|
|
325
|
+
tokenA
|
|
326
|
+
];
|
|
327
|
+
low.emit(STELLAR_SWAP_VENUE_OPCODE.Aquarius, MODE_FIXED_BASE + low.amount(preSwap), low.asset(payload.mintPool), low.asset(from), low.asset(to));
|
|
328
|
+
}
|
|
329
|
+
// The mint credits `tokenOut`, the pool's share token.
|
|
330
|
+
low.emit(OP_MINT, MODE_ALL, low.asset(payload.mintPool), tokenOut, low.amount(payload.mintMinShares ?? '1'));
|
|
331
|
+
}
|
|
332
|
+
const referralId = Number(payload.referralId ?? 0);
|
|
333
|
+
if (!Number.isInteger(referralId) || referralId < 0 || referralId > 0xffffffff) {
|
|
334
|
+
throw new Error(`Stellar builder: referral id ${payload.referralId} does not fit the wire’s u32`);
|
|
335
|
+
}
|
|
336
|
+
return scStruct({
|
|
337
|
+
amounts: xdr.ScVal.scvVec(low.amountList.map((a)=>i128(a))),
|
|
338
|
+
assets: xdr.ScVal.scvVec(low.assetList.map((a)=>addr(a))),
|
|
339
|
+
ops: xdr.ScVal.scvBytes(low.toBytes(tokenIn, tokenOut, referralId))
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
export const encodeStrategyPayloadToBytes = (payload)=>xdr.ScVal.scvBytes(Buffer.from(encodeStrategyPayload(payload).toXDR('base64'), 'base64'));
|
|
343
|
+
/**
|
|
344
|
+
* Validate the untyped `steps` field and narrow it to `StrategyPayload`.
|
|
345
|
+
* Throws if the caller passed the wrong shape so the failure surfaces at the
|
|
346
|
+
* SDK boundary, not inside the Soroban host on-chain.
|
|
347
|
+
*/ export const asStellarStrategyPayload = (steps)=>{
|
|
348
|
+
if (!steps || typeof steps !== 'object') {
|
|
349
|
+
throw new Error('Stellar builder: decoded strategy `steps` must be a StrategyPayload ({ paths, tokenIn, tokenOut, totalMinOut })');
|
|
350
|
+
}
|
|
351
|
+
const candidate = steps;
|
|
352
|
+
if (!Array.isArray(candidate.paths) || candidate.paths.length === 0 && !candidate.burnPool && !candidate.mintPool) {
|
|
353
|
+
throw new Error('Stellar builder: `steps.paths` needs a strategy path or a liquidity burn/mint operation');
|
|
354
|
+
}
|
|
355
|
+
if (typeof candidate.tokenIn !== 'string') {
|
|
356
|
+
throw new Error('Stellar builder: `steps.tokenIn` must be a contract address');
|
|
357
|
+
}
|
|
358
|
+
if (typeof candidate.tokenOut !== 'string') {
|
|
359
|
+
throw new Error('Stellar builder: `steps.tokenOut` must be a contract address');
|
|
360
|
+
}
|
|
361
|
+
if (typeof candidate.totalMinOut !== 'string') {
|
|
362
|
+
throw new Error('Stellar builder: `steps.totalMinOut` must be an i128 decimal string');
|
|
363
|
+
}
|
|
364
|
+
// Weights are grouped by the token a path starts from: parallel legs out of
|
|
365
|
+
// the same token share one budget, and a burn releases several tokens that
|
|
366
|
+
// each get their own.
|
|
367
|
+
const groupPpm = new Map();
|
|
368
|
+
for (const [idx, path] of candidate.paths.entries()){
|
|
369
|
+
if (!path || typeof path.splitPpm !== 'number' || path.splitPpm <= 0 || !Array.isArray(path.hops) || path.hops.length === 0) {
|
|
370
|
+
throw new Error(`Stellar builder: \`steps.paths[${idx}]\` must have splitPpm > 0 and a non-empty hops array`);
|
|
371
|
+
}
|
|
372
|
+
for (const [hopIdx, hop] of path.hops.entries()){
|
|
373
|
+
if (!hop || typeof hop.pool !== 'string' || typeof hop.tokenIn !== 'string' || typeof hop.tokenOut !== 'string' || !STELLAR_SWAP_VENUES.includes(hop.venue)) {
|
|
374
|
+
throw new Error(`Stellar builder: \`steps.paths[${idx}].hops[${hopIdx}]\` must have pool, tokenIn, tokenOut, and a valid Soroban venue`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const head = path.hops[0];
|
|
378
|
+
groupPpm.set(head.tokenIn, (groupPpm.get(head.tokenIn) ?? 0) + path.splitPpm);
|
|
379
|
+
}
|
|
380
|
+
for (const [token, sum] of groupPpm){
|
|
381
|
+
if (sum > PPM_DENOMINATOR) {
|
|
382
|
+
throw new Error(`Stellar builder: \`steps.paths[].splitPpm\` for ${token} sums to ${sum}, above ${PPM_DENOMINATOR}`);
|
|
383
|
+
}
|
|
384
|
+
// Without a mint leg every routed token must be fully consumed, or the
|
|
385
|
+
// router's residual guard rejects the strategy on-chain.
|
|
386
|
+
if (!candidate.mintPool && sum !== PPM_DENOMINATOR) {
|
|
387
|
+
throw new Error(`Stellar builder: \`steps.paths[].splitPpm\` for ${token} must sum to ${PPM_DENOMINATOR}, got ${sum}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return candidate;
|
|
391
|
+
};
|
|
392
|
+
const isHexString = (s)=>s.length > 0 && s.length % 2 === 0 && /^[0-9a-fA-F]+$/.test(s);
|
|
393
|
+
const strategyBytes = (data)=>{
|
|
394
|
+
const buf = Buffer.from(data);
|
|
395
|
+
if (buf.length === 0) {
|
|
396
|
+
throw new Error('Stellar builder: strategy swap bytes must be non-empty base64 routeXdr, 0x hex string, or Uint8Array');
|
|
397
|
+
}
|
|
398
|
+
return xdr.ScVal.scvBytes(buf);
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* Genuinely empty strategy swap bytes. Required by `repay_debt_with_collateral`
|
|
402
|
+
* and `swap_debt` when the collateral/borrowed asset is identical to the debt
|
|
403
|
+
* asset — the controller asserts `swap.is_empty()` in that case and reverts
|
|
404
|
+
* `InvalidPayments` if any route is supplied, even an unused placeholder.
|
|
405
|
+
*/ export const emptyStrategySwapBytes = ()=>xdr.ScVal.scvBytes(Buffer.alloc(0));
|
|
406
|
+
const decodeStrategyBytesString = (encoded)=>{
|
|
407
|
+
const trimmed = encoded.trim();
|
|
408
|
+
if (trimmed.startsWith('0x')) {
|
|
409
|
+
const hex = trimmed.slice(2);
|
|
410
|
+
if (!isHexString(hex)) {
|
|
411
|
+
throw new Error('Stellar builder: strategy swap bytes hex string must be 0x-prefixed and even-length');
|
|
412
|
+
}
|
|
413
|
+
return Buffer.from(hex, 'hex');
|
|
414
|
+
}
|
|
415
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(trimmed)) {
|
|
416
|
+
throw new Error('Stellar builder: strategy swap bytes must be base64 routeXdr or 0x-prefixed hex');
|
|
417
|
+
}
|
|
418
|
+
const decoded = Buffer.from(trimmed, 'base64');
|
|
419
|
+
if (decoded.length === 0) {
|
|
420
|
+
throw new Error('Stellar builder: strategy swap bytes must be non-empty base64 routeXdr, 0x hex string, or Uint8Array');
|
|
421
|
+
}
|
|
422
|
+
return decoded;
|
|
423
|
+
};
|
|
424
|
+
const hasRouteXdr = (input)=>'routeXdr' in input && typeof input.routeXdr === 'string';
|
|
425
|
+
const hasSwapXdr = (input)=>'swapXdr' in input && typeof input.swapXdr === 'string';
|
|
426
|
+
const hasBytes = (input)=>'bytes' in input && (typeof input.bytes === 'string' || input.bytes instanceof Uint8Array);
|
|
427
|
+
/**
|
|
428
|
+
* Encode opaque strategy bytes for the lending controller. Accepted inputs:
|
|
429
|
+
* - quote `routeXdr` base64 string
|
|
430
|
+
* - `{ routeXdr }` / `{ swapXdr }`
|
|
431
|
+
* - raw `Uint8Array` / `{ bytes }`
|
|
432
|
+
* - decoded `StrategyPayload` object
|
|
433
|
+
*/ export const asStellarStrategySwapBytes = (steps)=>{
|
|
434
|
+
if (typeof steps === 'string') {
|
|
435
|
+
return strategyBytes(decodeStrategyBytesString(steps));
|
|
436
|
+
}
|
|
437
|
+
if (steps instanceof Uint8Array) {
|
|
438
|
+
return steps.length === 0 ? emptyStrategySwapBytes() : strategyBytes(steps);
|
|
439
|
+
}
|
|
440
|
+
if (steps && typeof steps === 'object') {
|
|
441
|
+
if (hasRouteXdr(steps)) {
|
|
442
|
+
return strategyBytes(decodeStrategyBytesString(steps.routeXdr));
|
|
443
|
+
}
|
|
444
|
+
if (hasSwapXdr(steps)) {
|
|
445
|
+
return strategyBytes(decodeStrategyBytesString(steps.swapXdr));
|
|
446
|
+
}
|
|
447
|
+
if (hasBytes(steps)) {
|
|
448
|
+
if (typeof steps.bytes === 'string') {
|
|
449
|
+
return strategyBytes(decodeStrategyBytesString(steps.bytes));
|
|
450
|
+
}
|
|
451
|
+
return steps.bytes.length === 0 ? emptyStrategySwapBytes() : strategyBytes(steps.bytes);
|
|
452
|
+
}
|
|
453
|
+
return encodeStrategyPayloadToBytes(asStellarStrategyPayload(steps));
|
|
454
|
+
}
|
|
455
|
+
throw new Error('Stellar builder: `steps` must be opaque strategy bytes (`routeXdr`, base64/hex string, or Uint8Array)');
|
|
456
|
+
};
|
|
457
|
+
/**
|
|
458
|
+
* Validate the untyped `data` field on FlashLoanArgs and narrow it to
|
|
459
|
+
* Buffer / hex string for Soroban `Bytes` encoding.
|
|
460
|
+
*/ export const asStellarBytes = (data)=>{
|
|
461
|
+
if (typeof data === 'string') return bytes(data);
|
|
462
|
+
if (data instanceof Uint8Array) return xdr.ScVal.scvBytes(Buffer.from(data));
|
|
463
|
+
throw new Error('Stellar builder: `data` must be a hex string or Uint8Array (Soroban Bytes payload)');
|
|
464
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Address, scValToBigInt, StrKey, TransactionBuilder } from "@stellar/stellar-sdk";
|
|
2
|
+
import { decodeStellarRouteBytes } from "./route-verify.js";
|
|
3
|
+
import { xdrBytes, xdrField, xdrType } from "./xdr-compat.js";
|
|
4
|
+
/**
|
|
5
|
+
* Synchronously decode direct XOXNO router swaps, including fee-bump envelopes.
|
|
6
|
+
* Returns null for malformed, unrelated or ambiguous envelopes. Without an
|
|
7
|
+
* operation index exactly one matching swap is required. No RPC or output
|
|
8
|
+
* amount inference: the route minimum is not the amount actually received.
|
|
9
|
+
* The network passphrase parses the envelope; it cannot prove its network.
|
|
10
|
+
*/ export function decodeStellarSwapEnvelope(options) {
|
|
11
|
+
try {
|
|
12
|
+
const { envelopeXdr, networkPassphrase, routerAddress, viewer, operationIndex } = options;
|
|
13
|
+
if (!networkPassphrase || !StrKey.isValidContract(routerAddress) || new Address(viewer).toString() !== viewer || typeof envelopeXdr !== 'string' || envelopeXdr.length > 1024 * 1024 || envelopeXdr.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(envelopeXdr) || operationIndex !== undefined && (!Number.isSafeInteger(operationIndex) || operationIndex < 0)) return null;
|
|
14
|
+
const parsed = TransactionBuilder.fromXDR(envelopeXdr, networkPassphrase);
|
|
15
|
+
const tx = 'innerTransaction' in parsed ? parsed.innerTransaction : parsed;
|
|
16
|
+
if (operationIndex !== undefined && operationIndex >= tx.operations.length) return null;
|
|
17
|
+
let found = null;
|
|
18
|
+
for (const [index, operation] of tx.operations.entries()){
|
|
19
|
+
if (operationIndex !== undefined && index !== operationIndex) continue;
|
|
20
|
+
if (operation.type !== 'invokeHostFunction') continue;
|
|
21
|
+
const host = operation.func;
|
|
22
|
+
if (xdrType(host) !== 'hostFunctionTypeInvokeContract') continue;
|
|
23
|
+
const call = xdrField(host, 'invokeContract');
|
|
24
|
+
if (Address.fromScAddress(xdrField(call, 'contractAddress')).toString() !== routerAddress || xdrField(call, 'functionName').toString() !== 'execute_strategy') continue;
|
|
25
|
+
const args = xdrField(call, 'args');
|
|
26
|
+
const [sender, amount, routeBytes] = args;
|
|
27
|
+
if (args.length !== 3 || !sender || !amount || !routeBytes || xdrType(sender) !== 'scvAddress' || xdrType(amount) !== 'scvI128' || xdrType(routeBytes) !== 'scvBytes') return null;
|
|
28
|
+
if (Address.fromScAddress(xdrField(sender, 'address')).toString() !== viewer) continue;
|
|
29
|
+
// Read i128 with the SDK's own conversion, preserving values above 2^53.
|
|
30
|
+
const amountIn = scValToBigInt(amount);
|
|
31
|
+
if (amountIn <= 0n) return null;
|
|
32
|
+
const route = decodeStellarRouteBytes(xdrBytes(routeBytes));
|
|
33
|
+
if (route.tokenIn === route.tokenOut || !StrKey.isValidContract(route.tokenIn) || !StrKey.isValidContract(route.tokenOut)) return null;
|
|
34
|
+
if (found) return null;
|
|
35
|
+
found = {
|
|
36
|
+
tokenIn: route.tokenIn,
|
|
37
|
+
tokenOut: route.tokenOut,
|
|
38
|
+
amountInAtoms: amountIn.toString(),
|
|
39
|
+
operationIndex: index
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return found;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { decodeStellarSwapEnvelope } from "./swap-history.js";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stellar aggregator router direct-call transaction builder.
|
|
3
|
+
*
|
|
4
|
+
* Builds an unsigned XDR for
|
|
5
|
+
* `router.execute_strategy(sender, total_in, swap_xdr)` so a user can swap
|
|
6
|
+
* A -> B without involving the lending controller. The strategy route is
|
|
7
|
+
* opaque bytes: normally the quote response `routeXdr`, decoded into Soroban
|
|
8
|
+
* `Bytes` for the contract call.
|
|
9
|
+
*
|
|
10
|
+
* Lending strategies use the builders in `./lending.ts`; they pass the same
|
|
11
|
+
* opaque route bytes to the controller, which forwards them to the aggregator.
|
|
12
|
+
*/ import { Account, BASE_FEE, Contract, TransactionBuilder } from "@stellar/stellar-sdk";
|
|
13
|
+
import { STELLAR_NETWORK_PASSPHRASE } from "./contracts.js";
|
|
14
|
+
import { StellarRouteVerificationError, stellarTokenContractId, verifyStellarRouteBytes } from "./route-verify.js";
|
|
15
|
+
import { addr, asStellarStrategySwapBytes, encodeStrategyPayload, i128 } from "./scval-encode.js";
|
|
16
|
+
const hasPayloadPaths = (swap)=>Boolean(swap && typeof swap === 'object' && !(swap instanceof Uint8Array) && 'paths' in swap);
|
|
17
|
+
const withDefaultReferral = (swap, referralId)=>{
|
|
18
|
+
if (!hasPayloadPaths(swap) || swap.referralId !== undefined) return swap;
|
|
19
|
+
return {
|
|
20
|
+
...swap,
|
|
21
|
+
referralId: referralId ?? 0
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export const encodeStrategyPayloadToRouteXdr = (payload)=>encodeStrategyPayload(payload).toXDR('base64');
|
|
25
|
+
/**
|
|
26
|
+
* Build an unsigned XDR for a direct user -> aggregator router swap.
|
|
27
|
+
*
|
|
28
|
+
* Pass the quote response `routeXdr` string or `{ routeXdr }` directly when
|
|
29
|
+
* available. Decoded `StrategyPayload` objects are accepted for local tests and
|
|
30
|
+
* custom route builders.
|
|
31
|
+
*/ export function buildStellarExecuteStrategyTx(opts, swap) {
|
|
32
|
+
if (!opts.routerAddress) {
|
|
33
|
+
throw new Error('Stellar aggregator routerAddress is required');
|
|
34
|
+
}
|
|
35
|
+
const contract = new Contract(opts.routerAddress);
|
|
36
|
+
const source = new Account(opts.caller, opts.sourceSequence);
|
|
37
|
+
const swapBytes = asStellarStrategySwapBytes(withDefaultReferral(swap, opts.referralId));
|
|
38
|
+
const tx = new TransactionBuilder(source, {
|
|
39
|
+
fee: opts.fee ?? BASE_FEE,
|
|
40
|
+
networkPassphrase: STELLAR_NETWORK_PASSPHRASE[opts.network]
|
|
41
|
+
}).addOperation(contract.call('execute_strategy', addr(opts.caller), i128(opts.totalIn), swapBytes)).setTimeout(opts.timeoutSeconds ?? 300).build();
|
|
42
|
+
return {
|
|
43
|
+
xdr: tx.toXDR()
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Translate a quote-server response into the decoded `StrategyPayload` shape.
|
|
48
|
+
* Prefer `mapQuoteResponseToStrategySwap` when the quote includes `routeXdr`.
|
|
49
|
+
*/ export function mapQuoteResponseToStrategyPayload(quote, opts = {}) {
|
|
50
|
+
if (typeof quote.amountOutMin !== 'string') {
|
|
51
|
+
throw new Error('mapQuoteResponseToStrategyPayload: quote response is missing `amountOutMin`. Pass `slippage` when fetching the quote.');
|
|
52
|
+
}
|
|
53
|
+
const paths = quote.paths ? quote.paths.map((path)=>({
|
|
54
|
+
hops: path.swaps.map(mapHop),
|
|
55
|
+
splitPpm: path.splitPpm
|
|
56
|
+
})) : [
|
|
57
|
+
{
|
|
58
|
+
hops: quote.hops.map(mapHop),
|
|
59
|
+
splitPpm: 1000000
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
return {
|
|
63
|
+
paths,
|
|
64
|
+
referralId: opts.referralId ?? 0,
|
|
65
|
+
tokenIn: quote.from,
|
|
66
|
+
tokenOut: quote.to,
|
|
67
|
+
totalMinOut: quote.amountOutMin
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Return the executable quote route when the API provided it; otherwise build
|
|
72
|
+
* the decoded fallback payload from hop/path fields.
|
|
73
|
+
*
|
|
74
|
+
* Either form is decoded and checked before it is returned: it must spend and
|
|
75
|
+
* deliver the expected pair and enforce a minimum that is not below the
|
|
76
|
+
* quote's `amountOutMin` (what a UI displays) nor below `expected.minOut`.
|
|
77
|
+
* @throws StellarRouteVerificationError — never sign a quote that throws here.
|
|
78
|
+
*/ export function mapQuoteResponseToStrategySwap(quote, opts = {}) {
|
|
79
|
+
const expected = opts.expected ?? {};
|
|
80
|
+
// Built locally from the JSON when the server sent no route, so the encoded
|
|
81
|
+
// minimum IS `amountOutMin`; the same check then covers both forms.
|
|
82
|
+
const swap = typeof quote.routeXdr === 'string' && quote.routeXdr.length > 0 ? {
|
|
83
|
+
routeXdr: quote.routeXdr
|
|
84
|
+
} : mapQuoteResponseToStrategyPayload(quote, opts);
|
|
85
|
+
// The route is an opaque blob beside the JSON display fields. The router
|
|
86
|
+
// enforces only the minimum inside the blob, so it must not be below what
|
|
87
|
+
// the JSON (and therefore the UI) shows, nor below the caller's own floor.
|
|
88
|
+
const floors = [
|
|
89
|
+
quote.amountOutMin,
|
|
90
|
+
expected.minOut
|
|
91
|
+
].filter((v)=>v !== undefined).map((v)=>{
|
|
92
|
+
if (typeof v === 'bigint') return v;
|
|
93
|
+
if (typeof v !== 'string' || !/^\d+$/.test(v)) {
|
|
94
|
+
throw new StellarRouteVerificationError('MALFORMED', 'minimum output must be a base-unit integer string');
|
|
95
|
+
}
|
|
96
|
+
return BigInt(v);
|
|
97
|
+
});
|
|
98
|
+
const minOut = floors.reduce((max, v)=>max === undefined || v > max ? v : max, undefined);
|
|
99
|
+
if (minOut === undefined) {
|
|
100
|
+
throw new StellarRouteVerificationError('EXPECTATION_MISSING', 'quote has no `amountOutMin`; pass `slippage` when fetching it or `expected.minOut`');
|
|
101
|
+
}
|
|
102
|
+
// The caller's pair is authoritative; the JSON pair only labels the response.
|
|
103
|
+
verifyStellarRouteBytes(swap, {
|
|
104
|
+
tokenIn: stellarTokenContractId(expected.tokenIn ?? quote.from, opts.network),
|
|
105
|
+
tokenOut: stellarTokenContractId(expected.tokenOut ?? quote.to, opts.network),
|
|
106
|
+
minOut,
|
|
107
|
+
amountIn: expected.amountIn ?? quote.amountIn
|
|
108
|
+
});
|
|
109
|
+
return swap;
|
|
110
|
+
}
|
|
111
|
+
const mapHop = (hop)=>({
|
|
112
|
+
amountOut: hop.amountOut,
|
|
113
|
+
pool: hop.address,
|
|
114
|
+
tokenIn: hop.from,
|
|
115
|
+
tokenOut: hop.to,
|
|
116
|
+
venue: hop.dex
|
|
117
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
/** SDK 16 exposes XDR getters; SDK 17 exposes the same fields as properties. */ export function xdrField(value, key) {
|
|
3
|
+
const field = value[key];
|
|
4
|
+
return typeof field === 'function' ? field.call(value) : field;
|
|
5
|
+
}
|
|
6
|
+
export function xdrType(value) {
|
|
7
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
8
|
+
const union = value;
|
|
9
|
+
return typeof union.switch === 'function' ? union.switch().name : union.type;
|
|
10
|
+
}
|
|
11
|
+
/** SDK 17 opaque byte fields wrap their Uint8Array in `value`. */ export function xdrBytes(value) {
|
|
12
|
+
return xdrOpaque(xdrField(value, 'bytes'));
|
|
13
|
+
}
|
|
14
|
+
export function xdrOpaque(value) {
|
|
15
|
+
const bytes = value;
|
|
16
|
+
return Buffer.from('value' in bytes ? bytes.value : bytes);
|
|
17
|
+
}
|