@pear-protocol/utils 1.1.0 → 1.3.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/dist/copy/equity-fraction.d.ts +13 -0
- package/dist/copy/index.d.ts +1 -0
- package/dist/fills/compute.d.ts +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +573 -276
- package/dist/position/key.d.ts +2 -2
- package/dist/sync/applicables/basket.d.ts +1 -0
- package/dist/sync/applicables/constants.d.ts +7 -0
- package/dist/sync/applicables/fills.d.ts +1 -1
- package/dist/sync/applicables/matching.d.ts +5 -0
- package/dist/sync/applicables/types.d.ts +5 -0
- package/dist/sync/compute.d.ts +3 -8
- package/dist/sync/types.d.ts +0 -2
- package/dist/vaults/action-typed-data.d.ts +181 -0
- package/dist/vaults/index.d.ts +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Pure gross-notional valuation for copy sizing. */
|
|
2
|
+
import type { Fill, InstrumentPriceMap, PositionExposure, TradeLeg, UnsignedDecimalString } from '@pear-protocol/types';
|
|
3
|
+
/** Size a non-empty set of validated positive fill notionals without a market-price lookup. */
|
|
4
|
+
export declare function computeFilledEntrySizing(fills: ReadonlyArray<Pick<Fill, 'symbol' | 'usd'>>, equity: UnsignedDecimalString): {
|
|
5
|
+
equityFraction: UnsignedDecimalString;
|
|
6
|
+
legNotionals: Record<string, UnsignedDecimalString>;
|
|
7
|
+
};
|
|
8
|
+
/** Value signed exposure against equity; missing prices contribute no notional. */
|
|
9
|
+
export declare function computeEquityFraction(exposure: PositionExposure, priceMap: InstrumentPriceMap, equity: UnsignedDecimalString): UnsignedDecimalString;
|
|
10
|
+
/** Value USD and quantity entry legs; missing quantity prices contribute no notional. */
|
|
11
|
+
export declare function computeEntryEquityFraction(legs: ReadonlyArray<Pick<TradeLeg, 'symbol' | 'mode' | 'amount'>>, priceMap: InstrumentPriceMap, equity: UnsignedDecimalString): UnsignedDecimalString;
|
|
12
|
+
/** Divide valued notional by validated positive equity without capping leverage. */
|
|
13
|
+
export declare function computeNotionalEquityFraction(notional: Fill['usd'], equity: UnsignedDecimalString): UnsignedDecimalString;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './equity-fraction';
|
package/dist/fills/compute.d.ts
CHANGED
|
@@ -8,5 +8,9 @@ import type { Fill } from './fill.types';
|
|
|
8
8
|
* - Reduce position : entry price unchanged
|
|
9
9
|
* - Close to zero : keep last entry price (for tracking purpose)
|
|
10
10
|
* - Flip sign : entry price resets to flip fill's price
|
|
11
|
+
*
|
|
12
|
+
* A 0-price (unpriceable synthetic) fill that opens or adds to the held size taints the
|
|
13
|
+
* basis until the size flattens or flips: the entry is then unknown and reported as zero,
|
|
14
|
+
* the same "no entry" answer callers already get for an empty ledger.
|
|
11
15
|
*/
|
|
12
16
|
export declare function computeEntryPriceForAsset(fills: Fill[]): BigNumber;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './asset/index';
|
|
2
2
|
export * from './basket/index';
|
|
3
3
|
export * from './contract-size';
|
|
4
|
+
export * from './copy/index';
|
|
4
5
|
export * from './fills/index';
|
|
5
6
|
export * from './math/index';
|
|
6
7
|
export * from './position/index';
|
|
@@ -10,4 +11,5 @@ export * from './proxy';
|
|
|
10
11
|
export * from './sync/index';
|
|
11
12
|
export * from './validate-leverage';
|
|
12
13
|
export * from './validate-quantity';
|
|
14
|
+
export * from './vaults/index';
|
|
13
15
|
export * from './xauth/index';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import BigNumber7 from 'bignumber.js';
|
|
2
2
|
import { sha256 } from '@noble/hashes/sha2';
|
|
3
|
-
import { bytesToHex,
|
|
3
|
+
import { bytesToHex, utf8ToBytes, randomBytes } from '@noble/hashes/utils';
|
|
4
4
|
import { WATERMARK_FUTURE_SKEW_MS } from '@pear-protocol/types';
|
|
5
|
+
import { keccak_256 } from '@noble/hashes/sha3';
|
|
5
6
|
import { base64urlnopad } from '@scure/base';
|
|
6
7
|
|
|
7
8
|
var __defProp = Object.defineProperty;
|
|
@@ -124,6 +125,7 @@ __name(compareValue, "compareValue");
|
|
|
124
125
|
function computeEntryPriceForAsset(fills) {
|
|
125
126
|
let entryPrice = ZERO;
|
|
126
127
|
let positionSize = ZERO;
|
|
128
|
+
let unpriced = false;
|
|
127
129
|
for (const fill of fills) {
|
|
128
130
|
const price = new BigNumber7(fill.price);
|
|
129
131
|
const quantity = new BigNumber7(fill.quantity);
|
|
@@ -133,11 +135,16 @@ function computeEntryPriceForAsset(fills) {
|
|
|
133
135
|
const isFlip = !positionSize.isZero() && !newPositionSize.isZero() && newPositionSize.isPositive() !== positionSize.isPositive();
|
|
134
136
|
if (positionSize.isZero() || isFlip) {
|
|
135
137
|
entryPrice = price;
|
|
138
|
+
unpriced = price.isZero();
|
|
136
139
|
} else if (isSameDirection) {
|
|
137
140
|
entryPrice = entryPrice.times(positionSize.abs()).plus(price.times(quantity)).div(newPositionSize.abs());
|
|
141
|
+
unpriced = unpriced || price.isZero();
|
|
138
142
|
}
|
|
139
143
|
positionSize = newPositionSize;
|
|
140
144
|
}
|
|
145
|
+
if (unpriced) {
|
|
146
|
+
return ZERO;
|
|
147
|
+
}
|
|
141
148
|
return entryPrice;
|
|
142
149
|
}
|
|
143
150
|
__name(computeEntryPriceForAsset, "computeEntryPriceForAsset");
|
|
@@ -381,6 +388,9 @@ function computeAssetUnrealizedPnLs(exposureMap, priceMap, fills) {
|
|
|
381
388
|
const currentPrice = new BigNumber7(priceString);
|
|
382
389
|
const assetFills = fills.filter((fill) => fill.symbol === id);
|
|
383
390
|
const entryPrice = computeEntryPriceForAsset(assetFills);
|
|
391
|
+
if (entryPrice.isZero()) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
384
394
|
const upnl = signedQuantity.multipliedBy(currentPrice.minus(entryPrice));
|
|
385
395
|
result.push({
|
|
386
396
|
id,
|
|
@@ -865,6 +875,61 @@ function baseToContracts(base, contractSize) {
|
|
|
865
875
|
}
|
|
866
876
|
__name(baseToContracts, "baseToContracts");
|
|
867
877
|
|
|
878
|
+
// src/copy/equity-fraction.ts
|
|
879
|
+
function computeFilledEntrySizing(fills, equity) {
|
|
880
|
+
const notionals = /* @__PURE__ */ new Map();
|
|
881
|
+
let total = parse("0");
|
|
882
|
+
for (const fill of fills) {
|
|
883
|
+
const notional = parse(fill.usd);
|
|
884
|
+
notionals.set(fill.symbol, (notionals.get(fill.symbol) ?? parse("0")).plus(notional));
|
|
885
|
+
total = total.plus(notional);
|
|
886
|
+
}
|
|
887
|
+
const legNotionals = {};
|
|
888
|
+
for (const [symbol, notional] of notionals) {
|
|
889
|
+
legNotionals[symbol] = notional.toFixed();
|
|
890
|
+
}
|
|
891
|
+
return {
|
|
892
|
+
equityFraction: computeNotionalEquityFraction(total.toFixed(), equity),
|
|
893
|
+
legNotionals
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
__name(computeFilledEntrySizing, "computeFilledEntrySizing");
|
|
897
|
+
function computeEquityFraction(exposure, priceMap, equity) {
|
|
898
|
+
let notional = parse("0");
|
|
899
|
+
for (const [symbol, quantity] of Object.entries(exposure)) {
|
|
900
|
+
const price = priceMap[symbol];
|
|
901
|
+
if (price !== void 0) {
|
|
902
|
+
notional = notional.plus(parse(quantity).abs().multipliedBy(price));
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return computeNotionalEquityFraction(notional.toFixed(), equity);
|
|
906
|
+
}
|
|
907
|
+
__name(computeEquityFraction, "computeEquityFraction");
|
|
908
|
+
function computeEntryEquityFraction(legs, priceMap, equity) {
|
|
909
|
+
let notional = parse("0");
|
|
910
|
+
for (const leg of legs) {
|
|
911
|
+
const amount = parse(leg.amount).abs();
|
|
912
|
+
switch (leg.mode) {
|
|
913
|
+
case "USD":
|
|
914
|
+
notional = notional.plus(amount);
|
|
915
|
+
break;
|
|
916
|
+
case "QUANTITY": {
|
|
917
|
+
const price = priceMap[leg.symbol];
|
|
918
|
+
if (price !== void 0) {
|
|
919
|
+
notional = notional.plus(amount.multipliedBy(price));
|
|
920
|
+
}
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return computeNotionalEquityFraction(notional.toFixed(), equity);
|
|
926
|
+
}
|
|
927
|
+
__name(computeEntryEquityFraction, "computeEntryEquityFraction");
|
|
928
|
+
function computeNotionalEquityFraction(notional, equity) {
|
|
929
|
+
return parse(notional).dividedBy(equity).toFixed();
|
|
930
|
+
}
|
|
931
|
+
__name(computeNotionalEquityFraction, "computeNotionalEquityFraction");
|
|
932
|
+
|
|
868
933
|
// src/fills/group.ts
|
|
869
934
|
function groupFillsByPosition(fills) {
|
|
870
935
|
const byPosition = /* @__PURE__ */ new Map();
|
|
@@ -1268,6 +1333,7 @@ __name(proxyFetch, "proxyFetch");
|
|
|
1268
1333
|
|
|
1269
1334
|
// src/sync/applicables/constants.ts
|
|
1270
1335
|
var DUST = parse("0.00000001");
|
|
1336
|
+
var LATE_FILL_GRACE_MS = 5 * 6e4;
|
|
1271
1337
|
var MISSED_HISTORY_FILL_PREFIX = "opening-synthetic-";
|
|
1272
1338
|
var BASKET_RULES = [
|
|
1273
1339
|
// V2 Basket Hyperliquid — legs of one Pear basket order share the order's cloid.
|
|
@@ -1303,12 +1369,14 @@ var Basket = class _Basket {
|
|
|
1303
1369
|
__name(this, "Basket");
|
|
1304
1370
|
}
|
|
1305
1371
|
legs;
|
|
1306
|
-
|
|
1372
|
+
fills;
|
|
1373
|
+
constructor(legs, fills) {
|
|
1307
1374
|
this.legs = legs;
|
|
1375
|
+
this.fills = fills;
|
|
1308
1376
|
}
|
|
1309
1377
|
/** Detect baskets from a fill stream, returned in ascending time order. */
|
|
1310
1378
|
static detect(fills) {
|
|
1311
|
-
return groupFillsIntoBaskets(fills).map((group) => new _Basket(combineFillsBySymbolAndSide(group)));
|
|
1379
|
+
return groupFillsIntoBaskets(fills).map((group) => new _Basket(combineFillsBySymbolAndSide(group), group));
|
|
1312
1380
|
}
|
|
1313
1381
|
/** Active long symbol set the matcher compares against open positions. */
|
|
1314
1382
|
get longs() {
|
|
@@ -1515,189 +1583,6 @@ var Exposure = class _Exposure {
|
|
|
1515
1583
|
}
|
|
1516
1584
|
};
|
|
1517
1585
|
|
|
1518
|
-
// src/sync/applicables/matching.ts
|
|
1519
|
-
function matchPositions(args) {
|
|
1520
|
-
const { tradeAccountId, orphanFills, pear } = args;
|
|
1521
|
-
const book = WorkingBook.load({
|
|
1522
|
-
tradeAccountId,
|
|
1523
|
-
openPositions: pear.positions()
|
|
1524
|
-
});
|
|
1525
|
-
for (const basket of Basket.detect(orphanFills)) {
|
|
1526
|
-
const exactCloseTarget = findExactCloseTarget(book.open(), basket.longs, basket.shorts);
|
|
1527
|
-
const leftover = applyCloses({
|
|
1528
|
-
legs: basket.legs,
|
|
1529
|
-
book,
|
|
1530
|
-
exactCloseTarget
|
|
1531
|
-
});
|
|
1532
|
-
if (leftover.length === 0) {
|
|
1533
|
-
continue;
|
|
1534
|
-
}
|
|
1535
|
-
const target = resolveLeftoverTarget({
|
|
1536
|
-
book,
|
|
1537
|
-
leftover
|
|
1538
|
-
});
|
|
1539
|
-
applyLeftover(target, leftover);
|
|
1540
|
-
}
|
|
1541
|
-
return book.toMatched();
|
|
1542
|
-
}
|
|
1543
|
-
__name(matchPositions, "matchPositions");
|
|
1544
|
-
var WorkingBook = class WorkingBook2 {
|
|
1545
|
-
static {
|
|
1546
|
-
__name(this, "WorkingBook");
|
|
1547
|
-
}
|
|
1548
|
-
tradeAccountId;
|
|
1549
|
-
all = [];
|
|
1550
|
-
openByKey = /* @__PURE__ */ new Map();
|
|
1551
|
-
constructor(tradeAccountId) {
|
|
1552
|
-
this.tradeAccountId = tradeAccountId;
|
|
1553
|
-
}
|
|
1554
|
-
/** Seed the book from existing open DB positions (they arrive pre-filtered by connector). */
|
|
1555
|
-
static load(args) {
|
|
1556
|
-
const { tradeAccountId, openPositions } = args;
|
|
1557
|
-
const book = new WorkingBook2(tradeAccountId);
|
|
1558
|
-
for (const p of openPositions) {
|
|
1559
|
-
const position = {
|
|
1560
|
-
// Trust the position's own stored key — never recompute it from exposure.
|
|
1561
|
-
// A partial fill on the original order leaves exposure short of the
|
|
1562
|
-
// symbols the key was hashed from, so a recomputed key would never match.
|
|
1563
|
-
positionKey: p.positionKey,
|
|
1564
|
-
exposure: Exposure.fromRecord(p.exposure),
|
|
1565
|
-
parts: [],
|
|
1566
|
-
closed: false,
|
|
1567
|
-
existingOrigin: p.origin
|
|
1568
|
-
};
|
|
1569
|
-
book.all.push(position);
|
|
1570
|
-
book.openByKey.set(position.positionKey, position);
|
|
1571
|
-
}
|
|
1572
|
-
return book;
|
|
1573
|
-
}
|
|
1574
|
-
/** Deterministic position key for a symbol set under this book's trade account. */
|
|
1575
|
-
keyFor(symbols) {
|
|
1576
|
-
return generateDeterministicPositionKey({
|
|
1577
|
-
tradeAccountId: this.tradeAccountId,
|
|
1578
|
-
symbols
|
|
1579
|
-
});
|
|
1580
|
-
}
|
|
1581
|
-
/** Currently open working positions, in insertion order. */
|
|
1582
|
-
open() {
|
|
1583
|
-
return this.all.filter((position) => !position.closed);
|
|
1584
|
-
}
|
|
1585
|
-
/** Look up an open working position by its positionKey (for dedup on merge). */
|
|
1586
|
-
findOpenByKey(key) {
|
|
1587
|
-
return this.openByKey.get(key);
|
|
1588
|
-
}
|
|
1589
|
-
/** Register a fresh empty working position under `key` and return it. */
|
|
1590
|
-
create(key) {
|
|
1591
|
-
const position = {
|
|
1592
|
-
positionKey: key,
|
|
1593
|
-
exposure: Exposure.empty(),
|
|
1594
|
-
parts: [],
|
|
1595
|
-
closed: false
|
|
1596
|
-
};
|
|
1597
|
-
this.all.push(position);
|
|
1598
|
-
this.openByKey.set(key, position);
|
|
1599
|
-
return position;
|
|
1600
|
-
}
|
|
1601
|
-
/** Flip a position to closed once every leg is within DUST of zero. */
|
|
1602
|
-
markClosedIfFlat(position) {
|
|
1603
|
-
if (position.closed || !position.exposure.isFlat()) {
|
|
1604
|
-
return;
|
|
1605
|
-
}
|
|
1606
|
-
position.exposure.snapToZero();
|
|
1607
|
-
position.closed = true;
|
|
1608
|
-
this.openByKey.delete(position.positionKey);
|
|
1609
|
-
}
|
|
1610
|
-
/** Working positions that received fills become MatchedPosition records; untouched ones are dropped. */
|
|
1611
|
-
toMatched() {
|
|
1612
|
-
return this.all.filter((position) => position.parts.length > 0).map((position) => ({
|
|
1613
|
-
positionKey: position.positionKey,
|
|
1614
|
-
parts: position.parts,
|
|
1615
|
-
closed: position.closed,
|
|
1616
|
-
exposure: position.exposure,
|
|
1617
|
-
existingOrigin: position.existingOrigin
|
|
1618
|
-
}));
|
|
1619
|
-
}
|
|
1620
|
-
};
|
|
1621
|
-
function findExactCloseTarget(openList, basketLongs, basketShorts) {
|
|
1622
|
-
return openList.find((position) => {
|
|
1623
|
-
const { longs, shorts } = position.exposure.activeLegs();
|
|
1624
|
-
return setEq(longs, basketShorts) && setEq(shorts, basketLongs);
|
|
1625
|
-
});
|
|
1626
|
-
}
|
|
1627
|
-
__name(findExactCloseTarget, "findExactCloseTarget");
|
|
1628
|
-
var setEq = /* @__PURE__ */ __name((a, b) => a.size === b.size && [
|
|
1629
|
-
...a
|
|
1630
|
-
].every((x) => b.has(x)), "setEq");
|
|
1631
|
-
function applyCloses(args) {
|
|
1632
|
-
const { legs, book, exactCloseTarget } = args;
|
|
1633
|
-
const openList = book.open();
|
|
1634
|
-
const leftover = [];
|
|
1635
|
-
for (const leg of legs) {
|
|
1636
|
-
let remaining = leg.totalSize;
|
|
1637
|
-
const closeTargetIsLong = sideSign(leg.side) === -1;
|
|
1638
|
-
const holders = openList.filter((position) => {
|
|
1639
|
-
const qty = position.exposure.get(leg.symbol);
|
|
1640
|
-
if (qty.isZero()) {
|
|
1641
|
-
return false;
|
|
1642
|
-
}
|
|
1643
|
-
return closeTargetIsLong ? qty.isPositive() : qty.isNegative();
|
|
1644
|
-
});
|
|
1645
|
-
if (exactCloseTarget) {
|
|
1646
|
-
holders.sort((a, b) => a === exactCloseTarget ? -1 : b === exactCloseTarget ? 1 : 0);
|
|
1647
|
-
}
|
|
1648
|
-
for (const position of holders) {
|
|
1649
|
-
if (isLessThanOrEqual(remaining, ZERO)) {
|
|
1650
|
-
break;
|
|
1651
|
-
}
|
|
1652
|
-
const have = position.exposure.get(leg.symbol).abs();
|
|
1653
|
-
const reduce = min(have, remaining);
|
|
1654
|
-
position.parts.push(...leg.take(reduce));
|
|
1655
|
-
position.exposure.addDelta(leg.symbol, signedBySide(reduce, leg.side));
|
|
1656
|
-
remaining = remaining.minus(reduce);
|
|
1657
|
-
book.markClosedIfFlat(position);
|
|
1658
|
-
}
|
|
1659
|
-
if (isGreaterThanOrEqual(remaining, DUST)) {
|
|
1660
|
-
leftover.push({
|
|
1661
|
-
side: leg.side,
|
|
1662
|
-
qty: remaining,
|
|
1663
|
-
leg
|
|
1664
|
-
});
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
return leftover;
|
|
1668
|
-
}
|
|
1669
|
-
__name(applyCloses, "applyCloses");
|
|
1670
|
-
function resolveLeftoverTarget(args) {
|
|
1671
|
-
const { book, leftover } = args;
|
|
1672
|
-
const leftoverLongs = new Set(leftover.filter((entry) => entry.side === "BUY").map((entry) => entry.leg.symbol));
|
|
1673
|
-
const leftoverShorts = new Set(leftover.filter((entry) => entry.side === "SELL").map((entry) => entry.leg.symbol));
|
|
1674
|
-
const target = book.open().find((position) => {
|
|
1675
|
-
const { longs, shorts } = position.exposure.activeLegs();
|
|
1676
|
-
const sameActiveShape = setEq(leftoverLongs, longs) && setEq(leftoverShorts, shorts);
|
|
1677
|
-
const noDormantLegs = position.exposure.symbols().length === longs.size + shorts.size;
|
|
1678
|
-
return sameActiveShape && noDormantLegs;
|
|
1679
|
-
});
|
|
1680
|
-
if (target) {
|
|
1681
|
-
return target;
|
|
1682
|
-
}
|
|
1683
|
-
const symbols = [
|
|
1684
|
-
.../* @__PURE__ */ new Set([
|
|
1685
|
-
...leftoverLongs,
|
|
1686
|
-
...leftoverShorts
|
|
1687
|
-
])
|
|
1688
|
-
];
|
|
1689
|
-
const positionKey = book.keyFor(symbols);
|
|
1690
|
-
return book.findOpenByKey(positionKey) ?? book.create(positionKey);
|
|
1691
|
-
}
|
|
1692
|
-
__name(resolveLeftoverTarget, "resolveLeftoverTarget");
|
|
1693
|
-
function applyLeftover(target, leftover) {
|
|
1694
|
-
for (const entry of leftover) {
|
|
1695
|
-
target.parts.push(...entry.leg.take(entry.qty));
|
|
1696
|
-
target.exposure.addSigned(entry.leg.symbol, entry.qty.toFixed(), entry.side);
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
__name(applyLeftover, "applyLeftover");
|
|
1700
|
-
|
|
1701
1586
|
// src/sync/applicables/synthetics.ts
|
|
1702
1587
|
function isMissedHistoryFill(fill) {
|
|
1703
1588
|
return fill.exchangeFillId.startsWith(MISSED_HISTORY_FILL_PREFIX);
|
|
@@ -1892,7 +1777,8 @@ function assignSyntheticsToPositions(args) {
|
|
|
1892
1777
|
const positionKey = existing ? existing.positionKey : keyOf([
|
|
1893
1778
|
symbol
|
|
1894
1779
|
]);
|
|
1895
|
-
|
|
1780
|
+
const isKeyTarget = /* @__PURE__ */ __name((pos) => pos.positionKey === positionKey && (!existing || pos.existingOrigin !== void 0), "isKeyTarget");
|
|
1781
|
+
target = positions.find(isKeyTarget) ?? created.find(isKeyTarget);
|
|
1896
1782
|
if (!target) {
|
|
1897
1783
|
target = existing ? (
|
|
1898
1784
|
// seed its exposure and origin so folding the synthetic yields the true
|
|
@@ -1969,6 +1855,223 @@ function findGapToTarget(args) {
|
|
|
1969
1855
|
}
|
|
1970
1856
|
__name(findGapToTarget, "findGapToTarget");
|
|
1971
1857
|
|
|
1858
|
+
// src/sync/applicables/matching.ts
|
|
1859
|
+
function matchPositions(args) {
|
|
1860
|
+
const { tradeAccountId, orphanFills, pear } = args;
|
|
1861
|
+
const book = WorkingBook.load({
|
|
1862
|
+
tradeAccountId,
|
|
1863
|
+
openPositions: pear.positions()
|
|
1864
|
+
});
|
|
1865
|
+
for (const basket of Basket.detect(orphanFills)) {
|
|
1866
|
+
const at = basketTime(basket);
|
|
1867
|
+
book.foldIntoLive(at);
|
|
1868
|
+
const exactCloseTarget = findExactCloseTarget(book.live(at), basket.longs, basket.shorts);
|
|
1869
|
+
const leftover = applyCloses({
|
|
1870
|
+
legs: basket.legs,
|
|
1871
|
+
book,
|
|
1872
|
+
exactCloseTarget,
|
|
1873
|
+
at
|
|
1874
|
+
});
|
|
1875
|
+
if (leftover.length === 0) {
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
const target = resolveLeftoverTarget({
|
|
1879
|
+
book,
|
|
1880
|
+
leftover,
|
|
1881
|
+
at
|
|
1882
|
+
});
|
|
1883
|
+
applyLeftover(target, leftover);
|
|
1884
|
+
}
|
|
1885
|
+
book.foldIntoLive(Number.POSITIVE_INFINITY);
|
|
1886
|
+
return book.toMatched();
|
|
1887
|
+
}
|
|
1888
|
+
__name(matchPositions, "matchPositions");
|
|
1889
|
+
function basketTime(basket) {
|
|
1890
|
+
const realTimestamps = basket.fills.filter((fill) => !isMissedHistoryFill(fill)).map((fill) => fill.timestamp);
|
|
1891
|
+
if (realTimestamps.length === 0) {
|
|
1892
|
+
return Number.POSITIVE_INFINITY;
|
|
1893
|
+
}
|
|
1894
|
+
return Math.min(...realTimestamps);
|
|
1895
|
+
}
|
|
1896
|
+
__name(basketTime, "basketTime");
|
|
1897
|
+
var WorkingBook = class WorkingBook2 {
|
|
1898
|
+
static {
|
|
1899
|
+
__name(this, "WorkingBook");
|
|
1900
|
+
}
|
|
1901
|
+
tradeAccountId;
|
|
1902
|
+
all = [];
|
|
1903
|
+
constructor(tradeAccountId) {
|
|
1904
|
+
this.tradeAccountId = tradeAccountId;
|
|
1905
|
+
}
|
|
1906
|
+
/** Seed the book from existing open DB positions (they arrive pre-filtered by connector). */
|
|
1907
|
+
static load(args) {
|
|
1908
|
+
const { tradeAccountId, openPositions } = args;
|
|
1909
|
+
const book = new WorkingBook2(tradeAccountId);
|
|
1910
|
+
for (const p of openPositions) {
|
|
1911
|
+
const position = {
|
|
1912
|
+
// Trust the position's own stored key — never recompute it from exposure.
|
|
1913
|
+
// A partial fill on the original order leaves exposure short of the
|
|
1914
|
+
// symbols the key was hashed from, so a recomputed key would never match.
|
|
1915
|
+
positionKey: p.positionKey,
|
|
1916
|
+
exposure: Exposure.fromRecord(p.exposure),
|
|
1917
|
+
parts: [],
|
|
1918
|
+
closed: false,
|
|
1919
|
+
existingOrigin: p.origin,
|
|
1920
|
+
// A seeded position takes fills from its creation, less the late-arrival grace.
|
|
1921
|
+
liveFrom: Date.parse(p.createdAt) - LATE_FILL_GRACE_MS
|
|
1922
|
+
};
|
|
1923
|
+
book.all.push(position);
|
|
1924
|
+
}
|
|
1925
|
+
return book;
|
|
1926
|
+
}
|
|
1927
|
+
/** Deterministic position key for a symbol set under this book's trade account. */
|
|
1928
|
+
keyFor(symbols) {
|
|
1929
|
+
return generateDeterministicPositionKey({
|
|
1930
|
+
tradeAccountId: this.tradeAccountId,
|
|
1931
|
+
symbols
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
/** Working positions open and already existing at basket time `at`, in insertion order. */
|
|
1935
|
+
live(at) {
|
|
1936
|
+
return this.all.filter((position) => !position.closed && position.liveFrom <= at);
|
|
1937
|
+
}
|
|
1938
|
+
/** Look up a position open and existing at `at` by its positionKey (for dedup on merge). */
|
|
1939
|
+
findLiveByKey(key, at) {
|
|
1940
|
+
return this.live(at).find((position) => position.positionKey === key);
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Fold every open position opened mid-replay into the open seeded position sharing its
|
|
1944
|
+
* key once that seeded position is live at `at`. The earlier open was still holding
|
|
1945
|
+
* exposure when the seeded position was created, and the server keeps at most one open
|
|
1946
|
+
* row per key. Ownership is ambiguous here — exposure and timestamps cannot prove the
|
|
1947
|
+
* seeded position traded on top of that history — so the fold conservatively keeps
|
|
1948
|
+
* the attribution the matcher gave before #1067.
|
|
1949
|
+
*/
|
|
1950
|
+
foldIntoLive(at) {
|
|
1951
|
+
for (const seeded of this.live(at).filter((position) => position.existingOrigin !== void 0)) {
|
|
1952
|
+
const earlier = this.all.filter((position) => position.existingOrigin === void 0 && !position.closed && position.positionKey === seeded.positionKey);
|
|
1953
|
+
for (const position of earlier) {
|
|
1954
|
+
seeded.parts.push(...position.parts);
|
|
1955
|
+
for (const symbol of position.exposure.symbols()) {
|
|
1956
|
+
seeded.exposure.addDelta(symbol, position.exposure.get(symbol));
|
|
1957
|
+
}
|
|
1958
|
+
this.all = this.all.filter((candidate) => candidate !== position);
|
|
1959
|
+
}
|
|
1960
|
+
this.markClosedIfFlat(seeded);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
/** Register a fresh empty working position under `key` and return it. */
|
|
1964
|
+
create(key) {
|
|
1965
|
+
const position = {
|
|
1966
|
+
positionKey: key,
|
|
1967
|
+
exposure: Exposure.empty(),
|
|
1968
|
+
parts: [],
|
|
1969
|
+
closed: false,
|
|
1970
|
+
// Opened by this replay's own fills, so it takes fills from the start.
|
|
1971
|
+
liveFrom: Number.NEGATIVE_INFINITY
|
|
1972
|
+
};
|
|
1973
|
+
this.all.push(position);
|
|
1974
|
+
return position;
|
|
1975
|
+
}
|
|
1976
|
+
/** Flip a position to closed once every leg is within DUST of zero. */
|
|
1977
|
+
markClosedIfFlat(position) {
|
|
1978
|
+
if (position.closed || !position.exposure.isFlat()) {
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
position.exposure.snapToZero();
|
|
1982
|
+
position.closed = true;
|
|
1983
|
+
}
|
|
1984
|
+
/** Working positions that received fills become MatchedPosition records; untouched ones are dropped. */
|
|
1985
|
+
toMatched() {
|
|
1986
|
+
return this.all.filter((position) => position.parts.length > 0).map((position) => ({
|
|
1987
|
+
positionKey: position.positionKey,
|
|
1988
|
+
parts: position.parts,
|
|
1989
|
+
closed: position.closed,
|
|
1990
|
+
exposure: position.exposure,
|
|
1991
|
+
existingOrigin: position.existingOrigin
|
|
1992
|
+
}));
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
function findExactCloseTarget(openList, basketLongs, basketShorts) {
|
|
1996
|
+
return openList.find((position) => {
|
|
1997
|
+
const { longs, shorts } = position.exposure.activeLegs();
|
|
1998
|
+
return setEq(longs, basketShorts) && setEq(shorts, basketLongs);
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
__name(findExactCloseTarget, "findExactCloseTarget");
|
|
2002
|
+
var setEq = /* @__PURE__ */ __name((a, b) => a.size === b.size && [
|
|
2003
|
+
...a
|
|
2004
|
+
].every((x) => b.has(x)), "setEq");
|
|
2005
|
+
function applyCloses(args) {
|
|
2006
|
+
const { legs, book, exactCloseTarget, at } = args;
|
|
2007
|
+
const openList = book.live(at);
|
|
2008
|
+
const leftover = [];
|
|
2009
|
+
for (const leg of legs) {
|
|
2010
|
+
let remaining = leg.totalSize;
|
|
2011
|
+
const closeTargetIsLong = sideSign(leg.side) === -1;
|
|
2012
|
+
const holders = openList.filter((position) => {
|
|
2013
|
+
const qty = position.exposure.get(leg.symbol);
|
|
2014
|
+
if (qty.isZero()) {
|
|
2015
|
+
return false;
|
|
2016
|
+
}
|
|
2017
|
+
return closeTargetIsLong ? qty.isPositive() : qty.isNegative();
|
|
2018
|
+
});
|
|
2019
|
+
if (exactCloseTarget) {
|
|
2020
|
+
holders.sort((a, b) => a === exactCloseTarget ? -1 : b === exactCloseTarget ? 1 : 0);
|
|
2021
|
+
}
|
|
2022
|
+
for (const position of holders) {
|
|
2023
|
+
if (isLessThanOrEqual(remaining, ZERO)) {
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
const have = position.exposure.get(leg.symbol).abs();
|
|
2027
|
+
const reduce = min(have, remaining);
|
|
2028
|
+
position.parts.push(...leg.take(reduce));
|
|
2029
|
+
position.exposure.addDelta(leg.symbol, signedBySide(reduce, leg.side));
|
|
2030
|
+
remaining = remaining.minus(reduce);
|
|
2031
|
+
book.markClosedIfFlat(position);
|
|
2032
|
+
}
|
|
2033
|
+
if (isGreaterThanOrEqual(remaining, DUST)) {
|
|
2034
|
+
leftover.push({
|
|
2035
|
+
side: leg.side,
|
|
2036
|
+
qty: remaining,
|
|
2037
|
+
leg
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return leftover;
|
|
2042
|
+
}
|
|
2043
|
+
__name(applyCloses, "applyCloses");
|
|
2044
|
+
function resolveLeftoverTarget(args) {
|
|
2045
|
+
const { book, leftover, at } = args;
|
|
2046
|
+
const leftoverLongs = new Set(leftover.filter((entry) => entry.side === "BUY").map((entry) => entry.leg.symbol));
|
|
2047
|
+
const leftoverShorts = new Set(leftover.filter((entry) => entry.side === "SELL").map((entry) => entry.leg.symbol));
|
|
2048
|
+
const target = book.live(at).find((position) => {
|
|
2049
|
+
const { longs, shorts } = position.exposure.activeLegs();
|
|
2050
|
+
const sameActiveShape = setEq(leftoverLongs, longs) && setEq(leftoverShorts, shorts);
|
|
2051
|
+
const noDormantLegs = position.exposure.symbols().length === longs.size + shorts.size;
|
|
2052
|
+
return sameActiveShape && noDormantLegs;
|
|
2053
|
+
});
|
|
2054
|
+
if (target) {
|
|
2055
|
+
return target;
|
|
2056
|
+
}
|
|
2057
|
+
const symbols = [
|
|
2058
|
+
.../* @__PURE__ */ new Set([
|
|
2059
|
+
...leftoverLongs,
|
|
2060
|
+
...leftoverShorts
|
|
2061
|
+
])
|
|
2062
|
+
];
|
|
2063
|
+
const positionKey = book.keyFor(symbols);
|
|
2064
|
+
return book.findLiveByKey(positionKey, at) ?? book.create(positionKey);
|
|
2065
|
+
}
|
|
2066
|
+
__name(resolveLeftoverTarget, "resolveLeftoverTarget");
|
|
2067
|
+
function applyLeftover(target, leftover) {
|
|
2068
|
+
for (const entry of leftover) {
|
|
2069
|
+
target.parts.push(...entry.leg.take(entry.qty));
|
|
2070
|
+
target.exposure.addSigned(entry.leg.symbol, entry.qty.toFixed(), entry.side);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
__name(applyLeftover, "applyLeftover");
|
|
2074
|
+
|
|
1972
2075
|
// src/sync/applicables/fills.ts
|
|
1973
2076
|
function assignPartIds(positions) {
|
|
1974
2077
|
const bySource = /* @__PURE__ */ new Map();
|
|
@@ -2007,7 +2110,7 @@ function assignPartIds(positions) {
|
|
|
2007
2110
|
};
|
|
2008
2111
|
}
|
|
2009
2112
|
__name(assignPartIds, "assignPartIds");
|
|
2010
|
-
function partToApplicableFill(part, id, connector) {
|
|
2113
|
+
function partToApplicableFill(part, id, connector, liquidationOutcome) {
|
|
2011
2114
|
const fill = part.source;
|
|
2012
2115
|
const base = {
|
|
2013
2116
|
exchangeFillId: id.exchangeFillId,
|
|
@@ -2040,7 +2143,10 @@ function partToApplicableFill(part, id, connector) {
|
|
|
2040
2143
|
isExternal: !fromPear,
|
|
2041
2144
|
tradeFee: feeShareForPart(fill.tradeFee, part.quantity, fill.quantity),
|
|
2042
2145
|
builderFee: feeShareForPart(fill.builderFee, part.quantity, fill.quantity),
|
|
2043
|
-
liquidation: fill.liquidation
|
|
2146
|
+
liquidation: fill.liquidation ? {
|
|
2147
|
+
mark: fill.liquidation.mark,
|
|
2148
|
+
outcome: liquidationOutcome ?? void 0
|
|
2149
|
+
} : null,
|
|
2044
2150
|
...id.original ? {
|
|
2045
2151
|
original: id.original
|
|
2046
2152
|
} : {}
|
|
@@ -2074,7 +2180,7 @@ function assembleApplicables(args) {
|
|
|
2074
2180
|
}
|
|
2075
2181
|
__name(assembleApplicables, "assembleApplicables");
|
|
2076
2182
|
function assembleOneApplicable(pos, connector, partIdFor, assignedSynthetics, now) {
|
|
2077
|
-
const realFills
|
|
2183
|
+
const { realFills, closedReason } = finalizeRealFills(pos, connector, partIdFor);
|
|
2078
2184
|
const syntheticFills = assignedSynthetics.map((synthetic) => buildSyntheticFill({
|
|
2079
2185
|
positionKey: pos.positionKey,
|
|
2080
2186
|
connector,
|
|
@@ -2098,13 +2204,38 @@ function assembleOneApplicable(pos, connector, partIdFor, assignedSynthetics, no
|
|
|
2098
2204
|
origin,
|
|
2099
2205
|
status,
|
|
2100
2206
|
...closed ? {
|
|
2101
|
-
closedReason
|
|
2207
|
+
closedReason
|
|
2102
2208
|
} : {},
|
|
2103
2209
|
fills,
|
|
2104
2210
|
metadata: buildMetadata(new Set(exposure.symbols()), realFills)
|
|
2105
2211
|
};
|
|
2106
2212
|
}
|
|
2107
2213
|
__name(assembleOneApplicable, "assembleOneApplicable");
|
|
2214
|
+
function finalizeRealFills(position, connector, partIdFor) {
|
|
2215
|
+
const newestFirst = [
|
|
2216
|
+
...position.parts
|
|
2217
|
+
].sort((a, b) => a.source.timestamp - b.source.timestamp).reverse();
|
|
2218
|
+
const exposure = position.exposure.clone();
|
|
2219
|
+
const realFills = [];
|
|
2220
|
+
let closedReason = "NETTED";
|
|
2221
|
+
for (const [index, part] of newestFirst.entries()) {
|
|
2222
|
+
const after = exposure.get(part.source.symbol);
|
|
2223
|
+
const flatAfter = exposure.isFlat();
|
|
2224
|
+
exposure.addDelta(part.source.symbol, negate(signedBySide(part.quantity, part.source.side)));
|
|
2225
|
+
const reducesLeg = isGreaterThan(exposure.get(part.source.symbol).abs(), after.abs());
|
|
2226
|
+
const liquidationOutcome = part.source.liquidation && reducesLeg ? flatAfter ? "CLOSED" : "REDUCED" : null;
|
|
2227
|
+
if (index === 0 && position.closed && liquidationOutcome === "CLOSED") {
|
|
2228
|
+
closedReason = "LIQUIDATED";
|
|
2229
|
+
}
|
|
2230
|
+
realFills.push(partToApplicableFill(part, partIdFor(part), connector, liquidationOutcome));
|
|
2231
|
+
}
|
|
2232
|
+
realFills.reverse();
|
|
2233
|
+
return {
|
|
2234
|
+
realFills,
|
|
2235
|
+
closedReason
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
__name(finalizeRealFills, "finalizeRealFills");
|
|
2108
2239
|
function finalExposure(pos, synthetics) {
|
|
2109
2240
|
const exposure = pos.exposure.clone();
|
|
2110
2241
|
for (const synthetic of synthetics) {
|
|
@@ -2355,8 +2486,7 @@ __name(isStrictTimestampBoundary, "isStrictTimestampBoundary");
|
|
|
2355
2486
|
async function computeSyncPayload(args) {
|
|
2356
2487
|
const { tradeAccountId, connector, openPositions, entryBySymbol, provisionalFills, confirmedExchangeFillIds, externalFills, exchangePositions, neverSynced } = args;
|
|
2357
2488
|
const fillsToProcess = _prepareExternalFills(externalFills, confirmedExchangeFillIds);
|
|
2358
|
-
const
|
|
2359
|
-
const confirmablesByPosition = groupConfirmablesByPosition(confirmableFills);
|
|
2489
|
+
const orphans = _excludeClaimedFills(provisionalFills, fillsToProcess);
|
|
2360
2490
|
const { keptOrphans, firstSyncTrim } = applyFirstSyncCutoff({
|
|
2361
2491
|
neverSynced,
|
|
2362
2492
|
fillsToProcess,
|
|
@@ -2373,13 +2503,11 @@ async function computeSyncPayload(args) {
|
|
|
2373
2503
|
exchangePositions
|
|
2374
2504
|
});
|
|
2375
2505
|
const mustPostFirstSyncCheckpoint = neverSynced;
|
|
2376
|
-
if (!mustPostFirstSyncCheckpoint &&
|
|
2506
|
+
if (!mustPostFirstSyncCheckpoint && applicables.length === 0) {
|
|
2377
2507
|
return null;
|
|
2378
2508
|
}
|
|
2379
2509
|
const externalMaxTs = latestFillTimestamp(fillsToProcess);
|
|
2380
2510
|
return {
|
|
2381
|
-
reconcilables: Object.keys(reconcilables).length > 0 ? reconcilables : null,
|
|
2382
|
-
confirmables: Object.keys(confirmablesByPosition).length > 0 ? confirmablesByPosition : null,
|
|
2383
2511
|
applicables,
|
|
2384
2512
|
exchangePositions,
|
|
2385
2513
|
meta: {
|
|
@@ -2421,77 +2549,20 @@ function _prepareExternalFills(externalFills, confirmedExchangeFillIdsInput) {
|
|
|
2421
2549
|
return Array.from(dedupedByExchangeFillId.values()).sort((a, b) => a.timestamp - b.timestamp);
|
|
2422
2550
|
}
|
|
2423
2551
|
__name(_prepareExternalFills, "_prepareExternalFills");
|
|
2424
|
-
function
|
|
2425
|
-
const
|
|
2552
|
+
function _excludeClaimedFills(provisionalFills, externalFills) {
|
|
2553
|
+
const ownedCloids = /* @__PURE__ */ new Set();
|
|
2426
2554
|
for (const fill of provisionalFills) {
|
|
2427
2555
|
const cloid = _normalizeCloid(fill.cloid);
|
|
2428
|
-
if (
|
|
2429
|
-
|
|
2556
|
+
if (cloid) {
|
|
2557
|
+
ownedCloids.add(cloid);
|
|
2430
2558
|
}
|
|
2431
|
-
provisionalFillsMap.set(cloid, fill);
|
|
2432
2559
|
}
|
|
2433
|
-
|
|
2434
|
-
const fillsByCloid = /* @__PURE__ */ new Map();
|
|
2435
|
-
for (const fill of externalFills) {
|
|
2560
|
+
return externalFills.filter((fill) => {
|
|
2436
2561
|
const cloid = _normalizeCloid(fill.cloid);
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
} else {
|
|
2440
|
-
fillsByCloid.set(cloid, [
|
|
2441
|
-
...fillsByCloid.get(cloid) || [],
|
|
2442
|
-
fill
|
|
2443
|
-
]);
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
const reconcilables = {};
|
|
2447
|
-
const confirmableFills = [];
|
|
2448
|
-
for (const [cloid, fills] of fillsByCloid.entries()) {
|
|
2449
|
-
if (fills.length > 1) {
|
|
2450
|
-
const provisional = provisionalFillsMap.get(cloid);
|
|
2451
|
-
if (provisional) {
|
|
2452
|
-
const [first, second, ...rest] = fills;
|
|
2453
|
-
if (!first || !second) {
|
|
2454
|
-
throw new Error("Corrupted state: sibling group must contain at least 2 fills for reconcile");
|
|
2455
|
-
}
|
|
2456
|
-
reconcilables[cloid] = [
|
|
2457
|
-
{
|
|
2458
|
-
...first,
|
|
2459
|
-
cloid
|
|
2460
|
-
},
|
|
2461
|
-
{
|
|
2462
|
-
...second,
|
|
2463
|
-
cloid
|
|
2464
|
-
},
|
|
2465
|
-
...rest.map((fill) => ({
|
|
2466
|
-
...fill,
|
|
2467
|
-
cloid
|
|
2468
|
-
}))
|
|
2469
|
-
];
|
|
2470
|
-
} else {
|
|
2471
|
-
orphans.push(...fills);
|
|
2472
|
-
}
|
|
2473
|
-
} else {
|
|
2474
|
-
const fill = fills[0];
|
|
2475
|
-
const provisional = provisionalFillsMap.get(cloid);
|
|
2476
|
-
if (provisional) {
|
|
2477
|
-
confirmableFills.push({
|
|
2478
|
-
...fill,
|
|
2479
|
-
type: "confirm",
|
|
2480
|
-
cloid,
|
|
2481
|
-
positionId: provisional.positionId
|
|
2482
|
-
});
|
|
2483
|
-
} else {
|
|
2484
|
-
orphans.push(fill);
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
return {
|
|
2489
|
-
reconcilables,
|
|
2490
|
-
confirmables: confirmableFills,
|
|
2491
|
-
orphans
|
|
2492
|
-
};
|
|
2562
|
+
return cloid === void 0 || !ownedCloids.has(cloid);
|
|
2563
|
+
});
|
|
2493
2564
|
}
|
|
2494
|
-
__name(
|
|
2565
|
+
__name(_excludeClaimedFills, "_excludeClaimedFills");
|
|
2495
2566
|
function _normalizeCloid(cloid) {
|
|
2496
2567
|
const value = cloid?.trim();
|
|
2497
2568
|
if (!value) {
|
|
@@ -2525,16 +2596,6 @@ function _isValidExternalTimestamp(timestamp) {
|
|
|
2525
2596
|
return timestamp <= Date.now() + WATERMARK_FUTURE_SKEW_MS;
|
|
2526
2597
|
}
|
|
2527
2598
|
__name(_isValidExternalTimestamp, "_isValidExternalTimestamp");
|
|
2528
|
-
function groupConfirmablesByPosition(confirmableFills) {
|
|
2529
|
-
return confirmableFills.reduce((acc, fill) => {
|
|
2530
|
-
acc[fill.positionId] = [
|
|
2531
|
-
...acc[fill.positionId] || [],
|
|
2532
|
-
fill
|
|
2533
|
-
];
|
|
2534
|
-
return acc;
|
|
2535
|
-
}, {});
|
|
2536
|
-
}
|
|
2537
|
-
__name(groupConfirmablesByPosition, "groupConfirmablesByPosition");
|
|
2538
2599
|
function latestFillTimestamp(fills) {
|
|
2539
2600
|
const last = fills[fills.length - 1];
|
|
2540
2601
|
if (!last) {
|
|
@@ -2585,10 +2646,6 @@ function applyFirstSyncCutoff(args) {
|
|
|
2585
2646
|
};
|
|
2586
2647
|
}
|
|
2587
2648
|
__name(applyFirstSyncCutoff, "applyFirstSyncCutoff");
|
|
2588
|
-
function isEmptyCycle(reconcilables, confirmablesByPosition, applicables) {
|
|
2589
|
-
return Object.keys(reconcilables).length === 0 && Object.keys(confirmablesByPosition).length === 0 && applicables.length === 0;
|
|
2590
|
-
}
|
|
2591
|
-
__name(isEmptyCycle, "isEmptyCycle");
|
|
2592
2649
|
|
|
2593
2650
|
// src/validate-leverage.ts
|
|
2594
2651
|
function validateLeverage(leverage, instrument) {
|
|
@@ -2629,6 +2686,246 @@ function validateQuantity(quantity, price, precision) {
|
|
|
2629
2686
|
return true;
|
|
2630
2687
|
}
|
|
2631
2688
|
__name(validateQuantity, "validateQuantity");
|
|
2689
|
+
var VAULT_ACTION_DOMAIN = {
|
|
2690
|
+
name: "PearVaultService",
|
|
2691
|
+
version: "1",
|
|
2692
|
+
chainId: 999
|
|
2693
|
+
};
|
|
2694
|
+
var VAULT_ACTION_TYPES = {
|
|
2695
|
+
"withdraw-to-evm": {
|
|
2696
|
+
WithdrawToEvm: [
|
|
2697
|
+
{
|
|
2698
|
+
name: "vault",
|
|
2699
|
+
type: "address"
|
|
2700
|
+
},
|
|
2701
|
+
{
|
|
2702
|
+
name: "amount",
|
|
2703
|
+
type: "string"
|
|
2704
|
+
},
|
|
2705
|
+
{
|
|
2706
|
+
name: "token",
|
|
2707
|
+
type: "string"
|
|
2708
|
+
},
|
|
2709
|
+
{
|
|
2710
|
+
name: "nonce",
|
|
2711
|
+
type: "string"
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2714
|
+
name: "deadline",
|
|
2715
|
+
type: "uint256"
|
|
2716
|
+
}
|
|
2717
|
+
]
|
|
2718
|
+
},
|
|
2719
|
+
"set-trader": {
|
|
2720
|
+
SetTrader: [
|
|
2721
|
+
{
|
|
2722
|
+
name: "vault",
|
|
2723
|
+
type: "address"
|
|
2724
|
+
},
|
|
2725
|
+
{
|
|
2726
|
+
name: "trader",
|
|
2727
|
+
type: "address"
|
|
2728
|
+
},
|
|
2729
|
+
{
|
|
2730
|
+
name: "nonce",
|
|
2731
|
+
type: "string"
|
|
2732
|
+
},
|
|
2733
|
+
{
|
|
2734
|
+
name: "deadline",
|
|
2735
|
+
type: "uint256"
|
|
2736
|
+
}
|
|
2737
|
+
]
|
|
2738
|
+
},
|
|
2739
|
+
"agent-approve": {
|
|
2740
|
+
AgentApprove: [
|
|
2741
|
+
{
|
|
2742
|
+
name: "vault",
|
|
2743
|
+
type: "address"
|
|
2744
|
+
},
|
|
2745
|
+
{
|
|
2746
|
+
name: "nonce",
|
|
2747
|
+
type: "string"
|
|
2748
|
+
},
|
|
2749
|
+
{
|
|
2750
|
+
name: "deadline",
|
|
2751
|
+
type: "uint256"
|
|
2752
|
+
}
|
|
2753
|
+
]
|
|
2754
|
+
},
|
|
2755
|
+
"set-unified-account": {
|
|
2756
|
+
SetUnifiedAccount: [
|
|
2757
|
+
{
|
|
2758
|
+
name: "vault",
|
|
2759
|
+
type: "address"
|
|
2760
|
+
},
|
|
2761
|
+
{
|
|
2762
|
+
name: "nonce",
|
|
2763
|
+
type: "string"
|
|
2764
|
+
},
|
|
2765
|
+
{
|
|
2766
|
+
name: "deadline",
|
|
2767
|
+
type: "uint256"
|
|
2768
|
+
}
|
|
2769
|
+
]
|
|
2770
|
+
},
|
|
2771
|
+
"update-metadata": {
|
|
2772
|
+
UpdateVaultMetadata: [
|
|
2773
|
+
{
|
|
2774
|
+
name: "vault",
|
|
2775
|
+
type: "address"
|
|
2776
|
+
},
|
|
2777
|
+
{
|
|
2778
|
+
name: "name",
|
|
2779
|
+
type: "string"
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
name: "about",
|
|
2783
|
+
type: "string"
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
name: "imageUri",
|
|
2787
|
+
type: "string"
|
|
2788
|
+
},
|
|
2789
|
+
{
|
|
2790
|
+
name: "nonce",
|
|
2791
|
+
type: "string"
|
|
2792
|
+
},
|
|
2793
|
+
{
|
|
2794
|
+
name: "deadline",
|
|
2795
|
+
type: "uint256"
|
|
2796
|
+
}
|
|
2797
|
+
]
|
|
2798
|
+
}
|
|
2799
|
+
};
|
|
2800
|
+
var CREATE_VAULT_TYPES = {
|
|
2801
|
+
CreateVault: [
|
|
2802
|
+
{
|
|
2803
|
+
name: "leader",
|
|
2804
|
+
type: "address"
|
|
2805
|
+
},
|
|
2806
|
+
{
|
|
2807
|
+
name: "name",
|
|
2808
|
+
type: "string"
|
|
2809
|
+
},
|
|
2810
|
+
{
|
|
2811
|
+
name: "description",
|
|
2812
|
+
type: "string"
|
|
2813
|
+
},
|
|
2814
|
+
{
|
|
2815
|
+
name: "imageUri",
|
|
2816
|
+
type: "string"
|
|
2817
|
+
},
|
|
2818
|
+
{
|
|
2819
|
+
name: "configId",
|
|
2820
|
+
type: "uint256"
|
|
2821
|
+
},
|
|
2822
|
+
{
|
|
2823
|
+
name: "maxWithdrawalPeriodDays",
|
|
2824
|
+
type: "uint256"
|
|
2825
|
+
},
|
|
2826
|
+
{
|
|
2827
|
+
name: "nonce",
|
|
2828
|
+
type: "string"
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
name: "deadline",
|
|
2832
|
+
type: "uint256"
|
|
2833
|
+
}
|
|
2834
|
+
]
|
|
2835
|
+
};
|
|
2836
|
+
function buildLeaderActionTypedData(action, vaultAddress, body) {
|
|
2837
|
+
const vault = checksumAddress(vaultAddress);
|
|
2838
|
+
const nonce = String(body.nonce ?? "");
|
|
2839
|
+
const deadline = BigInt(body.deadline ?? 0);
|
|
2840
|
+
switch (action) {
|
|
2841
|
+
case "withdraw-to-evm":
|
|
2842
|
+
return {
|
|
2843
|
+
types: VAULT_ACTION_TYPES[action],
|
|
2844
|
+
primaryType: "WithdrawToEvm",
|
|
2845
|
+
message: {
|
|
2846
|
+
vault,
|
|
2847
|
+
amount: String(body.amount ?? ""),
|
|
2848
|
+
token: String(body.tokenId ?? "0"),
|
|
2849
|
+
nonce,
|
|
2850
|
+
deadline
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
case "set-trader":
|
|
2854
|
+
return {
|
|
2855
|
+
types: VAULT_ACTION_TYPES[action],
|
|
2856
|
+
primaryType: "SetTrader",
|
|
2857
|
+
message: {
|
|
2858
|
+
vault,
|
|
2859
|
+
trader: checksumAddress(String(body.traderAddress ?? "")),
|
|
2860
|
+
nonce,
|
|
2861
|
+
deadline
|
|
2862
|
+
}
|
|
2863
|
+
};
|
|
2864
|
+
case "agent-approve":
|
|
2865
|
+
return {
|
|
2866
|
+
types: VAULT_ACTION_TYPES[action],
|
|
2867
|
+
primaryType: "AgentApprove",
|
|
2868
|
+
message: {
|
|
2869
|
+
vault,
|
|
2870
|
+
nonce,
|
|
2871
|
+
deadline
|
|
2872
|
+
}
|
|
2873
|
+
};
|
|
2874
|
+
case "set-unified-account":
|
|
2875
|
+
return {
|
|
2876
|
+
types: VAULT_ACTION_TYPES[action],
|
|
2877
|
+
primaryType: "SetUnifiedAccount",
|
|
2878
|
+
message: {
|
|
2879
|
+
vault,
|
|
2880
|
+
nonce,
|
|
2881
|
+
deadline
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
case "update-metadata":
|
|
2885
|
+
return {
|
|
2886
|
+
types: VAULT_ACTION_TYPES[action],
|
|
2887
|
+
primaryType: "UpdateVaultMetadata",
|
|
2888
|
+
message: {
|
|
2889
|
+
vault,
|
|
2890
|
+
name: String(body.name ?? ""),
|
|
2891
|
+
about: String(body.about ?? ""),
|
|
2892
|
+
imageUri: String(body.imageUri ?? ""),
|
|
2893
|
+
nonce,
|
|
2894
|
+
deadline
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
__name(buildLeaderActionTypedData, "buildLeaderActionTypedData");
|
|
2900
|
+
function buildCreateVaultMessage(body) {
|
|
2901
|
+
return {
|
|
2902
|
+
leader: checksumAddress(String(body.leader ?? "")),
|
|
2903
|
+
name: String(body.name ?? ""),
|
|
2904
|
+
description: String(body.description ?? ""),
|
|
2905
|
+
imageUri: String(body.imageUri ?? ""),
|
|
2906
|
+
configId: BigInt(String(body.configId ?? 0)),
|
|
2907
|
+
maxWithdrawalPeriodDays: BigInt(String(body.maxWithdrawalPeriodDays ?? 0)),
|
|
2908
|
+
nonce: String(body.nonce ?? ""),
|
|
2909
|
+
deadline: BigInt(body.deadline ?? 0)
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
__name(buildCreateVaultMessage, "buildCreateVaultMessage");
|
|
2913
|
+
function checksumAddress(address) {
|
|
2914
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
2915
|
+
throw new Error(`invalid address: ${address}`);
|
|
2916
|
+
}
|
|
2917
|
+
const lower = address.slice(2).toLowerCase();
|
|
2918
|
+
const hash = keccak_256(utf8ToBytes(lower));
|
|
2919
|
+
let out = "";
|
|
2920
|
+
for (const [index, byte] of hash.subarray(0, 20).entries()) {
|
|
2921
|
+
const high = lower.charAt(index * 2);
|
|
2922
|
+
const low = lower.charAt(index * 2 + 1);
|
|
2923
|
+
out += byte >> 4 >= 8 ? high.toUpperCase() : high;
|
|
2924
|
+
out += (byte & 15) >= 8 ? low.toUpperCase() : low;
|
|
2925
|
+
}
|
|
2926
|
+
return `0x${out}`;
|
|
2927
|
+
}
|
|
2928
|
+
__name(checksumAddress, "checksumAddress");
|
|
2632
2929
|
var STORAGE_PREFIX = "pear.x-handoff.";
|
|
2633
2930
|
function startXHandoff(storage) {
|
|
2634
2931
|
const handoffId = base64urlnopad.encode(randomBytes(16));
|
|
@@ -2700,4 +2997,4 @@ function takeVerifier(handoffId, storage) {
|
|
|
2700
2997
|
}
|
|
2701
2998
|
__name(takeVerifier, "takeVerifier");
|
|
2702
2999
|
|
|
2703
|
-
export { BIPS, ZERO, abs, add, aggregateRealizedPositionState, baseToContracts, buildApplicables, compareValue, computeAssetEntryNotional, computeAssetGrossRealizedPnL, computeAssetRealizedPnLs, computeAssetUndeterminedSizes, computeAssetUnrealizedPnL, computeAssetUnrealizedPnLs, computeBasketWeightedRatioV1, computeDeployedCapital, computeDeployedCapitalState, computeEntryPriceForAsset, computeFillTransitions, computeMaxBasketSize, computeMultiQuoteBasketSize, computePositionEntryPrices, computeRealizedPnlBySymbol, computeRealizedPnlFromFills, computeRealizedPositionState, computeSyncPayload, computeUnrealizedPositionState, computeUnrealizedPositionStateFromEntryPrices, configureProxy, contractsToBase, countDecimals, deriveBasketKey, divide, exponentiate, finishXHandoff, generateDeterministicPositionKey, groupFillsByAsset, groupFillsByPosition, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isZero, min, multiply, negate, parse, precisePrice, preciseQuantity, proxyFetch, sideSign, sign, signed, signedBySide, sizeBasket, sizeBasketByLeverageMode, sizeBasketPerAssetLeverage, startXHandoff, stripXReturn, subtract, sumAttributedFunding, toDecimalString, validateLeverage, validateQuantity, walkPositionLedger, walkPositionTransitions };
|
|
3000
|
+
export { BIPS, CREATE_VAULT_TYPES, VAULT_ACTION_DOMAIN, VAULT_ACTION_TYPES, ZERO, abs, add, aggregateRealizedPositionState, baseToContracts, buildApplicables, buildCreateVaultMessage, buildLeaderActionTypedData, compareValue, computeAssetEntryNotional, computeAssetGrossRealizedPnL, computeAssetRealizedPnLs, computeAssetUndeterminedSizes, computeAssetUnrealizedPnL, computeAssetUnrealizedPnLs, computeBasketWeightedRatioV1, computeDeployedCapital, computeDeployedCapitalState, computeEntryEquityFraction, computeEntryPriceForAsset, computeEquityFraction, computeFillTransitions, computeFilledEntrySizing, computeMaxBasketSize, computeMultiQuoteBasketSize, computeNotionalEquityFraction, computePositionEntryPrices, computeRealizedPnlBySymbol, computeRealizedPnlFromFills, computeRealizedPositionState, computeSyncPayload, computeUnrealizedPositionState, computeUnrealizedPositionStateFromEntryPrices, configureProxy, contractsToBase, countDecimals, deriveBasketKey, divide, exponentiate, finishXHandoff, generateDeterministicPositionKey, groupFillsByAsset, groupFillsByPosition, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isZero, min, multiply, negate, parse, precisePrice, preciseQuantity, proxyFetch, sideSign, sign, signed, signedBySide, sizeBasket, sizeBasketByLeverageMode, sizeBasketPerAssetLeverage, startXHandoff, stripXReturn, subtract, sumAttributedFunding, toDecimalString, validateLeverage, validateQuantity, walkPositionLedger, walkPositionTransitions };
|
package/dist/position/key.d.ts
CHANGED
|
@@ -11,8 +11,8 @@ type PositionKeyInput = {
|
|
|
11
11
|
* exactly what the active-position unique index relies on and what makes the
|
|
12
12
|
* apply UPSERT find-or-create the single correct target.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* (`leg.id ?? leg.symbol`, or exposure keys). A
|
|
14
|
+
* SYMBOL-MIGRATION id-forever: `symbols` must be instrument ids
|
|
15
|
+
* (`leg.id ?? leg.symbol`, or exposure keys). A display ticker here re-hashes and orphans every position.
|
|
16
16
|
*/
|
|
17
17
|
export declare function generateDeterministicPositionKey(input: PositionKeyInput): string;
|
|
18
18
|
export {};
|
|
@@ -12,6 +12,7 @@ import type { FillPart } from './types';
|
|
|
12
12
|
*/
|
|
13
13
|
export declare class Basket {
|
|
14
14
|
readonly legs: CombinedLeg[];
|
|
15
|
+
readonly fills: HttpTypes.UnknownExternalFill[];
|
|
15
16
|
private constructor();
|
|
16
17
|
/** Detect baskets from a fill stream, returned in ascending time order. */
|
|
17
18
|
static detect(fills: HttpTypes.UnknownExternalFill[]): Basket[];
|
|
@@ -9,6 +9,13 @@ import type { BasketRule } from './types';
|
|
|
9
9
|
* tolerance: a genuine partial close (>= 1e-8) stays open, as it should.
|
|
10
10
|
*/
|
|
11
11
|
export declare const DUST: BigNumber;
|
|
12
|
+
/**
|
|
13
|
+
* How long before an open position's creation a fill may still belong to it. Fills can
|
|
14
|
+
* reach sync out of order by seconds across cycles (observed up to ~30s), so a strict
|
|
15
|
+
* cutoff would split one position in two; anything older predates the position and must
|
|
16
|
+
* not be replayed into it (see pear-protocol/backend#1067).
|
|
17
|
+
*/
|
|
18
|
+
export declare const LATE_FILL_GRACE_MS: number;
|
|
12
19
|
export declare const MISSED_HISTORY_FILL_PREFIX = "opening-synthetic-";
|
|
13
20
|
export declare const BASKET_RULES: BasketRule[];
|
|
14
21
|
/**
|
|
@@ -13,4 +13,4 @@ export declare function assignPartIds(positions: MatchedPosition[]): (part: Fill
|
|
|
13
13
|
* missed-history synthetic (external, priced to reconcile with the exchange entry).
|
|
14
14
|
* Fees are quantity-prorated when the source fill was split across positions.
|
|
15
15
|
*/
|
|
16
|
-
export declare function partToApplicableFill(part: FillPart, id: PartId, connector: HttpTypes.Connector): HttpTypes.ApplicableFill;
|
|
16
|
+
export declare function partToApplicableFill(part: FillPart, id: PartId, connector: HttpTypes.Connector, liquidationOutcome: 'CLOSED' | 'REDUCED' | null): HttpTypes.ApplicableFill;
|
|
@@ -9,6 +9,11 @@ import type { MatchedPosition } from './types';
|
|
|
9
9
|
* - resolveLeftoverTarget merges into a position whose FULL leg set (active and
|
|
10
10
|
* dormant) is exactly the leftover's, else opens new (same-shape ADDs consolidate here).
|
|
11
11
|
* The asset SET of a position never grows, so positionKey == key(opening symbols).
|
|
12
|
+
*
|
|
13
|
+
* Every phase only sees positions that already existed when the basket happened: a
|
|
14
|
+
* basket older than an open position's creation (beyond LATE_FILL_GRACE_MS) cannot
|
|
15
|
+
* close, reduce or merge into it, so a late-fetched earlier lifecycle is not bound to a
|
|
16
|
+
* position opened after it (pear-protocol/backend#1067).
|
|
12
17
|
*/
|
|
13
18
|
export declare function matchPositions(args: {
|
|
14
19
|
tradeAccountId: string;
|
|
@@ -31,6 +31,11 @@ export type WorkingPosition = {
|
|
|
31
31
|
closed: boolean;
|
|
32
32
|
/** Origin of the seeded existing position; undefined for positions opened mid-replay. */
|
|
33
33
|
existingOrigin?: HttpTypes.PositionOrigin;
|
|
34
|
+
/**
|
|
35
|
+
* Earliest basket time (ms) this position may take fills from: its creation minus the
|
|
36
|
+
* late-fill grace for a seeded position, -Infinity for one opened mid-replay.
|
|
37
|
+
*/
|
|
38
|
+
liveFrom: number;
|
|
34
39
|
};
|
|
35
40
|
/** A matched position that received fill slices, ready to become an applicable. */
|
|
36
41
|
export type MatchedPosition = {
|
package/dist/sync/compute.d.ts
CHANGED
|
@@ -14,15 +14,10 @@ export declare function computeSyncPayload(args: ComputeSyncPayloadArgs): Promis
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function _prepareExternalFills(externalFills: HttpTypes.UnknownExternalFill[], confirmedExchangeFillIdsInput: string[]): HttpTypes.UnknownExternalFill[];
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* everything else (no cloid, or cloid we do not own) becomes an orphan.
|
|
17
|
+
* Drop external fills whose cloid is claimed by a provisional fill we own.
|
|
18
|
+
* The server-side syncer owns confirming those fills; the client applies orphans only.
|
|
20
19
|
*/
|
|
21
|
-
export declare function
|
|
22
|
-
reconcilables: HttpTypes.ReconcileFillsRequest;
|
|
23
|
-
confirmables: HttpTypes.ConfirmableExternalFill[];
|
|
24
|
-
orphans: HttpTypes.UnknownExternalFill[];
|
|
25
|
-
};
|
|
20
|
+
export declare function _excludeClaimedFills(provisionalFills: HttpTypes.ProvisionalFill[], externalFills: HttpTypes.UnknownExternalFill[]): HttpTypes.UnknownExternalFill[];
|
|
26
21
|
/** Trim a cloid; empty or whitespace-only collapses to undefined. */
|
|
27
22
|
export declare function _normalizeCloid(cloid?: string | null): string | undefined;
|
|
28
23
|
/** Normalise an exchange fill id: trim + lowercase; absent or blank → undefined. */
|
package/dist/sync/types.d.ts
CHANGED
|
@@ -11,8 +11,6 @@ export type ComputeSyncPayloadArgs = {
|
|
|
11
11
|
neverSynced: boolean;
|
|
12
12
|
};
|
|
13
13
|
export type ComputeSyncPayloadResult = {
|
|
14
|
-
reconcilables: HttpTypes.ReconcileFillsRequest | null;
|
|
15
|
-
confirmables: HttpTypes.ConfirmFillsRequest | null;
|
|
16
14
|
applicables: HttpTypes.ApplicablePosition[];
|
|
17
15
|
exchangePositions: HttpTypes.ExternalPosition[];
|
|
18
16
|
meta: {
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every action a leader-signed route may carry. A new route must add its member
|
|
3
|
+
* here, which forces the typed-data switch below to name it at build time.
|
|
4
|
+
*/
|
|
5
|
+
export type LeaderActionName = 'withdraw-to-evm' | 'set-trader' | 'agent-approve' | 'set-unified-account' | 'update-metadata';
|
|
6
|
+
/**
|
|
7
|
+
* EIP-712 domain for off-chain vault-service auth. `chainId` is pinned to 999
|
|
8
|
+
* (HyperEVM) by the shared contract — signing is offline, so there is no chain
|
|
9
|
+
* switch and this must NOT be read from env. `verifyingContract` is omitted on
|
|
10
|
+
* purpose: this is service auth, not an on-chain contract call.
|
|
11
|
+
*/
|
|
12
|
+
export declare const VAULT_ACTION_DOMAIN: {
|
|
13
|
+
readonly name: "PearVaultService";
|
|
14
|
+
readonly version: "1";
|
|
15
|
+
readonly chainId: 999;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* EIP-712 types per action — the one copy both signer and verifier import.
|
|
19
|
+
* One struct per action: every signed parameter is a readable field in the
|
|
20
|
+
* wallet prompt, and the primary-type name itself discriminates the action,
|
|
21
|
+
* so structurally identical actions can never replay into each other.
|
|
22
|
+
*/
|
|
23
|
+
export declare const VAULT_ACTION_TYPES: {
|
|
24
|
+
readonly 'withdraw-to-evm': {
|
|
25
|
+
readonly WithdrawToEvm: readonly [{
|
|
26
|
+
readonly name: "vault";
|
|
27
|
+
readonly type: "address";
|
|
28
|
+
}, {
|
|
29
|
+
readonly name: "amount";
|
|
30
|
+
readonly type: "string";
|
|
31
|
+
}, {
|
|
32
|
+
readonly name: "token";
|
|
33
|
+
readonly type: "string";
|
|
34
|
+
}, {
|
|
35
|
+
readonly name: "nonce";
|
|
36
|
+
readonly type: "string";
|
|
37
|
+
}, {
|
|
38
|
+
readonly name: "deadline";
|
|
39
|
+
readonly type: "uint256";
|
|
40
|
+
}];
|
|
41
|
+
};
|
|
42
|
+
readonly 'set-trader': {
|
|
43
|
+
readonly SetTrader: readonly [{
|
|
44
|
+
readonly name: "vault";
|
|
45
|
+
readonly type: "address";
|
|
46
|
+
}, {
|
|
47
|
+
readonly name: "trader";
|
|
48
|
+
readonly type: "address";
|
|
49
|
+
}, {
|
|
50
|
+
readonly name: "nonce";
|
|
51
|
+
readonly type: "string";
|
|
52
|
+
}, {
|
|
53
|
+
readonly name: "deadline";
|
|
54
|
+
readonly type: "uint256";
|
|
55
|
+
}];
|
|
56
|
+
};
|
|
57
|
+
readonly 'agent-approve': {
|
|
58
|
+
readonly AgentApprove: readonly [{
|
|
59
|
+
readonly name: "vault";
|
|
60
|
+
readonly type: "address";
|
|
61
|
+
}, {
|
|
62
|
+
readonly name: "nonce";
|
|
63
|
+
readonly type: "string";
|
|
64
|
+
}, {
|
|
65
|
+
readonly name: "deadline";
|
|
66
|
+
readonly type: "uint256";
|
|
67
|
+
}];
|
|
68
|
+
};
|
|
69
|
+
readonly 'set-unified-account': {
|
|
70
|
+
readonly SetUnifiedAccount: readonly [{
|
|
71
|
+
readonly name: "vault";
|
|
72
|
+
readonly type: "address";
|
|
73
|
+
}, {
|
|
74
|
+
readonly name: "nonce";
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
}, {
|
|
77
|
+
readonly name: "deadline";
|
|
78
|
+
readonly type: "uint256";
|
|
79
|
+
}];
|
|
80
|
+
};
|
|
81
|
+
readonly 'update-metadata': {
|
|
82
|
+
readonly UpdateVaultMetadata: readonly [{
|
|
83
|
+
readonly name: "vault";
|
|
84
|
+
readonly type: "address";
|
|
85
|
+
}, {
|
|
86
|
+
readonly name: "name";
|
|
87
|
+
readonly type: "string";
|
|
88
|
+
}, {
|
|
89
|
+
readonly name: "about";
|
|
90
|
+
readonly type: "string";
|
|
91
|
+
}, {
|
|
92
|
+
readonly name: "imageUri";
|
|
93
|
+
readonly type: "string";
|
|
94
|
+
}, {
|
|
95
|
+
readonly name: "nonce";
|
|
96
|
+
readonly type: "string";
|
|
97
|
+
}, {
|
|
98
|
+
readonly name: "deadline";
|
|
99
|
+
readonly type: "uint256";
|
|
100
|
+
}];
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* EIP-712 type for a create — the one copy both signer and verifier import.
|
|
105
|
+
* Every signed parameter is a readable field in the wallet prompt.
|
|
106
|
+
*/
|
|
107
|
+
export declare const CREATE_VAULT_TYPES: {
|
|
108
|
+
readonly CreateVault: readonly [{
|
|
109
|
+
readonly name: "leader";
|
|
110
|
+
readonly type: "address";
|
|
111
|
+
}, {
|
|
112
|
+
readonly name: "name";
|
|
113
|
+
readonly type: "string";
|
|
114
|
+
}, {
|
|
115
|
+
readonly name: "description";
|
|
116
|
+
readonly type: "string";
|
|
117
|
+
}, {
|
|
118
|
+
readonly name: "imageUri";
|
|
119
|
+
readonly type: "string";
|
|
120
|
+
}, {
|
|
121
|
+
readonly name: "configId";
|
|
122
|
+
readonly type: "uint256";
|
|
123
|
+
}, {
|
|
124
|
+
readonly name: "maxWithdrawalPeriodDays";
|
|
125
|
+
readonly type: "uint256";
|
|
126
|
+
}, {
|
|
127
|
+
readonly name: "nonce";
|
|
128
|
+
readonly type: "string";
|
|
129
|
+
}, {
|
|
130
|
+
readonly name: "deadline";
|
|
131
|
+
readonly type: "uint256";
|
|
132
|
+
}];
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* One action's complete signable typed data. Widened (non-generic) on purpose:
|
|
136
|
+
* viem's generic overloads cannot take a union of per-action shapes at one call site.
|
|
137
|
+
*/
|
|
138
|
+
export type LeaderActionTypedData = {
|
|
139
|
+
types: Record<string, readonly {
|
|
140
|
+
name: string;
|
|
141
|
+
type: string;
|
|
142
|
+
}[]>;
|
|
143
|
+
primaryType: string;
|
|
144
|
+
message: {
|
|
145
|
+
vault: `0x${string}`;
|
|
146
|
+
amount?: string;
|
|
147
|
+
token?: string;
|
|
148
|
+
trader?: `0x${string}`;
|
|
149
|
+
name?: string;
|
|
150
|
+
about?: string;
|
|
151
|
+
imageUri?: string;
|
|
152
|
+
nonce: string;
|
|
153
|
+
deadline: bigint;
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Canonical typed-data reconstruction from the request path + body. Signer and
|
|
158
|
+
* verifier MUST call this same builder with these same normalized values:
|
|
159
|
+
* - vault = the :vaultAddress path segment (checksummed inside the signed EIP-712 message; storage stays lowercase)
|
|
160
|
+
* - amount = body.amount, verbatim decimal string (withdraw only)
|
|
161
|
+
* - token = body.tokenId ?? '0', verbatim string (withdraw only)
|
|
162
|
+
* - trader = body.traderAddress, checksummed (set-trader only)
|
|
163
|
+
* - name / about / imageUri = the full display set, verbatim strings; '' means cleared (update-metadata only)
|
|
164
|
+
* - nonce = body.nonce (client-generated; salts the signature for idempotency)
|
|
165
|
+
* - deadline = body.deadline (unix seconds) as uint256
|
|
166
|
+
*/
|
|
167
|
+
export declare function buildLeaderActionTypedData(action: LeaderActionName, vaultAddress: string, body: Record<string, unknown>): LeaderActionTypedData;
|
|
168
|
+
/**
|
|
169
|
+
* Canonical CreateVault reconstruction from the request body. Signer and verifier
|
|
170
|
+
* MUST call this same builder; absent optionals sign as '' and 0.
|
|
171
|
+
*/
|
|
172
|
+
export declare function buildCreateVaultMessage(body: Record<string, unknown>): {
|
|
173
|
+
leader: `0x${string}`;
|
|
174
|
+
name: string;
|
|
175
|
+
description: string;
|
|
176
|
+
imageUri: string;
|
|
177
|
+
configId: bigint;
|
|
178
|
+
maxWithdrawalPeriodDays: bigint;
|
|
179
|
+
nonce: string;
|
|
180
|
+
deadline: bigint;
|
|
181
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './action-typed-data';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pear-protocol/utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Pear Protocol Utility functions",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@noble/hashes": "^1.3.2",
|
|
30
|
-
"@pear-protocol/types": "^2.
|
|
30
|
+
"@pear-protocol/types": "^2.12.0",
|
|
31
31
|
"@scure/base": "^2.3.0",
|
|
32
32
|
"bignumber.js": "9.3.1"
|
|
33
33
|
},
|