@shipload/sdk 1.0.0-next.63 → 1.0.0-next.65

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.
@@ -0,0 +1,161 @@
1
+ import {describe, expect, test} from 'bun:test'
2
+ import {Asset, type Name} from '@wharfkit/antelope'
3
+ import {BalancesManager} from './balances'
4
+ import type {GameContext} from './context'
5
+ import {PlatformContract} from '../contracts'
6
+
7
+ interface StubOptions {
8
+ depositConfig?: {token_contract: string; token_symbol: string} | null
9
+ balances?: {token_contract: string; balance: string}[] | null
10
+ wallet?: string[]
11
+ }
12
+
13
+ function stubContext(options: StubOptions = {}) {
14
+ const counts = {depositcfg: 0, balance: 0, currency: 0}
15
+ const rows = (options.balances ?? []).map((row) => PlatformContract.Types.balance_row.from(row))
16
+ const platform = {
17
+ table(name: string) {
18
+ if (name === 'depositcfg') {
19
+ return {
20
+ get: async () => {
21
+ counts.depositcfg++
22
+ return options.depositConfig
23
+ ? PlatformContract.Types.depositcfg_row.from(options.depositConfig)
24
+ : undefined
25
+ },
26
+ }
27
+ }
28
+ return {
29
+ get: async (key: Name) => {
30
+ counts.balance++
31
+ return rows.find((row) => row.token_contract.equals(key))
32
+ },
33
+ all: async () => {
34
+ counts.balance++
35
+ return rows
36
+ },
37
+ }
38
+ },
39
+ }
40
+ const client = {
41
+ v1: {
42
+ chain: {
43
+ get_currency_balance: async () => {
44
+ counts.currency++
45
+ return (options.wallet ?? []).map((value) => Asset.from(value))
46
+ },
47
+ },
48
+ },
49
+ }
50
+ const context = {platform, client} as unknown as GameContext
51
+ return {manager: new BalancesManager(context), counts}
52
+ }
53
+
54
+ const SCRAP = {token_contract: 'scrap.gm', token_symbol: '0,SCRAP'}
55
+
56
+ describe('getDepositConfig', () => {
57
+ test('reads the configured deposit token', async () => {
58
+ const {manager} = stubContext({depositConfig: SCRAP})
59
+ const config = await manager.getDepositConfig()
60
+ expect(String(config?.tokenContract)).toBe('scrap.gm')
61
+ expect(String(config?.symbol)).toBe('0,SCRAP')
62
+ })
63
+
64
+ test('caches the config across calls', async () => {
65
+ const {manager, counts} = stubContext({depositConfig: SCRAP})
66
+ await manager.getDepositConfig()
67
+ await manager.getDepositConfig()
68
+ expect(counts.depositcfg).toBe(1)
69
+ await manager.getDepositConfig(true)
70
+ expect(counts.depositcfg).toBe(2)
71
+ })
72
+
73
+ test('caches a missing config as null without refetching', async () => {
74
+ const {manager, counts} = stubContext({depositConfig: null})
75
+ expect(await manager.getDepositConfig()).toBeNull()
76
+ expect(await manager.getDepositConfig()).toBeNull()
77
+ expect(counts.depositcfg).toBe(1)
78
+ })
79
+ })
80
+
81
+ describe('getPlatformBalance', () => {
82
+ test('returns null when the owner has never opened a row', async () => {
83
+ const {manager} = stubContext({depositConfig: SCRAP, balances: []})
84
+ expect(await manager.getPlatformBalance('alice')).toBeNull()
85
+ })
86
+
87
+ test('returns a zero balance for an opened but empty row', async () => {
88
+ const {manager} = stubContext({
89
+ depositConfig: SCRAP,
90
+ balances: [{token_contract: 'scrap.gm', balance: '0 SCRAP'}],
91
+ })
92
+ const balance = await manager.getPlatformBalance('alice')
93
+ expect(balance).not.toBeNull()
94
+ expect(String(balance?.balance)).toBe('0 SCRAP')
95
+ expect(balance?.balance.units.toNumber()).toBe(0)
96
+ expect(balance?.balance.symbol.precision).toBe(0)
97
+ expect(String(balance?.owner)).toBe('alice')
98
+ })
99
+
100
+ test('defaults to the deposit token and honours an explicit contract', async () => {
101
+ const {manager} = stubContext({
102
+ depositConfig: SCRAP,
103
+ balances: [
104
+ {token_contract: 'scrap.gm', balance: '12 SCRAP'},
105
+ {token_contract: 'eosio.token', balance: '3.0000 EOS'},
106
+ ],
107
+ })
108
+ expect(String((await manager.getPlatformBalance('alice'))?.balance)).toBe('12 SCRAP')
109
+ expect(String((await manager.getPlatformBalance('alice', 'eosio.token'))?.balance)).toBe(
110
+ '3.0000 EOS'
111
+ )
112
+ })
113
+
114
+ test('returns null when no deposit token is configured', async () => {
115
+ const {manager, counts} = stubContext({depositConfig: null})
116
+ expect(await manager.getPlatformBalance('alice')).toBeNull()
117
+ expect(counts.balance).toBe(0)
118
+ })
119
+ })
120
+
121
+ describe('getPlatformBalances', () => {
122
+ test('returns every row the owner holds', async () => {
123
+ const {manager} = stubContext({
124
+ depositConfig: SCRAP,
125
+ balances: [
126
+ {token_contract: 'scrap.gm', balance: '12 SCRAP'},
127
+ {token_contract: 'eosio.token', balance: '3.0000 EOS'},
128
+ ],
129
+ })
130
+ const balances = await manager.getPlatformBalances('alice')
131
+ expect(balances.map((row) => String(row.balance))).toEqual(['12 SCRAP', '3.0000 EOS'])
132
+ expect(balances.map((row) => String(row.tokenContract))).toEqual([
133
+ 'scrap.gm',
134
+ 'eosio.token',
135
+ ])
136
+ })
137
+
138
+ test('returns an empty array for an owner with no rows', async () => {
139
+ const {manager} = stubContext({depositConfig: SCRAP, balances: []})
140
+ expect(await manager.getPlatformBalances('alice')).toEqual([])
141
+ })
142
+ })
143
+
144
+ describe('getWalletBalance', () => {
145
+ test('reads the wallet balance for the deposit token', async () => {
146
+ const {manager} = stubContext({depositConfig: SCRAP, wallet: ['500 SCRAP']})
147
+ const balance = await manager.getWalletBalance('alice')
148
+ expect(String(balance?.balance)).toBe('500 SCRAP')
149
+ expect(String(balance?.tokenContract)).toBe('scrap.gm')
150
+ })
151
+
152
+ test('returns null when the account holds none of the token', async () => {
153
+ const {manager} = stubContext({depositConfig: SCRAP, wallet: []})
154
+ expect(await manager.getWalletBalance('alice')).toBeNull()
155
+ })
156
+
157
+ test('ignores balances of other symbols on the same contract', async () => {
158
+ const {manager} = stubContext({depositConfig: SCRAP, wallet: ['1.0000 OTHER']})
159
+ expect(await manager.getWalletBalance('alice')).toBeNull()
160
+ })
161
+ })
@@ -0,0 +1,93 @@
1
+ import {Asset, Name, type NameType} from '@wharfkit/antelope'
2
+ import {BaseManager} from './base'
3
+ import type {PlatformContract} from '../contracts'
4
+
5
+ export interface DepositConfig {
6
+ tokenContract: Name
7
+ symbol: Asset.Symbol
8
+ }
9
+
10
+ export interface PlatformBalance {
11
+ owner: Name
12
+ tokenContract: Name
13
+ balance: Asset
14
+ }
15
+
16
+ export interface TokenBalance {
17
+ account: Name
18
+ tokenContract: Name
19
+ balance: Asset
20
+ }
21
+
22
+ export class BalancesManager extends BaseManager {
23
+ private depositConfig?: DepositConfig | null
24
+
25
+ async getDepositConfig(reload = false): Promise<DepositConfig | null> {
26
+ if (!reload && this.depositConfig !== undefined) {
27
+ return this.depositConfig
28
+ }
29
+ const row = (await this.platform.table('depositcfg').get()) as
30
+ | PlatformContract.Types.depositcfg_row
31
+ | undefined
32
+ this.depositConfig = row
33
+ ? {tokenContract: row.token_contract, symbol: row.token_symbol}
34
+ : null
35
+ return this.depositConfig
36
+ }
37
+
38
+ private async resolveTokenContract(tokenContract?: NameType): Promise<Name | null> {
39
+ if (tokenContract) return Name.from(tokenContract)
40
+ const config = await this.getDepositConfig()
41
+ return config ? config.tokenContract : null
42
+ }
43
+
44
+ async getPlatformBalances(owner: NameType): Promise<PlatformBalance[]> {
45
+ const account = Name.from(owner)
46
+ const rows = (await this.platform
47
+ .table('balance', account)
48
+ .all()) as PlatformContract.Types.balance_row[]
49
+ return rows.map((row) => ({
50
+ owner: account,
51
+ tokenContract: row.token_contract,
52
+ balance: row.balance,
53
+ }))
54
+ }
55
+
56
+ // Null means no balance row at all: deposits abort until `open`. A zero row is a valid state.
57
+ async getPlatformBalance(
58
+ owner: NameType,
59
+ tokenContract?: NameType
60
+ ): Promise<PlatformBalance | null> {
61
+ const contract = await this.resolveTokenContract(tokenContract)
62
+ if (!contract) return null
63
+ const account = Name.from(owner)
64
+ const row = (await this.platform.table('balance', account).get(contract)) as
65
+ | PlatformContract.Types.balance_row
66
+ | undefined
67
+ if (!row) return null
68
+ return {owner: account, tokenContract: row.token_contract, balance: row.balance}
69
+ }
70
+
71
+ async getWalletBalance(
72
+ account: NameType,
73
+ tokenContract?: NameType,
74
+ symbolCode?: Asset.SymbolCodeType
75
+ ): Promise<TokenBalance | null> {
76
+ const contract = await this.resolveTokenContract(tokenContract)
77
+ if (!contract) return null
78
+ let code = symbolCode ? Asset.SymbolCode.from(symbolCode) : undefined
79
+ if (!code) {
80
+ const config = await this.getDepositConfig()
81
+ code = config?.symbol.code
82
+ }
83
+ const holder = Name.from(account)
84
+ const balances = await this.client.v1.chain.get_currency_balance(
85
+ contract,
86
+ holder,
87
+ code ? String(code) : undefined
88
+ )
89
+ const balance = balances.find((asset) => !code || asset.symbol.code.equals(code))
90
+ if (!balance) return null
91
+ return {account: holder, tokenContract: contract, balance}
92
+ }
93
+ }
@@ -11,6 +11,7 @@ import {EpochsManager} from './epochs'
11
11
  import {ActionsManager} from './actions'
