mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-27 07:58:55 -07:00
[econ] key purchasing
This commit is contained in:
@@ -1941,6 +1941,163 @@ describe('econ endpoints', () => {
|
||||
expect(res.status).toBe(200)
|
||||
expect((await envOf(res)).Value).toMatchObject({ Limit: 1000000, Color: -1 })
|
||||
})
|
||||
|
||||
test('POST roomCurrencies/v2/purchase sells an offer’s currency for tokens, paying the owner', async () => {
|
||||
const form = async (path: string, fields: Record<string, string>, sub: string | null) =>
|
||||
exports.default.fetch(`${ORIGIN}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
...(sub === null ? {} : await bearer(sub)),
|
||||
},
|
||||
body: new URLSearchParams(fields),
|
||||
})
|
||||
const purchase = async (sub: string | null, offerId: string, amount: number, price: number) =>
|
||||
form(
|
||||
'/api/roomCurrencies/v2/purchase',
|
||||
{
|
||||
PurchaseOfferId: offerId,
|
||||
RequestedAmount: String(amount),
|
||||
RequestedPrice: String(price),
|
||||
},
|
||||
sub
|
||||
)
|
||||
const addOffer = async (currencyId: string, amount: number, price: number) =>
|
||||
(
|
||||
(await (
|
||||
await form(
|
||||
'/api/roomcurrencies/v1/createPurchaseOffer',
|
||||
{
|
||||
CurrencyId: currencyId,
|
||||
Name: 'pack',
|
||||
Amount: String(amount),
|
||||
Price: String(price),
|
||||
},
|
||||
'1'
|
||||
)
|
||||
).json()) as { Value: { CurrencyPurchaseOfferId: string } }
|
||||
).Value.CurrencyPurchaseOfferId
|
||||
const setTokens = (accountId: number, amount: number) =>
|
||||
env.DB.prepare(
|
||||
'INSERT OR REPLACE INTO balance (account_id, currency_type, amount) VALUES (?1, 2, ?2)'
|
||||
)
|
||||
.bind(accountId, amount)
|
||||
.run()
|
||||
const tokens = async (accountId: number) =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT amount FROM balance WHERE account_id = ?1 AND currency_type = 2'
|
||||
)
|
||||
.bind(accountId)
|
||||
.first<{ amount: number }>()
|
||||
)?.amount
|
||||
const held = async (currencyId: string, playerId: number) =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT amount FROM room_balance WHERE currency_id = ?1 AND player_id = ?2'
|
||||
)
|
||||
.bind(currencyId, playerId)
|
||||
.first<{ amount: number }>()
|
||||
)?.amount ?? 0
|
||||
const refused = (error: string) => ({
|
||||
Value: null,
|
||||
Success: false,
|
||||
Error: error,
|
||||
error_id: null,
|
||||
})
|
||||
|
||||
// Two currencies in room 2511 (account 1's), so the offer has to pick the right one.
|
||||
const other = (await envOf(await create({ ...body, Name: 'Other' }, await bearer('1'))))
|
||||
.Value!
|
||||
const sold = (await envOf(await create({ ...body, Name: 'Sold' }, await bearer('1')))).Value!
|
||||
await addOffer(other.CurrencyId, 444, 1)
|
||||
const offerId = await addOffer(sold.CurrencyId, 444, 555)
|
||||
const freeOfferId = await addOffer(sold.CurrencyId, 5, 0)
|
||||
await setTokens(1, 1000)
|
||||
await setTokens(7401, 600)
|
||||
await setTokens(7402, 100)
|
||||
await drainFrames()
|
||||
|
||||
expect((await purchase(null, offerId, 444, 555)).status).toBe(401)
|
||||
|
||||
// Refusals: an unknown offer, numbers that are not the offer's, too few tokens.
|
||||
const cases: Array<[Response, string]> = [
|
||||
[await purchase('7401', crypto.randomUUID(), 444, 555), 'No such purchase offer'],
|
||||
[await purchase('7401', '', 444, 555), 'No such purchase offer'],
|
||||
[await purchase('7401', offerId, 445, 555), 'Requested amount does not match'],
|
||||
[await purchase('7401', offerId, 444, 554), 'Requested price does not match'],
|
||||
[await purchase('7401', offerId, 444, 0), 'Requested price does not match'],
|
||||
[await purchase('7402', offerId, 444, 555), 'Not enough tokens'],
|
||||
]
|
||||
for (const [res, error] of cases) {
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual(refused(error))
|
||||
}
|
||||
expect([await tokens(1), await tokens(7401), await tokens(7402)]).toEqual([1000, 600, 100])
|
||||
expect(await held(sold.CurrencyId, 7401)).toBe(0)
|
||||
expect(await drainFrames()).toEqual([])
|
||||
|
||||
// The sale, exactly as the client posts it: 555 tokens for 444 of the offer's currency.
|
||||
const res = await purchase('7401', offerId, 444, 555)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
Value: {
|
||||
CurrencyBalanceResponse: {
|
||||
AccountId: 7401,
|
||||
CurrencyId: sold.CurrencyId,
|
||||
Balance: 444,
|
||||
ModifiedAt: expect.any(String),
|
||||
},
|
||||
TokenBalanceResponse: { Balance: 45, CurrencyType: 2, Platform: -2 },
|
||||
},
|
||||
Success: true,
|
||||
Error: null,
|
||||
error_id: null,
|
||||
})
|
||||
expect([await tokens(1), await tokens(7401)]).toEqual([1555, 45])
|
||||
expect(await held(sold.CurrencyId, 7401)).toBe(444)
|
||||
expect(await held(other.CurrencyId, 7401)).toBe(0)
|
||||
// Both hear their RESULTING token total, in the account-wide bucket.
|
||||
const frames = await drainFrames()
|
||||
expect(frames.find((f) => f.accountId === 1)?.payload).toEqual({
|
||||
Balance: 1555,
|
||||
CurrencyType: 2,
|
||||
Platform: -2,
|
||||
})
|
||||
expect(frames.find((f) => f.accountId === 7401)?.payload).toMatchObject({
|
||||
Delta: -555,
|
||||
Balance: 45,
|
||||
Platform: -2,
|
||||
})
|
||||
|
||||
// A second purchase needs the tokens again; the holding is left where it was.
|
||||
expect(await (await purchase('7401', offerId, 444, 555)).json()).toEqual(
|
||||
refused('Not enough tokens')
|
||||
)
|
||||
expect(await held(sold.CurrencyId, 7401)).toBe(444)
|
||||
|
||||
// A free offer, and the owner buying from their own shop, move no tokens.
|
||||
const gratis = (await (await purchase('7402', freeOfferId, 5, 0)).json()) as {
|
||||
Value: {
|
||||
CurrencyBalanceResponse: { Balance: number }
|
||||
TokenBalanceResponse: { Balance: number }
|
||||
}
|
||||
}
|
||||
expect(gratis.Value.CurrencyBalanceResponse.Balance).toBe(5)
|
||||
expect(gratis.Value.TokenBalanceResponse.Balance).toBe(100)
|
||||
const own = (await (await purchase('1', offerId, 444, 555)).json()) as typeof gratis
|
||||
expect(own.Value.CurrencyBalanceResponse.Balance).toBe(444)
|
||||
expect(own.Value.TokenBalanceResponse.Balance).toBe(1555)
|
||||
expect([await tokens(1), await tokens(7402)]).toEqual([1555, 100])
|
||||
expect(await drainFrames()).toEqual([])
|
||||
|
||||
await env.DB.prepare('DELETE FROM room_balance WHERE currency_id IN (?1, ?2)')
|
||||
.bind(sold.CurrencyId, other.CurrencyId)
|
||||
.run()
|
||||
await env.DB.prepare('DELETE FROM room_currency WHERE currency_id IN (?1, ?2)')
|
||||
.bind(sold.CurrencyId, other.CurrencyId)
|
||||
.run()
|
||||
})
|
||||
})
|
||||
|
||||
// Room 2511 is seeded as account 1's, with account 2 as co-owner.
|
||||
@@ -2415,6 +2572,301 @@ describe('econ endpoints', () => {
|
||||
.bind(currencyId)
|
||||
.run()
|
||||
})
|
||||
|
||||
test('GET buyRoomKey moves the key’s price from the buyer to the room’s owner', async () => {
|
||||
const buy = async (sub: string | null, roomKeyId: number | string, price?: number) =>
|
||||
exports.default.fetch(
|
||||
`${ORIGIN}/api/storefronts/v1/buyRoomKey?RoomKeyId=${roomKeyId}` +
|
||||
(price === undefined ? '' : `&RequestedPrice=${price}`),
|
||||
sub === null ? undefined : { headers: await bearer(sub) }
|
||||
)
|
||||
const setTokens = (accountId: number, amount: number) =>
|
||||
env.DB.prepare(
|
||||
'INSERT OR REPLACE INTO balance (account_id, currency_type, amount) VALUES (?1, 2, ?2)'
|
||||
)
|
||||
.bind(accountId, amount)
|
||||
.run()
|
||||
const tokens = async (accountId: number) =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT amount FROM balance WHERE account_id = ?1 AND currency_type = 2'
|
||||
)
|
||||
.bind(accountId)
|
||||
.first<{ amount: number }>()
|
||||
)?.amount
|
||||
const holders = async (roomKeyId: number) =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT account_id FROM room_key_player WHERE room_key_id = ?1 ORDER BY account_id'
|
||||
)
|
||||
.bind(roomKeyId)
|
||||
.all<{ account_id: number }>()
|
||||
).results.map((r) => r.account_id)
|
||||
// A refusal: the same shape, nothing sold, the balance as it stood.
|
||||
const refused = (code: number, data: RoomKey | null, balance: number) => ({
|
||||
RoomKeyResponse: { Status: 1, RoomKey: null },
|
||||
BalanceUpdateResponse: {
|
||||
BalanceUpdates: [{ UpdateResponse: code, Data: data }],
|
||||
Balance: balance,
|
||||
CurrencyType: 2,
|
||||
Platform: -2,
|
||||
},
|
||||
})
|
||||
|
||||
// Room 2511 is account 1's. A 300-token key, and a free one.
|
||||
const key = await keyOf(await create({ ...body, Price: '300' }, await bearer('1')))
|
||||
const free = await keyOf(
|
||||
await create({ ...body, Name: 'free', Price: '0' }, await bearer('1'))
|
||||
)
|
||||
await setTokens(1, 1000)
|
||||
await setTokens(7301, 500)
|
||||
await setTokens(7302, 100)
|
||||
await drainFrames()
|
||||
|
||||
expect((await buy(null, key.RoomKeyId, 300)).status).toBe(401)
|
||||
|
||||
// The price must match — `RequestedPrice=0`, or none, does not buy a priced key.
|
||||
for (const wrong of [0, 299, undefined]) {
|
||||
const res = await buy('7301', key.RoomKeyId, wrong)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual(refused(6, key, 500))
|
||||
}
|
||||
// No such key; not enough tokens.
|
||||
expect(await (await buy('7301', 999999, 300)).json()).toEqual(refused(4, null, 500))
|
||||
expect(await (await buy('7301', 'abc', 300)).json()).toEqual(refused(4, null, 500))
|
||||
expect(await (await buy('7302', key.RoomKeyId, 300)).json()).toEqual(refused(2, key, 100))
|
||||
expect(await holders(key.RoomKeyId)).toEqual([])
|
||||
expect([await tokens(1), await tokens(7301), await tokens(7302)]).toEqual([1000, 500, 100])
|
||||
expect(await drainFrames()).toEqual([])
|
||||
|
||||
// The sale: 300 leaves the buyer, reaches the owner, and the buyer holds the key.
|
||||
const sold = await buy('7301', key.RoomKeyId, 300)
|
||||
expect(sold.status).toBe(200)
|
||||
const soldBody = (await sold.json()) as {
|
||||
BalanceUpdateResponse: Record<string, unknown>
|
||||
}
|
||||
expect(soldBody).toEqual({
|
||||
RoomKeyResponse: { Status: 0, RoomKey: key },
|
||||
BalanceUpdateResponse: {
|
||||
BalanceUpdates: [{ UpdateResponse: 0, Data: key }],
|
||||
Balance: 200,
|
||||
CurrencyType: 2,
|
||||
Platform: -2,
|
||||
},
|
||||
})
|
||||
// Derived-first key order, as the client's formatter runs it.
|
||||
expect(Object.keys(soldBody.BalanceUpdateResponse)).toEqual([
|
||||
'BalanceUpdates',
|
||||
'Balance',
|
||||
'CurrencyType',
|
||||
'Platform',
|
||||
])
|
||||
expect(await holders(key.RoomKeyId)).toEqual([7301])
|
||||
expect([await tokens(1), await tokens(7301)]).toEqual([1300, 200])
|
||||
// Both hear their RESULTING total, in the account-wide bucket.
|
||||
const frames = await drainFrames()
|
||||
expect(frames.find((f) => f.accountId === 1)?.payload).toEqual({
|
||||
Balance: 1300,
|
||||
CurrencyType: 2,
|
||||
Platform: -2,
|
||||
})
|
||||
expect(frames.find((f) => f.accountId === 7301)?.payload).toMatchObject({
|
||||
Delta: -300,
|
||||
Balance: 200,
|
||||
Platform: -2,
|
||||
})
|
||||
|
||||
// Held once: buying it again is refused and charges nothing.
|
||||
expect(await (await buy('7301', key.RoomKeyId, 300)).json()).toEqual(refused(3, key, 200))
|
||||
expect([await tokens(1), await tokens(7301)]).toEqual([1300, 200])
|
||||
|
||||
// A free key moves nothing; nor does the owner buying their own room's key.
|
||||
const gratis = (await (await buy('7302', free.RoomKeyId)).json()) as {
|
||||
RoomKeyResponse: { Status: number }
|
||||
BalanceUpdateResponse: { Balance: number }
|
||||
}
|
||||
expect(gratis.RoomKeyResponse.Status).toBe(0)
|
||||
expect(gratis.BalanceUpdateResponse.Balance).toBe(100)
|
||||
const own = (await (await buy('1', key.RoomKeyId, 300)).json()) as typeof gratis
|
||||
expect(own.RoomKeyResponse.Status).toBe(0)
|
||||
expect(own.BalanceUpdateResponse.Balance).toBe(1300)
|
||||
expect(await holders(key.RoomKeyId)).toEqual([1, 7301])
|
||||
expect(await drainFrames()).toEqual([])
|
||||
|
||||
// A key priced in a room currency is not sold for tokens.
|
||||
await env.DB.prepare(
|
||||
"UPDATE room_key SET purchase_currency_id = 'c0ffee' WHERE room_key_id = ?1"
|
||||
)
|
||||
.bind(free.RoomKeyId)
|
||||
.run()
|
||||
const inCurrency = { ...free, PurchaseCurrencyId: 'c0ffee' }
|
||||
expect(await (await buy('7301', free.RoomKeyId, 0)).json()).toEqual(
|
||||
refused(4, inCurrency, 200)
|
||||
)
|
||||
|
||||
await env.DB.prepare('DELETE FROM room_key_player').run()
|
||||
await env.DB.prepare('DELETE FROM room_key').run()
|
||||
})
|
||||
|
||||
test('POST PurchaseRoomKeyWithCurrency subtracts the key’s price in its room currency and pays nobody', async () => {
|
||||
const form = async (
|
||||
path: string,
|
||||
fields: Record<string, string>,
|
||||
sub: string | null,
|
||||
method = 'POST'
|
||||
) =>
|
||||
exports.default.fetch(`${ORIGIN}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
...(sub === null ? {} : await bearer(sub)),
|
||||
},
|
||||
body: new URLSearchParams(fields),
|
||||
})
|
||||
const purchase = async (
|
||||
sub: string | null,
|
||||
roomKeyId: number,
|
||||
price: number,
|
||||
currency: string
|
||||
) =>
|
||||
form(
|
||||
'/api/storefronts/v1/PurchaseRoomKeyWithCurrency',
|
||||
{
|
||||
RoomKeyId: String(roomKeyId),
|
||||
RequestedPrice: String(price),
|
||||
RequestedPurchaseCurrencyId: currency,
|
||||
},
|
||||
sub
|
||||
)
|
||||
const mint = async (name: string) =>
|
||||
(
|
||||
(await (
|
||||
await form('/api/roomcurrencies/v1/createCurrency', { RoomId: '2511', Name: name }, '1')
|
||||
).json()) as { Value: { CurrencyId: string } }
|
||||
).Value.CurrencyId
|
||||
const give = (currencyId: string, playerId: number, amount: number) =>
|
||||
env.DB.prepare(
|
||||
'INSERT OR REPLACE INTO room_balance (currency_id, player_id, amount) VALUES (?1, ?2, ?3)'
|
||||
)
|
||||
.bind(currencyId, playerId, amount)
|
||||
.run()
|
||||
const held = async (currencyId: string, playerId: number) =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT amount FROM room_balance WHERE currency_id = ?1 AND player_id = ?2'
|
||||
)
|
||||
.bind(currencyId, playerId)
|
||||
.first<{ amount: number }>()
|
||||
)?.amount ?? 0
|
||||
const holders = async (roomKeyId: number) =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT account_id FROM room_key_player WHERE room_key_id = ?1 ORDER BY account_id'
|
||||
)
|
||||
.bind(roomKeyId)
|
||||
.all<{ account_id: number }>()
|
||||
).results.map((r) => r.account_id)
|
||||
const tokens = async (accountId: number) =>
|
||||
(
|
||||
await env.DB.prepare(
|
||||
'SELECT amount FROM balance WHERE account_id = ?1 AND currency_type = 2'
|
||||
)
|
||||
.bind(accountId)
|
||||
.first<{ amount: number }>()
|
||||
)?.amount
|
||||
// A refusal: the same shape, nothing sold, the holding as it stands.
|
||||
const refused = (accountId: number, currencyId: string, balance: number) => ({
|
||||
Balance: {
|
||||
AccountId: accountId,
|
||||
CurrencyId: currencyId,
|
||||
Balance: balance,
|
||||
ModifiedAt: expect.any(String),
|
||||
},
|
||||
RoomKeyResponse: { Status: 1, RoomKey: null },
|
||||
})
|
||||
|
||||
// A key priced at 1000 of the room's own currency; a second currency; a token key.
|
||||
const coin = await mint('coin')
|
||||
const other = await mint('other')
|
||||
const made = await keyOf(await create(body, await bearer('1')))
|
||||
const key = (
|
||||
await envOf(
|
||||
await form(
|
||||
'/api/roomkeys/v1/updateAll',
|
||||
{ RoomKeyId: String(made.RoomKeyId), Price: '1000', PurchaseCurrencyId: coin },
|
||||
'1',
|
||||
'PUT'
|
||||
)
|
||||
)
|
||||
).RoomKey!
|
||||
expect(key).toMatchObject({ Price: 1000, PurchaseCurrencyId: coin })
|
||||
const tokenKey = await keyOf(await create({ ...body, Name: 'token key' }, await bearer('1')))
|
||||
await give(coin, 7501, 1500)
|
||||
await give(other, 7501, 5000)
|
||||
await give(coin, 7502, 999)
|
||||
const ownerTokens = await tokens(1)
|
||||
await drainFrames()
|
||||
|
||||
expect((await purchase(null, key.RoomKeyId, 1000, coin)).status).toBe(401)
|
||||
|
||||
// Refusals: no such key, the wrong price, a currency that is not the key's, a key
|
||||
// priced in tokens, and a buyer one coin short — who is NOT sold it for what they have.
|
||||
const cases: Array<[Response, ReturnType<typeof refused>]> = [
|
||||
[await purchase('7501', 999999, 1000, coin), refused(7501, coin, 1500)],
|
||||
[await purchase('7501', key.RoomKeyId, 999, coin), refused(7501, coin, 1500)],
|
||||
[await purchase('7501', key.RoomKeyId, 1000, other), refused(7501, coin, 1500)],
|
||||
[await purchase('7501', tokenKey.RoomKeyId, 50, coin), refused(7501, coin, 1500)],
|
||||
[await purchase('7502', key.RoomKeyId, 1000, coin), refused(7502, coin, 999)],
|
||||
]
|
||||
for (const [res, expected] of cases) {
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual(expected)
|
||||
}
|
||||
expect(await holders(key.RoomKeyId)).toEqual([])
|
||||
expect(await holders(tokenKey.RoomKeyId)).toEqual([])
|
||||
expect([await held(coin, 7501), await held(other, 7501), await held(coin, 7502)]).toEqual([
|
||||
1500, 5000, 999,
|
||||
])
|
||||
|
||||
// The sale, exactly as the client posts it. `Balance` is an OBJECT holding the
|
||||
// resulting total, and it comes first.
|
||||
const res = await purchase('7501', key.RoomKeyId, 1000, coin)
|
||||
expect(res.status).toBe(200)
|
||||
const sold = (await res.json()) as Record<string, unknown>
|
||||
expect(sold).toEqual({
|
||||
Balance: {
|
||||
AccountId: 7501,
|
||||
CurrencyId: coin,
|
||||
Balance: 500,
|
||||
ModifiedAt: expect.any(String),
|
||||
},
|
||||
RoomKeyResponse: { Status: 0, RoomKey: key },
|
||||
})
|
||||
expect(Object.keys(sold)).toEqual(['Balance', 'RoomKeyResponse'])
|
||||
expect(await holders(key.RoomKeyId)).toEqual([7501])
|
||||
expect([await held(coin, 7501), await held(other, 7501)]).toEqual([500, 5000])
|
||||
|
||||
// Nobody is paid: the owner holds none of the coin, their tokens did not move, and
|
||||
// no balance frame went out.
|
||||
expect(await held(coin, 1)).toBe(0)
|
||||
expect(await tokens(1)).toBe(ownerTokens)
|
||||
expect(await drainFrames()).toEqual([])
|
||||
|
||||
// Held once: buying it again is refused and subtracts nothing.
|
||||
expect(await (await purchase('7501', key.RoomKeyId, 1000, coin)).json()).toEqual(
|
||||
refused(7501, coin, 500)
|
||||
)
|
||||
expect(await held(coin, 7501)).toBe(500)
|
||||
|
||||
await env.DB.prepare('DELETE FROM room_key_player').run()
|
||||
await env.DB.prepare('DELETE FROM room_key').run()
|
||||
await env.DB.prepare('DELETE FROM room_balance WHERE currency_id IN (?1, ?2)')
|
||||
.bind(coin, other)
|
||||
.run()
|
||||
await env.DB.prepare('DELETE FROM room_currency WHERE currency_id IN (?1, ?2)')
|
||||
.bind(coin, other)
|
||||
.run()
|
||||
})
|
||||
})
|
||||
|
||||
test('GET /api/roomconsumables/v1/roomConsumable/room/:id/me returns []', async () => {
|
||||
@@ -6205,6 +6657,7 @@ describe('econ endpoints', () => {
|
||||
'GET /api/roomkeys/v1/mine',
|
||||
'GET /api/roomkeys/v1/room',
|
||||
'GET /api/storefronts/v1/adcarouselitems',
|
||||
'GET /api/storefronts/v1/buyRoomKey',
|
||||
'GET /api/storefronts/v2/buyInvention',
|
||||
'GET /api/storefronts/v3/giftdropstore/{id}',
|
||||
'GET /api/storefronts/v4/balance/{currencyType}',
|
||||
@@ -6234,6 +6687,7 @@ describe('econ endpoints', () => {
|
||||
'POST /api/items/purchaseInfos',
|
||||
'POST /api/objectives/v1/cleargroup',
|
||||
'POST /api/objectives/v1/updateobjective',
|
||||
'POST /api/roomCurrencies/v2/purchase',
|
||||
'POST /api/roomconsumables/v1/roomConsumable/awardBulk',
|
||||
'POST /api/roomcurrencies/v1/awardCurrency/bulk',
|
||||
'POST /api/roomcurrencies/v1/createCurrency',
|
||||
@@ -6242,6 +6696,7 @@ describe('econ endpoints', () => {
|
||||
'POST /api/roomkeys/v1/awardbulk',
|
||||
'POST /api/roomkeys/v1/create',
|
||||
'POST /api/roomkeys/v1/owns/bulk',
|
||||
'POST /api/storefronts/v1/PurchaseRoomKeyWithCurrency',
|
||||
'POST /api/storefronts/v2/buyItem',
|
||||
'POST /api/storefronts/v3/buyInvention',
|
||||
'POST /api/ugcPurchasables/v1/items/bulk',
|
||||
|
||||
Reference in New Issue
Block a user