@flayerlabs/gamemode-client 0.4.3 → 0.5.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/embed.js +2 -2
- package/dist/embed.js.map +1 -1
- package/dist/host.js +3 -3
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +16 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/live.d.ts +1 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +76 -39
- package/dist/live.js.map +1 -1
- package/dist/mock.d.ts +3 -3
- package/dist/mock.d.ts.map +1 -1
- package/dist/mock.js +56 -41
- package/dist/mock.js.map +1 -1
- package/dist/replay-market.d.ts +1 -1
- package/dist/replay-market.d.ts.map +1 -1
- package/dist/replay-market.js +6 -6
- package/dist/replay-market.js.map +1 -1
- package/package.json +2 -2
- package/src/embed.ts +4 -4
- package/src/host.ts +3 -3
- package/src/index.ts +16 -7
- package/src/live.ts +80 -40
- package/src/mock.ts +56 -41
- package/src/replay-market.ts +7 -7
package/src/live.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
type PresenceState,
|
|
27
27
|
type Reaction,
|
|
28
28
|
type RoundTerms,
|
|
29
|
+
type SpendToken,
|
|
29
30
|
type WireEconomyBalance,
|
|
30
31
|
type WireMarketState,
|
|
31
32
|
type WireRoundTerms,
|
|
@@ -64,7 +65,7 @@ export interface Host {
|
|
|
64
65
|
authorisation: Authorisation,
|
|
65
66
|
progress?: (event: HostBuyProgress) => void,
|
|
66
67
|
signal?: AbortSignal,
|
|
67
|
-
): Promise<{
|
|
68
|
+
): Promise<{ spent: bigint } | { failed: BuyResult }>;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/** The host reports only what it uniquely knows; LiveRoom owns the terminal result. */
|
|
@@ -377,7 +378,7 @@ class LiveRoom<PublicView, PlayerView, Action> implements Room<PublicView, Playe
|
|
|
377
378
|
readonly economy: Economy = {
|
|
378
379
|
current: () => this.economySignal.current(),
|
|
379
380
|
subscribe: (listener) => this.economySignal.subscribe(listener),
|
|
380
|
-
available: () => this.economySignal.current().
|
|
381
|
+
available: () => this.economySignal.current().available,
|
|
381
382
|
buy: (maxSpendWei) => this.buy(maxSpendWei),
|
|
382
383
|
};
|
|
383
384
|
|
|
@@ -411,9 +412,9 @@ class LiveRoom<PublicView, PlayerView, Action> implements Room<PublicView, Playe
|
|
|
411
412
|
return this.failBuy(attempt, 'try-again');
|
|
412
413
|
}
|
|
413
414
|
|
|
414
|
-
let
|
|
415
|
+
let spend: bigint;
|
|
415
416
|
try {
|
|
416
|
-
|
|
417
|
+
spend = BigInt(authorisation.maxSpendWei);
|
|
417
418
|
} catch {
|
|
418
419
|
return this.failBuy(attempt, 'try-again');
|
|
419
420
|
}
|
|
@@ -424,11 +425,11 @@ class LiveRoom<PublicView, PlayerView, Action> implements Room<PublicView, Playe
|
|
|
424
425
|
...before,
|
|
425
426
|
...(this.balanceRevision === balanceRevision
|
|
426
427
|
? {
|
|
427
|
-
|
|
428
|
-
|
|
428
|
+
held: before.held + spend,
|
|
429
|
+
available: before.available > spend ? before.available - spend : 0n,
|
|
429
430
|
}
|
|
430
431
|
: {}),
|
|
431
|
-
buy: { state: 'signing',
|
|
432
|
+
buy: { state: 'signing', spend },
|
|
432
433
|
});
|
|
433
434
|
|
|
434
435
|
let transactionHash: string | undefined;
|
|
@@ -443,8 +444,8 @@ class LiveRoom<PublicView, PlayerView, Action> implements Room<PublicView, Playe
|
|
|
443
444
|
this.economySignal.set({
|
|
444
445
|
...current,
|
|
445
446
|
buy: transactionHash === undefined
|
|
446
|
-
? { state: 'pending',
|
|
447
|
-
: { state: 'pending',
|
|
447
|
+
? { state: 'pending', spend }
|
|
448
|
+
: { state: 'pending', spend, transactionHash },
|
|
448
449
|
});
|
|
449
450
|
});
|
|
450
451
|
hostSettled = true;
|
|
@@ -466,11 +467,11 @@ class LiveRoom<PublicView, PlayerView, Action> implements Room<PublicView, Playe
|
|
|
466
467
|
|
|
467
468
|
const current = this.economySignal.current();
|
|
468
469
|
const confirmed: BuyProgress = transactionHash === undefined
|
|
469
|
-
? { state: 'confirmed',
|
|
470
|
-
: { state: 'confirmed',
|
|
470
|
+
? { state: 'confirmed', spent: result.spent }
|
|
471
|
+
: { state: 'confirmed', spent: result.spent, transactionHash };
|
|
471
472
|
// The signed allowance remains held until the gate observes chain settlement or it expires.
|
|
472
473
|
this.economySignal.set({ ...current, buy: confirmed });
|
|
473
|
-
return { bought: true,
|
|
474
|
+
return { bought: true, spent: result.spent };
|
|
474
475
|
}
|
|
475
476
|
|
|
476
477
|
readonly social: Social = {
|
|
@@ -567,11 +568,11 @@ class LiveRoom<PublicView, PlayerView, Action> implements Room<PublicView, Playe
|
|
|
567
568
|
|
|
568
569
|
function emptyEconomy(): EconomyState {
|
|
569
570
|
return {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
571
|
+
unitsPerPoint: 0n,
|
|
572
|
+
earned: 0n,
|
|
573
|
+
held: 0n,
|
|
574
|
+
spent: 0n,
|
|
575
|
+
available: 0n,
|
|
575
576
|
holdExpiresAt: null,
|
|
576
577
|
buy: null,
|
|
577
578
|
};
|
|
@@ -582,13 +583,13 @@ function economyFrom(input: unknown): Omit<EconomyState, 'buy'> | null {
|
|
|
582
583
|
const value = input as Partial<WireEconomyBalance>;
|
|
583
584
|
try {
|
|
584
585
|
const amounts = {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
586
|
+
unitsPerPoint: BigInt(value.unitsPerPoint ?? '0'),
|
|
587
|
+
earned: BigInt(value.earned ?? '-1'),
|
|
588
|
+
held: BigInt(value.held ?? '-1'),
|
|
589
|
+
spent: BigInt(value.spent ?? '-1'),
|
|
590
|
+
available: BigInt(value.available ?? '-1'),
|
|
590
591
|
};
|
|
591
|
-
if (amounts.
|
|
592
|
+
if (amounts.unitsPerPoint <= 0n || Object.values(amounts).some((amount) => amount < 0n)) return null;
|
|
592
593
|
|
|
593
594
|
// A deadline is either a whole millisecond timestamp or absent. Anything else — a string, a
|
|
594
595
|
// fraction, Infinity — is a malformed frame, and a countdown drawn from one would run to a
|
|
@@ -614,38 +615,77 @@ export function termsFrom(input: unknown): RoundTerms | null {
|
|
|
614
615
|
const value = input as Partial<WireRoundTerms>;
|
|
615
616
|
const decimal = /^(0|[1-9][0-9]*)$/;
|
|
616
617
|
if (
|
|
617
|
-
typeof value.
|
|
618
|
-
!decimal.test(value.
|
|
619
|
-
typeof value.
|
|
620
|
-
!decimal.test(value.
|
|
618
|
+
typeof value.walletCap !== 'string' ||
|
|
619
|
+
!decimal.test(value.walletCap) ||
|
|
620
|
+
typeof value.unitsPerPoint !== 'string' ||
|
|
621
|
+
!decimal.test(value.unitsPerPoint)
|
|
621
622
|
) return null;
|
|
622
623
|
try {
|
|
623
|
-
const
|
|
624
|
-
const
|
|
625
|
-
const { maxPointsPerPlayer, pointsPerDollar,
|
|
624
|
+
const walletCap = BigInt(value.walletCap);
|
|
625
|
+
const unitsPerPoint = BigInt(value.unitsPerPoint);
|
|
626
|
+
const { maxPointsPerPlayer, pointsPerDollar, usdPerSpendToken } = value;
|
|
626
627
|
if (
|
|
627
|
-
|
|
628
|
-
|
|
628
|
+
walletCap <= 0n ||
|
|
629
|
+
unitsPerPoint <= 0n ||
|
|
629
630
|
typeof maxPointsPerPlayer !== 'number' ||
|
|
630
631
|
!Number.isSafeInteger(maxPointsPerPlayer) ||
|
|
631
632
|
maxPointsPerPlayer <= 0 ||
|
|
632
633
|
// Prices, not counts, so these are finite rather than integral. Both are still positive: a
|
|
633
|
-
// round priced at zero dollars
|
|
634
|
+
// round priced at zero dollars a token is a round whose rate cannot mean anything.
|
|
634
635
|
typeof pointsPerDollar !== 'number' ||
|
|
635
636
|
!Number.isFinite(pointsPerDollar) ||
|
|
636
637
|
pointsPerDollar <= 0 ||
|
|
637
|
-
typeof
|
|
638
|
-
!Number.isFinite(
|
|
639
|
-
|
|
638
|
+
typeof usdPerSpendToken !== 'number' ||
|
|
639
|
+
!Number.isFinite(usdPerSpendToken) ||
|
|
640
|
+
usdPerSpendToken <= 0
|
|
640
641
|
) {
|
|
641
642
|
return null;
|
|
642
643
|
}
|
|
643
|
-
|
|
644
|
+
// The token is required: every amount in these terms is in its base units, so terms without it
|
|
645
|
+
// cannot be displayed or spent against. A gate that does not send one is not one this client
|
|
646
|
+
// can play against, and refusing here says so at the snapshot rather than in the arithmetic.
|
|
647
|
+
const spendToken = spendTokenFrom((value as { spendToken?: unknown }).spendToken);
|
|
648
|
+
if (!spendToken) return null;
|
|
649
|
+
return Object.freeze({
|
|
650
|
+
maxPointsPerPlayer,
|
|
651
|
+
walletCap,
|
|
652
|
+
unitsPerPoint,
|
|
653
|
+
pointsPerDollar,
|
|
654
|
+
usdPerSpendToken,
|
|
655
|
+
spendToken,
|
|
656
|
+
});
|
|
644
657
|
} catch {
|
|
645
658
|
return null;
|
|
646
659
|
}
|
|
647
660
|
}
|
|
648
661
|
|
|
662
|
+
const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
|
|
663
|
+
|
|
664
|
+
function spendTokenFrom(input: unknown): SpendToken | null {
|
|
665
|
+
if (typeof input !== 'object' || input === null) return null;
|
|
666
|
+
const value = input as Partial<SpendToken>;
|
|
667
|
+
if (
|
|
668
|
+
typeof value.address !== 'string' ||
|
|
669
|
+
!ADDRESS.test(value.address) ||
|
|
670
|
+
typeof value.symbol !== 'string' ||
|
|
671
|
+
value.symbol.length === 0 ||
|
|
672
|
+
value.symbol.length > 64 ||
|
|
673
|
+
typeof value.decimals !== 'number' ||
|
|
674
|
+
!Number.isSafeInteger(value.decimals) ||
|
|
675
|
+
value.decimals < 0 ||
|
|
676
|
+
value.decimals > 36 ||
|
|
677
|
+
typeof value.isNative !== 'boolean'
|
|
678
|
+
) {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
return Object.freeze({
|
|
682
|
+
address: value.address,
|
|
683
|
+
symbol: value.symbol,
|
|
684
|
+
decimals: value.decimals,
|
|
685
|
+
isNative: value.isNative,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
649
689
|
function presenceFrom(input: unknown): PresenceState | null {
|
|
650
690
|
if (typeof input !== 'object' || input === null) return null;
|
|
651
691
|
const value = input as Partial<PresenceState>;
|
|
@@ -707,14 +747,14 @@ export function marketFrom(input: unknown): MarketState | null {
|
|
|
707
747
|
(trade.side !== 'buy' && trade.side !== 'sell') ||
|
|
708
748
|
(trade.priceEth !== null && (typeof trade.priceEth !== 'number' || !Number.isFinite(trade.priceEth)))
|
|
709
749
|
) return [];
|
|
710
|
-
const
|
|
711
|
-
if (
|
|
750
|
+
const spend = BigInt(trade.spend);
|
|
751
|
+
if (spend < 0n) return [];
|
|
712
752
|
return [{
|
|
713
753
|
id: trade.id,
|
|
714
754
|
at: trade.at,
|
|
715
755
|
player: trade.player,
|
|
716
756
|
side: trade.side,
|
|
717
|
-
|
|
757
|
+
spend,
|
|
718
758
|
priceEth: trade.priceEth,
|
|
719
759
|
...(typeof trade.transactionHash === 'string' ? { transactionHash: trade.transactionHash } : {}),
|
|
720
760
|
}];
|
package/src/mock.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Round } from '@flayerlabs/gamemode-spec/round';
|
|
|
2
2
|
import { isRefusal, type GameModule, type PlayerId } from '@flayerlabs/gamemode-spec';
|
|
3
3
|
import type { BuyProgress, BuyResult, Economy, EconomyState, MockOptions, Room, SendResult, Snapshot, Social } from './index.js';
|
|
4
4
|
import {
|
|
5
|
+
ETH_SPEND_TOKEN,
|
|
5
6
|
isReactionId,
|
|
6
7
|
type ConnectionState,
|
|
7
8
|
type LaunchContext,
|
|
@@ -16,8 +17,6 @@ import { CachedIdentityResolver } from './identity.js';
|
|
|
16
17
|
/** How often the mock looks for a deadline that has fallen due. */
|
|
17
18
|
const TICK_MS = 100;
|
|
18
19
|
|
|
19
|
-
const WEI_PER_ETH = 1_000_000_000_000_000_000n;
|
|
20
|
-
|
|
21
20
|
/** Long enough to see a spinner, short enough not to be annoying while building. */
|
|
22
21
|
const FAKE_SIGN_MS = 150;
|
|
23
22
|
const FAKE_PENDING_MS = 250;
|
|
@@ -29,13 +28,13 @@ export class MockRoom<Config, State, Event, Action, PublicView, PlayerView>
|
|
|
29
28
|
{
|
|
30
29
|
private readonly round: Round<Config, State, Event, Action, PublicView, PlayerView>;
|
|
31
30
|
private readonly player: PlayerId;
|
|
32
|
-
private readonly
|
|
31
|
+
private readonly unitsPerPoint: bigint;
|
|
33
32
|
private readonly allowedReactions: ReadonlySet<string>;
|
|
34
33
|
private readonly listeners = new Set<(s: Snapshot<PublicView, PlayerView>) => void>();
|
|
35
34
|
private readonly reactionListeners = new Set<(r: Reaction) => void>();
|
|
36
35
|
private readonly timer: ReturnType<typeof setInterval>;
|
|
37
|
-
private
|
|
38
|
-
private
|
|
36
|
+
private spent = 0n;
|
|
37
|
+
private held = 0n;
|
|
39
38
|
/** Mirrors the gate's five-minute hold, so a game can build the countdown against the mock. */
|
|
40
39
|
private holdExpiresAt: number | null = null;
|
|
41
40
|
private buyProgress: BuyProgress | null = null;
|
|
@@ -53,7 +52,7 @@ export class MockRoom<Config, State, Event, Action, PublicView, PlayerView>
|
|
|
53
52
|
) {
|
|
54
53
|
const opensAt = Date.now() + (options.lobbyMs ?? 3_000);
|
|
55
54
|
this.player = options.player ?? '0xyou';
|
|
56
|
-
this.
|
|
55
|
+
this.unitsPerPoint = options.unitsPerPoint ?? 10_000_000_000_000n;
|
|
57
56
|
this.allowedReactions = new Set(options.reactionIds ?? []);
|
|
58
57
|
if ([...this.allowedReactions].some((id) => !isReactionId(id))) {
|
|
59
58
|
throw new RangeError('reactionIds must contain only valid reaction identifiers');
|
|
@@ -79,10 +78,10 @@ export class MockRoom<Config, State, Event, Action, PublicView, PlayerView>
|
|
|
79
78
|
// may spend. Defaulting the cap to exactly that payout, and refusing anything under it, keeps
|
|
80
79
|
// the mock from letting a game pass locally on numbers the gate would reject.
|
|
81
80
|
const maxPointsPerPlayer = game.rewardBounds(options.config).maxPointsPerPlayer;
|
|
82
|
-
const flawlessRoundWei = BigInt(maxPointsPerPlayer) * this.
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
throw new RangeError('
|
|
81
|
+
const flawlessRoundWei = BigInt(maxPointsPerPlayer) * this.unitsPerPoint;
|
|
82
|
+
const walletCap = options.walletCap ?? flawlessRoundWei;
|
|
83
|
+
if (walletCap < flawlessRoundWei) {
|
|
84
|
+
throw new RangeError('walletCap must cover a flawless round, or the gate would refuse this launch');
|
|
86
85
|
}
|
|
87
86
|
// The gate derives its rate from these two, so the mock runs the identity backwards from
|
|
88
87
|
// whatever rate it was given. Reporting three numbers that do not agree would let a menu built
|
|
@@ -91,17 +90,33 @@ export class MockRoom<Config, State, Event, Action, PublicView, PlayerView>
|
|
|
91
90
|
if (!Number.isFinite(pointsPerDollar) || pointsPerDollar <= 0) {
|
|
92
91
|
throw new RangeError('pointsPerDollar must be a positive finite number');
|
|
93
92
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
// In the spend token's own base units — wei unless a token was given. The gate's identity is
|
|
94
|
+
// `unitsPerPoint = units / round(usdPerSpendToken × pointsPerDollar)`, so the mock runs it
|
|
95
|
+
// backwards and reports exactly the three numbers a live round would agree on.
|
|
96
|
+
const spendToken = options.spendToken ?? ETH_SPEND_TOKEN;
|
|
97
|
+
const decimals = spendToken.decimals;
|
|
98
|
+
if (!Number.isSafeInteger(decimals) || decimals < 0 || decimals > 36) {
|
|
99
|
+
throw new RangeError('spendToken.decimals must be an integer between 0 and 36');
|
|
100
|
+
}
|
|
101
|
+
const units = 10n ** BigInt(decimals);
|
|
102
|
+
const pointsPerToken = units / this.unitsPerPoint;
|
|
103
|
+
if (pointsPerToken <= 0n || pointsPerToken > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
104
|
+
throw new RangeError('unitsPerPoint does not produce representable round terms');
|
|
97
105
|
}
|
|
98
|
-
const
|
|
99
|
-
const roundTrip =
|
|
100
|
-
if (roundTrip !== this.
|
|
101
|
-
throw new RangeError('
|
|
106
|
+
const usdPerSpendToken = Number(pointsPerToken) / pointsPerDollar;
|
|
107
|
+
const roundTrip = units / BigInt(Math.round(usdPerSpendToken * pointsPerDollar));
|
|
108
|
+
if (roundTrip !== this.unitsPerPoint) {
|
|
109
|
+
throw new RangeError('unitsPerPoint must round-trip through gate pricing terms');
|
|
102
110
|
}
|
|
103
111
|
this.termsSignal = new Signal<RoundTerms>(
|
|
104
|
-
Object.freeze({
|
|
112
|
+
Object.freeze({
|
|
113
|
+
maxPointsPerPlayer,
|
|
114
|
+
walletCap,
|
|
115
|
+
unitsPerPoint: this.unitsPerPoint,
|
|
116
|
+
pointsPerDollar,
|
|
117
|
+
usdPerSpendToken,
|
|
118
|
+
spendToken: Object.freeze({ ...spendToken }),
|
|
119
|
+
}),
|
|
105
120
|
);
|
|
106
121
|
this.marketSignal = new Signal(
|
|
107
122
|
options.platform?.market?.current() ?? { status: 'unavailable', prices: [], trades: [], marketCapUsd: null },
|
|
@@ -180,25 +195,25 @@ export class MockRoom<Config, State, Event, Action, PublicView, PlayerView>
|
|
|
180
195
|
};
|
|
181
196
|
|
|
182
197
|
private economyState(): EconomyState {
|
|
183
|
-
const
|
|
184
|
-
const
|
|
198
|
+
const earned = BigInt(this.round.pointsFor(this.player)) * this.unitsPerPoint;
|
|
199
|
+
const available = earned - this.spent - this.held;
|
|
185
200
|
return {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
holdExpiresAt: this.
|
|
201
|
+
unitsPerPoint: this.unitsPerPoint,
|
|
202
|
+
earned,
|
|
203
|
+
held: this.held,
|
|
204
|
+
spent: this.spent,
|
|
205
|
+
available: available > 0n ? available : 0n,
|
|
206
|
+
holdExpiresAt: this.held > 0n ? this.holdExpiresAt : null,
|
|
192
207
|
buy: this.buyProgress,
|
|
193
208
|
};
|
|
194
209
|
}
|
|
195
210
|
|
|
196
211
|
private readonly economySignal = new Signal<EconomyState>({
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
unitsPerPoint: 0n,
|
|
213
|
+
earned: 0n,
|
|
214
|
+
held: 0n,
|
|
215
|
+
spent: 0n,
|
|
216
|
+
available: 0n,
|
|
202
217
|
holdExpiresAt: null,
|
|
203
218
|
buy: null,
|
|
204
219
|
});
|
|
@@ -210,7 +225,7 @@ export class MockRoom<Config, State, Event, Action, PublicView, PlayerView>
|
|
|
210
225
|
readonly economy: Economy = {
|
|
211
226
|
current: () => this.economyState(),
|
|
212
227
|
subscribe: (listener) => this.economySignal.subscribe(listener),
|
|
213
|
-
available: () => this.economyState().
|
|
228
|
+
available: () => this.economyState().available,
|
|
214
229
|
buy: async (maxSpendWei: bigint): Promise<BuyResult> => {
|
|
215
230
|
if (maxSpendWei <= 0n) {
|
|
216
231
|
this.buyProgress = { state: 'failed', reason: 'nothing-to-spend' };
|
|
@@ -228,34 +243,34 @@ export class MockRoom<Config, State, Event, Action, PublicView, PlayerView>
|
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
const spend = maxSpendWei < available ? maxSpendWei : available;
|
|
231
|
-
this.
|
|
246
|
+
this.held += spend;
|
|
232
247
|
this.holdExpiresAt = Date.now() + MOCK_HOLD_MS;
|
|
233
|
-
this.buyProgress = { state: 'signing',
|
|
248
|
+
this.buyProgress = { state: 'signing', spend: spend };
|
|
234
249
|
this.publishEconomy();
|
|
235
250
|
|
|
236
251
|
await new Promise((resolve) => setTimeout(resolve, FAKE_SIGN_MS));
|
|
237
252
|
if (this.disposed) {
|
|
238
|
-
this.
|
|
253
|
+
this.held -= spend;
|
|
239
254
|
this.buyProgress = { state: 'failed', reason: 'try-again' };
|
|
240
255
|
this.publishEconomy();
|
|
241
256
|
return { bought: false, reason: 'try-again' };
|
|
242
257
|
}
|
|
243
|
-
this.buyProgress = { state: 'pending',
|
|
258
|
+
this.buyProgress = { state: 'pending', spend: spend };
|
|
244
259
|
this.publishEconomy();
|
|
245
260
|
|
|
246
261
|
await new Promise((resolve) => setTimeout(resolve, FAKE_PENDING_MS));
|
|
247
262
|
if (this.disposed) {
|
|
248
|
-
this.
|
|
263
|
+
this.held -= spend;
|
|
249
264
|
this.buyProgress = { state: 'failed', reason: 'try-again' };
|
|
250
265
|
this.publishEconomy();
|
|
251
266
|
return { bought: false, reason: 'try-again' };
|
|
252
267
|
}
|
|
253
268
|
|
|
254
|
-
this.
|
|
255
|
-
this.
|
|
256
|
-
this.buyProgress = { state: 'confirmed',
|
|
269
|
+
this.held -= spend;
|
|
270
|
+
this.spent += spend;
|
|
271
|
+
this.buyProgress = { state: 'confirmed', spent: spend };
|
|
257
272
|
this.emit();
|
|
258
|
-
return { bought: true,
|
|
273
|
+
return { bought: true, spent: spend };
|
|
259
274
|
},
|
|
260
275
|
};
|
|
261
276
|
|
package/src/replay-market.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface MarketFixture {
|
|
|
27
27
|
afterMs: number;
|
|
28
28
|
player: string | null;
|
|
29
29
|
side: 'buy' | 'sell';
|
|
30
|
-
|
|
30
|
+
spend: bigint;
|
|
31
31
|
priceEth: number | null;
|
|
32
32
|
}[];
|
|
33
33
|
marketCapUsd?: number | null;
|
|
@@ -91,7 +91,7 @@ export function replayMarket(fixture: MarketFixture, now: () => number = Date.no
|
|
|
91
91
|
at: startedAt + trade.afterMs,
|
|
92
92
|
player: trade.player,
|
|
93
93
|
side: trade.side,
|
|
94
|
-
|
|
94
|
+
spend: trade.spend,
|
|
95
95
|
priceEth: trade.priceEth,
|
|
96
96
|
}));
|
|
97
97
|
|
|
@@ -141,10 +141,10 @@ export const BUSY_LAUNCH: MarketFixture = {
|
|
|
141
141
|
{ afterMs: 18_000, priceEth: 0.000_049 },
|
|
142
142
|
],
|
|
143
143
|
trades: [
|
|
144
|
-
{ afterMs: 1_500, player: '0x1111111111111111111111111111111111111111', side: 'buy',
|
|
145
|
-
{ afterMs: 5_200, player: '0x2222222222222222222222222222222222222222', side: 'buy',
|
|
146
|
-
{ afterMs: 6_100, player: null, side: 'buy',
|
|
147
|
-
{ afterMs: 9_400, player: '0x3333333333333333333333333333333333333333', side: 'sell',
|
|
148
|
-
{ afterMs: 12_800, player: '0x1111111111111111111111111111111111111111', side: 'buy',
|
|
144
|
+
{ afterMs: 1_500, player: '0x1111111111111111111111111111111111111111', side: 'buy', spend: 12_000_000_000_000_000n, priceEth: 0.000_013 },
|
|
145
|
+
{ afterMs: 5_200, player: '0x2222222222222222222222222222222222222222', side: 'buy', spend: 40_000_000_000_000_000n, priceEth: 0.000_021 },
|
|
146
|
+
{ afterMs: 6_100, player: null, side: 'buy', spend: 8_000_000_000_000_000n, priceEth: 0.000_041 },
|
|
147
|
+
{ afterMs: 9_400, player: '0x3333333333333333333333333333333333333333', side: 'sell', spend: 5_000_000_000_000_000n, priceEth: 0.000_039 },
|
|
148
|
+
{ afterMs: 12_800, player: '0x1111111111111111111111111111111111111111', side: 'buy', spend: 25_000_000_000_000_000n, priceEth: 0.000_050 },
|
|
149
149
|
],
|
|
150
150
|
};
|