12
12
  import {ClusterManager} from './cluster'
13
13
  import {NftManager} from './nft'
14
+ import {BalancesManager} from './balances'
14
15
  import {JobsManager} from './jobs'
15
16
  import {InfluenceManager} from './influence'
16
17
  import {SubscriptionsManager} from '../subscriptions/manager'
@@ -24,6 +25,7 @@ export class GameContext {
24
25
  private _actions?: ActionsManager
25
26
  private _clusters?: ClusterManager
26
27
  private _nft?: NftManager
28
+ private _balances?: BalancesManager
27
29
  private _jobs?: JobsManager
28
30
  private _influence?: InfluenceManager
29
31
  private _subscriptions?: SubscriptionsManager
@@ -95,6 +97,13 @@ export class GameContext {
95
97
  return this._nft
96
98
  }
97
99
 
100
+ get balances(): BalancesManager {
101
+ if (!this._balances) {
102
+ this._balances = new BalancesManager(this)
103
+ }
104
+ return this._balances
105
+ }
106
+
98
107
  get jobs(): JobsManager {
99
108
  if (!this._jobs) {
100
109
  this._jobs = new JobsManager(this)
@@ -11,7 +11,9 @@ export {ActionsManager} from './actions'
11
11
  export {ClusterManager, computeFreeCells} from './cluster'
12
12
  export type {GridCell, ClusterCell, Cluster} from './cluster'
13
13
  export {NftManager} from './nft'
14
- export type {NftConfigForItem} from './nft'
14
+ export type {NftConfigForItem, WrapConfig, WrapGate} from './nft'
15
+ export {BalancesManager} from './balances'
16
+ export type {DepositConfig, PlatformBalance, TokenBalance} from './balances'
15
17
  export {JobsManager} from './jobs'
16
18
  export {InfluenceManager} from './influence'
17
19
  export type {
@@ -19,6 +21,7 @@ export type {
19
21
  ContributePreview,
20
22
  ContributePreviewRow,
21
23
  FoundedWorld,
24
+ FoundedWorldRef,
22
25
  InfluenceStanding,
23
26
  PendingBallot,
24
27
  VoteCast,
@@ -73,6 +73,12 @@ export interface FoundedWorld {
73
73
  founded: number
74
74
  }
75
75
 
76
+ export interface FoundedWorldRef {
77
+ x: number
78
+ y: number
79
+ mandate: number
80
+ }
81
+
76
82
  export interface VoteOption {
77
83
  nodeId: number
78
84
  cost: bigint
@@ -247,6 +253,25 @@ export class InfluenceManager extends BaseManager {
247
253
  return (await this.server.readonly('getmintcfg')) as ServerContract.Types.mintcfg_result
248
254
  }
249
255
 
256
+ async getMintReady(): Promise<number> {
257
+ return Number(await this.server.readonly('getmintready'))
258
+ }
259
+
260
+ async getVoteReady(): Promise<number> {
261
+ return Number(await this.server.readonly('getvoteready'))
262
+ }
263
+
264
+ async getCharterReady(): Promise<FoundedWorldRef[]> {
265
+ const result = (await this.server.readonly(
266
+ 'getchrtready'
267
+ )) as ServerContract.Types.charterready_result
268
+ return result.worlds.map((w) => ({
269
+ x: Number(w.x),
270
+ y: Number(w.y),
271
+ mandate: Number(w.mandate),
272
+ }))
273
+ }
274
+
250
275
  async getVotes(location: CoordinatesType): Promise<VoteStandings> {
251
276
  const result = (await this.server.readonly('getvotes', {
252
277
  x: Int64.from(location.x),
@@ -1,5 +1,5 @@
1
1
  import {describe, expect, test} from 'bun:test'
2
- import {resolveLockedAmount} from './nft'
2
+ import {resolveLockedAmount, wrapCostKey} from './nft'
3
3
 
4
4
  describe('resolveLockedAmount', () => {
5
5
  test('no fee refunds the full cost', () => {
@@ -12,3 +12,10 @@ describe('resolveLockedAmount', () => {
12
12
  expect(resolveLockedAmount(101n, 250)).toBe(99n) // fee = floor(101*250/10000)=2
13
13
  })
14
14
  })
15
+
16
+ describe('wrapCostKey', () => {
17
+ test('packs item type and tier as (type << 8) | tier', () => {
18
+ expect(wrapCostKey(0, 1).toString()).toBe('1')
19
+ expect(wrapCostKey(3, 2).toString()).toBe('770')
20
+ })
21
+ })
@@ -1,4 +1,4 @@
1
- import {UInt64, type UInt64Type} from '@wharfkit/antelope'
1
+ import {Name, UInt64, type NameType, type UInt64Type} from '@wharfkit/antelope'
2
2
  import {BaseManager} from './base'
3
3
  import type {PlatformContract, ServerContract} from '../contracts'
4
4
 
@@ -7,6 +7,17 @@ export interface NftConfigForItem {
7
7
  schemaName: string
8
8
  }
9
9
 
10
+ export interface WrapConfig {
11
+ feePctBasisPoints: number
12
+ feeAccount: Name
13
+ }
14
+
15
+ export interface WrapGate {
16
+ owner: Name
17
+ game: Name
18
+ lastAssetId: UInt64
19
+ }
20
+
10
21
  export interface WrapDeposit {
11
22
  cost: bigint
12
23
  refund: bigint
@@ -21,8 +32,13 @@ export function resolveLockedAmount(cost: bigint, feePctBasisPoints: number): bi
21
32
  return cost - fee
22
33
  }
23
34
 
35
+ export function wrapCostKey(itemType: number, tier: number): UInt64 {
36
+ return UInt64.from((BigInt(itemType) << 8n) | BigInt(tier))
37
+ }
38
+
24
39
  export class NftManager extends BaseManager {
25
40
  private cache = new Map<string, NftConfigForItem | null>()
41
+ private wrapConfig?: WrapConfig | null
26
42
 
27
43
  async getNftConfigForItem(itemId: UInt64Type): Promise<NftConfigForItem | undefined> {
28
44
  const id = UInt64.from(itemId)
@@ -40,31 +56,55 @@ export class NftManager extends BaseManager {
40
56
  return result ?? undefined
41
57
  }
42
58
 
43
- async getWrapDeposit(itemType: number, tier: number): Promise<WrapDeposit | null> {
44
- const key = UInt64.from((BigInt(itemType) << 8n) | BigInt(tier))
45
- const costRow = (await this.server.table('wrapcost').get(key)) as
59
+ async getWrapConfig(reload = false): Promise<WrapConfig | null> {
60
+ if (!reload && this.wrapConfig !== undefined) {
61
+ return this.wrapConfig
62
+ }
63
+ const row = (await this.server.table('wrapconfig').get()) as
64
+ | ServerContract.Types.wrapconfig_row
65
+ | undefined
66
+ this.wrapConfig = row
67
+ ? {feePctBasisPoints: Number(row.fee_pct), feeAccount: row.fee_account}
68
+ : null
69
+ return this.wrapConfig
70
+ }
71
+
72
+ async getWrapCost(itemType: number, tier: number): Promise<bigint> {
73
+ const row = (await this.server.table('wrapcost').get(wrapCostKey(itemType, tier))) as
46
74
  | ServerContract.Types.wrapcost_row
47
75
  | undefined
48
- const cost = costRow ? BigInt(costRow.amount.toString()) : 0n
76
+ return row ? BigInt(row.amount.toString()) : 0n
77
+ }
78
+
79
+ async getWrapDeposit(
80
+ itemType: number,
81
+ tier: number,
82
+ opts: {reload?: boolean} = {}
83
+ ): Promise<WrapDeposit | null> {
84
+ const cost = await this.getWrapCost(itemType, tier)
49
85
  if (cost === 0n) return null
50
86
 
51
- const cfg = (await this.server.table('wrapconfig').get()) as
52
- | ServerContract.Types.wrapconfig_row
53
- | undefined
54
- const feePctBasisPoints = cfg ? Number(cfg.fee_pct) : 0
87
+ const wrapConfig = await this.getWrapConfig(opts.reload)
88
+ const feePctBasisPoints = wrapConfig ? wrapConfig.feePctBasisPoints : 0
55
89
 
56
- const depositCfg = (await this.platform.table('depositcfg').get()) as
57
- | PlatformContract.Types.depositcfg_row
58
- | undefined
59
- if (!depositCfg) return null
90
+ const depositConfig = await this.context.balances.getDepositConfig(opts.reload)
91
+ if (!depositConfig) return null
60
92
 
61
93
  return {
62
94
  cost,
63
95
  refund: resolveLockedAmount(cost, feePctBasisPoints),
64
96
  feePct: feePctBasisPoints / 100,
65
- symbol: depositCfg.token_symbol.code.toString(),
66
- precision: depositCfg.token_symbol.precision,
67
- tokenContract: depositCfg.token_contract.toString(),
97
+ symbol: depositConfig.symbol.code.toString(),
98
+ precision: depositConfig.symbol.precision,
99
+ tokenContract: depositConfig.tokenContract.toString(),
68
100
  }
69
101
  }
102
+
103
+ async getWrapGate(owner: NameType): Promise<WrapGate | null> {
104
+ const row = (await this.platform.table('wrapgate').get(Name.from(owner))) as
105
+ | PlatformContract.Types.wrapgate_row
106
+ | undefined
107
+ if (!row) return null
108
+ return {owner: row.owner, game: row.game, lastAssetId: row.last_asset_id}
109
+ }
70
110
  }
package/src/shipload.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import {APIClient} from '@wharfkit/antelope'
2
2
  import {PlatformContract, ServerContract} from './contracts'
3
3
  import type {ChainDefinition} from '@wharfkit/common'
4
- import ContractKit, {type Contract} from '@wharfkit/contract'
4
+ import {ContractKit, type Contract} from '@wharfkit/contract'
5
5
 
6
6
  import {GameContext} from './managers/context'
7
7
  import type {EntitiesManager} from './managers/entities'
@@ -12,6 +12,7 @@ import type {EpochsManager} from './managers/epochs'
12
12
  import type {ActionsManager} from './managers/actions'
13
13
  import type {ClusterManager} from './managers/cluster'
14
14
  import type {NftManager} from './managers/nft'
15
+ import type {BalancesManager} from './managers/balances'
15
16
  import type {JobsManager} from './managers/jobs'
16
17
  import type {InfluenceManager} from './managers/influence'
17
18
  import type {SubscriptionsManager} from './subscriptions/manager'
@@ -134,6 +135,10 @@ export class Shipload {
134
135
  return this._context.nft
135
136
  }
136
137
 
138
+ get balances(): BalancesManager {
139
+ return this._context.balances
140
+ }
141
+
137
142
  get jobs(): JobsManager {
138
143
  return this._context.jobs
139
144
  }
package/src/utils/hash.ts CHANGED
@@ -9,3 +9,7 @@ export function hash512(seed: Checksum256Type, string: string): Checksum512 {
9
9
  const bytes = Bytes.from(`${seed}${string}`, 'utf8')
10
10
  return Checksum512.hash(bytes)
11
11
  }
12
+
13
+ export function uint16(hash: Checksum512, offset: number): number {
14
+ return (hash.array[offset] << 8) | hash.array[offset + 1]
15
+ }
@@ -1,5 +1,5 @@
1
1
  import {Checksum256, type Checksum256Type, type Checksum512, UInt8} from '@wharfkit/antelope'
2
- import {hash512} from './hash'
2
+ import {hash512, uint16} from './hash'
3
3
  import {Coordinates, type CoordinatesType, LocationType} from '../types'
4
4
  import {ServerContract} from '../contracts'
5
5
  import {deriveLocationSize} from '../derivation/location-size'
@@ -47,10 +47,6 @@ export function getLocationTypeName(type: LocationType): string {
47
47
  }
48
48
  }
49
49
 
50
- function uint16(hash: Checksum512, offset: number): number {
51
- return (hash.array[offset] << 8) | hash.array[offset + 1]
52
- }
53
-
54
50
  function generatePlanetName(hashResult: Checksum512): string {
55
51
  const syllableCount = 2 + (hashResult.array[0] % 2)
56
52
  const name: string[] = []