hive-stream 3.0.2 → 3.0.4

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.
Files changed (202) hide show
  1. package/DOCUMENTATION.md +50 -2
  2. package/README.md +282 -4
  3. package/dist/adapters/base.adapter.d.ts +5 -0
  4. package/dist/adapters/base.adapter.js +9 -0
  5. package/dist/adapters/base.adapter.js.map +1 -1
  6. package/dist/adapters/mongodb.adapter.d.ts +6 -6
  7. package/dist/adapters/mongodb.adapter.js +36 -21
  8. package/dist/adapters/mongodb.adapter.js.map +1 -1
  9. package/dist/adapters/postgresql.adapter.d.ts +7 -0
  10. package/dist/adapters/postgresql.adapter.js +46 -19
  11. package/dist/adapters/postgresql.adapter.js.map +1 -1
  12. package/dist/adapters/sqlite.adapter.d.ts +4 -0
  13. package/dist/adapters/sqlite.adapter.js +10 -0
  14. package/dist/adapters/sqlite.adapter.js.map +1 -1
  15. package/dist/api.d.ts +13 -3
  16. package/dist/api.js +96 -62
  17. package/dist/api.js.map +1 -1
  18. package/dist/builders.d.ts +176 -0
  19. package/dist/builders.js +727 -0
  20. package/dist/builders.js.map +1 -0
  21. package/dist/config.d.ts +16 -1
  22. package/dist/config.js +95 -3
  23. package/dist/config.js.map +1 -1
  24. package/dist/contracts/auctionhouse.contract.d.ts +4 -0
  25. package/dist/contracts/auctionhouse.contract.js +234 -0
  26. package/dist/contracts/auctionhouse.contract.js.map +1 -0
  27. package/dist/contracts/booking.contract.d.ts +4 -0
  28. package/dist/contracts/booking.contract.js +225 -0
  29. package/dist/contracts/booking.contract.js.map +1 -0
  30. package/dist/contracts/bountyboard.contract.d.ts +4 -0
  31. package/dist/contracts/bountyboard.contract.js +233 -0
  32. package/dist/contracts/bountyboard.contract.js.map +1 -0
  33. package/dist/contracts/bundlemarketplace.contract.d.ts +4 -0
  34. package/dist/contracts/bundlemarketplace.contract.js +195 -0
  35. package/dist/contracts/bundlemarketplace.contract.js.map +1 -0
  36. package/dist/contracts/charitymatch.contract.d.ts +4 -0
  37. package/dist/contracts/charitymatch.contract.js +172 -0
  38. package/dist/contracts/charitymatch.contract.js.map +1 -0
  39. package/dist/contracts/coinflip.contract.js +25 -22
  40. package/dist/contracts/coinflip.contract.js.map +1 -1
  41. package/dist/contracts/crowdfund.contract.d.ts +4 -0
  42. package/dist/contracts/crowdfund.contract.js +290 -0
  43. package/dist/contracts/crowdfund.contract.js.map +1 -0
  44. package/dist/contracts/dcabot.contract.d.ts +4 -0
  45. package/dist/contracts/dcabot.contract.js +217 -0
  46. package/dist/contracts/dcabot.contract.js.map +1 -0
  47. package/dist/contracts/dice.contract.js +25 -22
  48. package/dist/contracts/dice.contract.js.map +1 -1
  49. package/dist/contracts/domainregistry.contract.d.ts +4 -0
  50. package/dist/contracts/domainregistry.contract.js +232 -0
  51. package/dist/contracts/domainregistry.contract.js.map +1 -0
  52. package/dist/contracts/exchange.contract.js +209 -168
  53. package/dist/contracts/exchange.contract.js.map +1 -1
  54. package/dist/contracts/fanclub.contract.d.ts +4 -0
  55. package/dist/contracts/fanclub.contract.js +193 -0
  56. package/dist/contracts/fanclub.contract.js.map +1 -0
  57. package/dist/contracts/giftcard.contract.d.ts +4 -0
  58. package/dist/contracts/giftcard.contract.js +158 -0
  59. package/dist/contracts/giftcard.contract.js.map +1 -0
  60. package/dist/contracts/grantrounds.contract.d.ts +4 -0
  61. package/dist/contracts/grantrounds.contract.js +265 -0
  62. package/dist/contracts/grantrounds.contract.js.map +1 -0
  63. package/dist/contracts/groupbuy.contract.d.ts +4 -0
  64. package/dist/contracts/groupbuy.contract.js +198 -0
  65. package/dist/contracts/groupbuy.contract.js.map +1 -0
  66. package/dist/contracts/helpers.d.ts +66 -0
  67. package/dist/contracts/helpers.js +166 -0
  68. package/dist/contracts/helpers.js.map +1 -0
  69. package/dist/contracts/insurancepool.contract.d.ts +4 -0
  70. package/dist/contracts/insurancepool.contract.js +281 -0
  71. package/dist/contracts/insurancepool.contract.js.map +1 -0
  72. package/dist/contracts/invoice.contract.d.ts +4 -0
  73. package/dist/contracts/invoice.contract.js +193 -0
  74. package/dist/contracts/invoice.contract.js.map +1 -0
  75. package/dist/contracts/launchpad.contract.d.ts +4 -0
  76. package/dist/contracts/launchpad.contract.js +225 -0
  77. package/dist/contracts/launchpad.contract.js.map +1 -0
  78. package/dist/contracts/lotto.contract.js +53 -37
  79. package/dist/contracts/lotto.contract.js.map +1 -1
  80. package/dist/contracts/multisigtreasury.contract.d.ts +4 -0
  81. package/dist/contracts/multisigtreasury.contract.js +245 -0
  82. package/dist/contracts/multisigtreasury.contract.js.map +1 -0
  83. package/dist/contracts/nft.contract.d.ts +1 -0
  84. package/dist/contracts/nft.contract.js +234 -195
  85. package/dist/contracts/nft.contract.js.map +1 -1
  86. package/dist/contracts/oraclebounty.contract.d.ts +4 -0
  87. package/dist/contracts/oraclebounty.contract.js +250 -0
  88. package/dist/contracts/oraclebounty.contract.js.map +1 -0
  89. package/dist/contracts/payroll.contract.d.ts +4 -0
  90. package/dist/contracts/payroll.contract.js +232 -0
  91. package/dist/contracts/payroll.contract.js.map +1 -0
  92. package/dist/contracts/paywall.contract.d.ts +4 -0
  93. package/dist/contracts/paywall.contract.js +185 -0
  94. package/dist/contracts/paywall.contract.js.map +1 -0
  95. package/dist/contracts/poll.contract.js +2 -0
  96. package/dist/contracts/poll.contract.js.map +1 -1
  97. package/dist/contracts/predictionmarket.contract.d.ts +4 -0
  98. package/dist/contracts/predictionmarket.contract.js +213 -0
  99. package/dist/contracts/predictionmarket.contract.js.map +1 -0
  100. package/dist/contracts/proposaltimelock.contract.d.ts +4 -0
  101. package/dist/contracts/proposaltimelock.contract.js +250 -0
  102. package/dist/contracts/proposaltimelock.contract.js.map +1 -0
  103. package/dist/contracts/questpass.contract.d.ts +4 -0
  104. package/dist/contracts/questpass.contract.js +214 -0
  105. package/dist/contracts/questpass.contract.js.map +1 -0
  106. package/dist/contracts/referral.contract.d.ts +4 -0
  107. package/dist/contracts/referral.contract.js +238 -0
  108. package/dist/contracts/referral.contract.js.map +1 -0
  109. package/dist/contracts/rental.contract.d.ts +4 -0
  110. package/dist/contracts/rental.contract.js +221 -0
  111. package/dist/contracts/rental.contract.js.map +1 -0
  112. package/dist/contracts/revenuesplit.contract.d.ts +4 -0
  113. package/dist/contracts/revenuesplit.contract.js +211 -0
  114. package/dist/contracts/revenuesplit.contract.js.map +1 -0
  115. package/dist/contracts/rps.contract.js +48 -20
  116. package/dist/contracts/rps.contract.js.map +1 -1
  117. package/dist/contracts/savings.contract.d.ts +4 -0
  118. package/dist/contracts/savings.contract.js +208 -0
  119. package/dist/contracts/savings.contract.js.map +1 -0
  120. package/dist/contracts/subscription.contract.d.ts +4 -0
  121. package/dist/contracts/subscription.contract.js +241 -0
  122. package/dist/contracts/subscription.contract.js.map +1 -0
  123. package/dist/contracts/sweepstakes.contract.d.ts +4 -0
  124. package/dist/contracts/sweepstakes.contract.js +209 -0
  125. package/dist/contracts/sweepstakes.contract.js.map +1 -0
  126. package/dist/contracts/ticketing.contract.d.ts +4 -0
  127. package/dist/contracts/ticketing.contract.js +185 -0
  128. package/dist/contracts/ticketing.contract.js.map +1 -0
  129. package/dist/contracts/tipjar.contract.js +2 -0
  130. package/dist/contracts/tipjar.contract.js.map +1 -1
  131. package/dist/contracts/token.contract.js +135 -125
  132. package/dist/contracts/token.contract.js.map +1 -1
  133. package/dist/index.d.ts +40 -0
  134. package/dist/index.js +72 -1
  135. package/dist/index.js.map +1 -1
  136. package/dist/metadata.d.ts +20 -0
  137. package/dist/metadata.js +320 -1
  138. package/dist/metadata.js.map +1 -1
  139. package/dist/providers/block-provider.d.ts +22 -0
  140. package/dist/providers/block-provider.js +3 -0
  141. package/dist/providers/block-provider.js.map +1 -0
  142. package/dist/providers/haf-client.d.ts +30 -0
  143. package/dist/providers/haf-client.js +119 -0
  144. package/dist/providers/haf-client.js.map +1 -0
  145. package/dist/providers/haf-provider.d.ts +49 -0
  146. package/dist/providers/haf-provider.js +256 -0
  147. package/dist/providers/haf-provider.js.map +1 -0
  148. package/dist/providers/hive-provider.d.ts +13 -0
  149. package/dist/providers/hive-provider.js +25 -0
  150. package/dist/providers/hive-provider.js.map +1 -0
  151. package/dist/providers/index.d.ts +4 -0
  152. package/dist/providers/index.js +21 -0
  153. package/dist/providers/index.js.map +1 -0
  154. package/dist/streamer.d.ts +65 -4
  155. package/dist/streamer.js +768 -72
  156. package/dist/streamer.js.map +1 -1
  157. package/dist/types/hive-stream.d.ts +317 -0
  158. package/dist/utils.d.ts +33 -0
  159. package/dist/utils.js +198 -2
  160. package/dist/utils.js.map +1 -1
  161. package/package.json +16 -1
  162. package/.claude/settings.local.json +0 -12
  163. package/.env.example +0 -3
  164. package/.travis.yml +0 -11
  165. package/AGENTS.md +0 -35
  166. package/CLAUDE.md +0 -75
  167. package/ecosystem.config.js +0 -17
  168. package/examples/contracts/README.md +0 -8
  169. package/examples/contracts/exchange.ts +0 -38
  170. package/examples/contracts/poll.ts +0 -21
  171. package/examples/contracts/rps.ts +0 -19
  172. package/examples/contracts/tipjar.ts +0 -19
  173. package/jest.config.js +0 -9
  174. package/test-contract-block.md +0 -19
  175. package/tests/actions.spec.ts +0 -252
  176. package/tests/adapters/actions-persistence.spec.ts +0 -144
  177. package/tests/adapters/postgresql.adapter.spec.ts +0 -127
  178. package/tests/adapters/sqlite.adapter.spec.ts +0 -181
  179. package/tests/config-input.spec.ts +0 -90
  180. package/tests/contracts/coinflip.contract.spec.ts +0 -94
  181. package/tests/contracts/dice.contract.spec.ts +0 -87
  182. package/tests/contracts/entrants.json +0 -729
  183. package/tests/contracts/exchange.contract.spec.ts +0 -84
  184. package/tests/contracts/lotto.contract.spec.ts +0 -59
  185. package/tests/contracts/nft.contract.spec.ts +0 -948
  186. package/tests/contracts/token.contract.spec.ts +0 -90
  187. package/tests/exchanges/coingecko.exchange.spec.ts +0 -169
  188. package/tests/exchanges/exchange.base.spec.ts +0 -246
  189. package/tests/helpers/mock-adapter.ts +0 -214
  190. package/tests/helpers/mock-fetch.ts +0 -165
  191. package/tests/hive-chain-features.spec.ts +0 -319
  192. package/tests/hive-rates.spec.ts +0 -443
  193. package/tests/integration/hive-rates.integration.spec.ts +0 -35
  194. package/tests/metadata.spec.ts +0 -63
  195. package/tests/setup.ts +0 -30
  196. package/tests/streamer-actions.spec.ts +0 -274
  197. package/tests/streamer.spec.ts +0 -342
  198. package/tests/types/rates.spec.ts +0 -216
  199. package/tests/utils.spec.ts +0 -113
  200. package/tsconfig.build.json +0 -4
  201. package/tslint.json +0 -21
  202. package/wallaby.js +0 -26
@@ -6,7 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.NFTContract = void 0;
7
7
  exports.createNFTContract = createNFTContract;
8
8
  const bignumber_js_1 = __importDefault(require("bignumber.js"));
9
+ const utils_1 = require("../utils");
9
10
  const contract_1 = require("./contract");
11
+ const helpers_1 = require("./helpers");
12
+ const helpers_2 = require("./helpers");
10
13
  const CONTRACT_NAME = 'hivenft';
11
14
  class NFTContract {
12
15
  _instance;
@@ -17,6 +20,7 @@ class NFTContract {
17
20
  transactionId;
18
21
  async create() {
19
22
  this.adapter = this._instance.getAdapter();
23
+ (0, helpers_2.ensureSqlAdapter)(this.adapter);
20
24
  await this.initializeNFTTables();
21
25
  }
22
26
  destroy() {
@@ -29,82 +33,84 @@ class NFTContract {
29
33
  this.transactionId = transactionId;
30
34
  }
31
35
  async initializeNFTTables() {
32
- try {
33
- await this.adapter.query(`
34
- CREATE TABLE IF NOT EXISTS nft_collections (
35
- symbol TEXT PRIMARY KEY,
36
- name TEXT NOT NULL,
37
- description TEXT,
38
- creator TEXT NOT NULL,
39
- max_supply INTEGER,
40
- current_supply INTEGER NOT NULL DEFAULT 0,
41
- royalty REAL DEFAULT 0,
42
- base_uri TEXT,
43
- allow_updates BOOLEAN DEFAULT TRUE,
44
- updateable_by_owner BOOLEAN DEFAULT FALSE,
45
- created_at DATETIME NOT NULL
46
- )
47
- `);
48
- await this.adapter.query(`
49
- CREATE TABLE IF NOT EXISTS nft_tokens (
50
- token_id TEXT NOT NULL,
51
- collection_symbol TEXT NOT NULL,
52
- owner TEXT NOT NULL,
53
- metadata TEXT,
54
- attributes TEXT,
55
- minted_at DATETIME NOT NULL,
56
- minted_by TEXT NOT NULL,
57
- burned BOOLEAN DEFAULT FALSE,
58
- PRIMARY KEY (token_id, collection_symbol),
59
- FOREIGN KEY (collection_symbol) REFERENCES nft_collections(symbol)
60
- )
61
- `);
62
- await this.adapter.query(`
63
- CREATE TABLE IF NOT EXISTS nft_listings (
64
- id INTEGER PRIMARY KEY AUTOINCREMENT,
65
- token_id TEXT NOT NULL,
66
- collection_symbol TEXT NOT NULL,
67
- seller TEXT NOT NULL,
68
- price TEXT NOT NULL,
69
- currency TEXT NOT NULL DEFAULT 'HIVE',
70
- listed_at DATETIME NOT NULL,
71
- active BOOLEAN DEFAULT TRUE,
72
- FOREIGN KEY (token_id, collection_symbol) REFERENCES nft_tokens(token_id, collection_symbol)
73
- )
74
- `);
75
- await this.adapter.query(`
76
- CREATE TABLE IF NOT EXISTS nft_transfers (
77
- id INTEGER PRIMARY KEY AUTOINCREMENT,
78
- token_id TEXT NOT NULL,
79
- collection_symbol TEXT NOT NULL,
80
- from_account TEXT NOT NULL,
81
- to_account TEXT NOT NULL,
82
- transfer_type TEXT NOT NULL,
83
- price TEXT,
84
- currency TEXT,
85
- block_number INTEGER NOT NULL,
86
- transaction_id TEXT NOT NULL,
87
- timestamp DATETIME NOT NULL,
88
- FOREIGN KEY (token_id, collection_symbol) REFERENCES nft_tokens(token_id, collection_symbol)
89
- )
90
- `);
91
- await this.adapter.query(`
92
- CREATE INDEX IF NOT EXISTS idx_nft_tokens_owner ON nft_tokens(owner);
93
- `);
94
- await this.adapter.query(`
95
- CREATE INDEX IF NOT EXISTS idx_nft_tokens_collection ON nft_tokens(collection_symbol);
96
- `);
97
- await this.adapter.query(`
98
- CREATE INDEX IF NOT EXISTS idx_nft_listings_active ON nft_listings(active, collection_symbol);
99
- `);
100
- }
101
- catch (error) {
102
- console.error('[NFTContract] Error initializing tables:', error);
36
+ await this.adapter.query(`
37
+ CREATE TABLE IF NOT EXISTS nft_collections (
38
+ symbol TEXT PRIMARY KEY,
39
+ name TEXT NOT NULL,
40
+ description TEXT,
41
+ creator TEXT NOT NULL,
42
+ max_supply INTEGER,
43
+ current_supply INTEGER NOT NULL DEFAULT 0,
44
+ royalty REAL DEFAULT 0,
45
+ base_uri TEXT,
46
+ allow_updates BOOLEAN DEFAULT TRUE,
47
+ updateable_by_owner BOOLEAN DEFAULT FALSE,
48
+ created_at DATETIME NOT NULL
49
+ )
50
+ `);
51
+ await this.adapter.query(`
52
+ CREATE TABLE IF NOT EXISTS nft_tokens (
53
+ token_id TEXT NOT NULL,
54
+ collection_symbol TEXT NOT NULL,
55
+ owner TEXT NOT NULL,
56
+ metadata TEXT,
57
+ attributes TEXT,
58
+ minted_at DATETIME NOT NULL,
59
+ minted_by TEXT NOT NULL,
60
+ burned BOOLEAN DEFAULT FALSE,
61
+ PRIMARY KEY (token_id, collection_symbol),
62
+ FOREIGN KEY (collection_symbol) REFERENCES nft_collections(symbol)
63
+ )
64
+ `);
65
+ await this.adapter.query(`
66
+ CREATE TABLE IF NOT EXISTS nft_listings (
67
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
68
+ token_id TEXT NOT NULL,
69
+ collection_symbol TEXT NOT NULL,
70
+ seller TEXT NOT NULL,
71
+ price TEXT NOT NULL,
72
+ currency TEXT NOT NULL DEFAULT 'HIVE',
73
+ listed_at DATETIME NOT NULL,
74
+ active BOOLEAN DEFAULT TRUE,
75
+ FOREIGN KEY (token_id, collection_symbol) REFERENCES nft_tokens(token_id, collection_symbol)
76
+ )
77
+ `);
78
+ await this.adapter.query(`
79
+ CREATE TABLE IF NOT EXISTS nft_transfers (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ token_id TEXT NOT NULL,
82
+ collection_symbol TEXT NOT NULL,
83
+ from_account TEXT NOT NULL,
84
+ to_account TEXT NOT NULL,
85
+ transfer_type TEXT NOT NULL,
86
+ price TEXT,
87
+ currency TEXT,
88
+ block_number INTEGER NOT NULL,
89
+ transaction_id TEXT NOT NULL,
90
+ timestamp DATETIME NOT NULL,
91
+ FOREIGN KEY (token_id, collection_symbol) REFERENCES nft_tokens(token_id, collection_symbol)
92
+ )
93
+ `);
94
+ await this.adapter.query(`
95
+ CREATE INDEX IF NOT EXISTS idx_nft_tokens_owner ON nft_tokens(owner);
96
+ `);
97
+ await this.adapter.query(`
98
+ CREATE INDEX IF NOT EXISTS idx_nft_tokens_collection ON nft_tokens(collection_symbol);
99
+ `);
100
+ await this.adapter.query(`
101
+ CREATE INDEX IF NOT EXISTS idx_nft_listings_active ON nft_listings(active, collection_symbol);
102
+ `);
103
+ }
104
+ async withTransaction(work) {
105
+ if (typeof this.adapter?.runInTransaction === 'function') {
106
+ return this.adapter.runInTransaction(work);
103
107
  }
108
+ return work(this.adapter);
104
109
  }
105
110
  async createCollection(payload, { sender }) {
106
111
  try {
107
112
  const { symbol, name, description = '', maxSupply, royalty = 0, baseUri = '', allowUpdates = true, updateableByOwner = false } = payload;
113
+ const normalizedMaxSupply = maxSupply ?? null;
108
114
  if (!symbol.match(/^[A-Z0-9]{1,20}$/)) {
109
115
  throw new Error('Symbol must be 1-20 uppercase alphanumeric characters');
110
116
  }
@@ -123,23 +129,25 @@ class NFTContract {
123
129
  if (baseUri && baseUri.length > 500) {
124
130
  throw new Error('Base URI must be 500 characters or less');
125
131
  }
126
- const existingCollection = await this.adapter.query('SELECT symbol FROM nft_collections WHERE symbol = ?', [symbol]);
127
- if (existingCollection && existingCollection.length > 0) {
128
- throw new Error(`Collection with symbol ${symbol} already exists`);
129
- }
130
- await this.adapter.query(`
131
- INSERT INTO nft_collections (symbol, name, description, creator, max_supply, royalty, base_uri, allow_updates, updateable_by_owner, created_at)
132
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
133
- `, [symbol, name, description, sender, maxSupply, royalty, baseUri, allowUpdates, updateableByOwner, new Date()]);
134
- await this.adapter.addEvent(new Date(), CONTRACT_NAME, 'createCollection', payload, {
135
- action: 'collection_created',
136
- data: {
137
- symbol,
138
- name,
139
- creator: sender,
140
- maxSupply,
141
- royalty
132
+ await this.withTransaction(async (adapter) => {
133
+ const existingCollection = await adapter.query('SELECT symbol FROM nft_collections WHERE symbol = ?', [symbol]);
134
+ if (existingCollection && existingCollection.length > 0) {
135
+ throw new Error(`Collection with symbol ${symbol} already exists`);
142
136
  }
137
+ await adapter.query(`
138
+ INSERT INTO nft_collections (symbol, name, description, creator, max_supply, royalty, base_uri, allow_updates, updateable_by_owner, created_at)
139
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
140
+ `, [symbol, name, description, sender, normalizedMaxSupply, royalty, baseUri, allowUpdates, updateableByOwner, new Date()]);
141
+ await adapter.addEvent(new Date(), CONTRACT_NAME, 'createCollection', payload, {
142
+ action: 'collection_created',
143
+ data: {
144
+ symbol,
145
+ name,
146
+ creator: sender,
147
+ maxSupply,
148
+ royalty
149
+ }
150
+ });
143
151
  });
144
152
  console.log(`[NFTContract] Collection ${symbol} created by ${sender}`);
145
153
  }
@@ -168,30 +176,34 @@ class NFTContract {
168
176
  if (attributes && attributes.length > 1000) {
169
177
  throw new Error('Attributes must be 1000 characters or less');
170
178
  }
171
- if (collectionData.max_supply && collectionData.current_supply >= collectionData.max_supply) {
172
- throw new Error('Collection has reached maximum supply');
173
- }
174
- const existingToken = await this.adapter.query('SELECT token_id FROM nft_tokens WHERE token_id = ? AND collection_symbol = ?', [tokenId, collectionSymbol]);
175
- if (existingToken && existingToken.length > 0) {
176
- throw new Error(`Token ${tokenId} already exists in collection ${collectionSymbol}`);
177
- }
178
- await this.adapter.query(`
179
- INSERT INTO nft_tokens (token_id, collection_symbol, owner, metadata, attributes, minted_at, minted_by)
180
- VALUES (?, ?, ?, ?, ?, ?, ?)
181
- `, [tokenId, collectionSymbol, to, metadata, attributes, new Date(), sender]);
182
- await this.adapter.query('UPDATE nft_collections SET current_supply = current_supply + 1 WHERE symbol = ?', [collectionSymbol]);
183
- await this.adapter.query(`
184
- INSERT INTO nft_transfers (token_id, collection_symbol, from_account, to_account, transfer_type, block_number, transaction_id, timestamp)
185
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
186
- `, [tokenId, collectionSymbol, 'null', to, 'mint', this.blockNumber, this.transactionId, new Date()]);
187
- await this.adapter.addEvent(new Date(), CONTRACT_NAME, 'mintNFT', payload, {
188
- action: 'nft_minted',
189
- data: {
190
- tokenId,
191
- collectionSymbol,
192
- to,
193
- mintedBy: sender
179
+ await this.withTransaction(async (adapter) => {
180
+ // Re-read supply inside the transaction to prevent race conditions
181
+ const collectionCheck = await adapter.query('SELECT max_supply, current_supply FROM nft_collections WHERE symbol = ?', [collectionSymbol]);
182
+ if (collectionCheck[0]?.max_supply && collectionCheck[0].current_supply >= collectionCheck[0].max_supply) {
183
+ throw new Error('Collection has reached maximum supply');
194
184
  }
185
+ const existingToken = await adapter.query('SELECT token_id FROM nft_tokens WHERE token_id = ? AND collection_symbol = ?', [tokenId, collectionSymbol]);
186
+ if (existingToken && existingToken.length > 0) {
187
+ throw new Error(`Token ${tokenId} already exists in collection ${collectionSymbol}`);
188
+ }
189
+ await adapter.query(`
190
+ INSERT INTO nft_tokens (token_id, collection_symbol, owner, metadata, attributes, minted_at, minted_by)
191
+ VALUES (?, ?, ?, ?, ?, ?, ?)
192
+ `, [tokenId, collectionSymbol, to, metadata, attributes, new Date(), sender]);
193
+ await adapter.query('UPDATE nft_collections SET current_supply = current_supply + 1 WHERE symbol = ?', [collectionSymbol]);
194
+ await adapter.query(`
195
+ INSERT INTO nft_transfers (token_id, collection_symbol, from_account, to_account, transfer_type, block_number, transaction_id, timestamp)
196
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
197
+ `, [tokenId, collectionSymbol, 'null', to, 'mint', this.blockNumber, this.transactionId, new Date()]);
198
+ await adapter.addEvent(new Date(), CONTRACT_NAME, 'mintNFT', payload, {
199
+ action: 'nft_minted',
200
+ data: {
201
+ tokenId,
202
+ collectionSymbol,
203
+ to,
204
+ mintedBy: sender
205
+ }
206
+ });
195
207
  });
196
208
  console.log(`[NFTContract] NFT ${tokenId} minted in collection ${collectionSymbol} to ${to}`);
197
209
  }
@@ -246,16 +258,18 @@ class NFTContract {
246
258
  throw new Error('No update fields provided');
247
259
  }
248
260
  updateValues.push(tokenId, collectionSymbol);
249
- await this.adapter.query(`UPDATE nft_tokens SET ${updateFields.join(', ')} WHERE token_id = ? AND collection_symbol = ?`, updateValues);
250
- await this.adapter.addEvent(new Date(), CONTRACT_NAME, 'updateNFT', payload, {
251
- action: 'nft_updated',
252
- data: {
253
- tokenId,
254
- collectionSymbol,
255
- updatedBy: sender,
256
- metadata: metadata !== undefined ? metadata : 'unchanged',
257
- attributes: attributes !== undefined ? attributes : 'unchanged'
258
- }
261
+ await this.withTransaction(async (adapter) => {
262
+ await adapter.query(`UPDATE nft_tokens SET ${updateFields.join(', ')} WHERE token_id = ? AND collection_symbol = ?`, updateValues);
263
+ await adapter.addEvent(new Date(), CONTRACT_NAME, 'updateNFT', payload, {
264
+ action: 'nft_updated',
265
+ data: {
266
+ tokenId,
267
+ collectionSymbol,
268
+ updatedBy: sender,
269
+ metadata: metadata !== undefined ? metadata : 'unchanged',
270
+ attributes: attributes !== undefined ? attributes : 'unchanged'
271
+ }
272
+ });
259
273
  });
260
274
  console.log(`[NFTContract] NFT ${tokenId} updated by ${sender}`);
261
275
  }
@@ -278,23 +292,25 @@ class NFTContract {
278
292
  if (tokenData.owner === to) {
279
293
  throw new Error('Cannot transfer to the same address');
280
294
  }
281
- const activeListings = await this.adapter.query('SELECT id FROM nft_listings WHERE token_id = ? AND collection_symbol = ? AND seller = ? AND active = TRUE', [tokenId, collectionSymbol, sender]);
282
- if (activeListings && activeListings.length > 0) {
283
- await this.adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ? AND seller = ?', [tokenId, collectionSymbol, sender]);
284
- }
285
- await this.adapter.query('UPDATE nft_tokens SET owner = ? WHERE token_id = ? AND collection_symbol = ?', [to, tokenId, collectionSymbol]);
286
- await this.adapter.query(`
287
- INSERT INTO nft_transfers (token_id, collection_symbol, from_account, to_account, transfer_type, block_number, transaction_id, timestamp)
288
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
289
- `, [tokenId, collectionSymbol, sender, to, 'transfer', this.blockNumber, this.transactionId, new Date()]);
290
- await this.adapter.addEvent(new Date(), CONTRACT_NAME, 'transferNFT', payload, {
291
- action: 'nft_transferred',
292
- data: {
293
- tokenId,
294
- collectionSymbol,
295
- from: sender,
296
- to
295
+ await this.withTransaction(async (adapter) => {
296
+ const activeListings = await adapter.query('SELECT id FROM nft_listings WHERE token_id = ? AND collection_symbol = ? AND seller = ? AND active = TRUE', [tokenId, collectionSymbol, sender]);
297
+ if (activeListings && activeListings.length > 0) {
298
+ await adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ? AND seller = ?', [tokenId, collectionSymbol, sender]);
297
299
  }
300
+ await adapter.query('UPDATE nft_tokens SET owner = ? WHERE token_id = ? AND collection_symbol = ?', [to, tokenId, collectionSymbol]);
301
+ await adapter.query(`
302
+ INSERT INTO nft_transfers (token_id, collection_symbol, from_account, to_account, transfer_type, block_number, transaction_id, timestamp)
303
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
304
+ `, [tokenId, collectionSymbol, sender, to, 'transfer', this.blockNumber, this.transactionId, new Date()]);
305
+ await adapter.addEvent(new Date(), CONTRACT_NAME, 'transferNFT', payload, {
306
+ action: 'nft_transferred',
307
+ data: {
308
+ tokenId,
309
+ collectionSymbol,
310
+ from: sender,
311
+ to
312
+ }
313
+ });
298
314
  });
299
315
  console.log(`[NFTContract] NFT ${tokenId} transferred from ${sender} to ${to}`);
300
316
  }
@@ -314,20 +330,22 @@ class NFTContract {
314
330
  if (tokenData.owner !== sender) {
315
331
  throw new Error('Only the token owner can burn the NFT');
316
332
  }
317
- await this.adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ?', [tokenId, collectionSymbol]);
318
- await this.adapter.query('UPDATE nft_tokens SET burned = TRUE WHERE token_id = ? AND collection_symbol = ?', [tokenId, collectionSymbol]);
319
- await this.adapter.query('UPDATE nft_collections SET current_supply = current_supply - 1 WHERE symbol = ?', [collectionSymbol]);
320
- await this.adapter.query(`
321
- INSERT INTO nft_transfers (token_id, collection_symbol, from_account, to_account, transfer_type, block_number, transaction_id, timestamp)
322
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
323
- `, [tokenId, collectionSymbol, sender, 'null', 'burn', this.blockNumber, this.transactionId, new Date()]);
324
- await this.adapter.addEvent(new Date(), CONTRACT_NAME, 'burnNFT', payload, {
325
- action: 'nft_burned',
326
- data: {
327
- tokenId,
328
- collectionSymbol,
329
- burnedBy: sender
330
- }
333
+ await this.withTransaction(async (adapter) => {
334
+ await adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ?', [tokenId, collectionSymbol]);
335
+ await adapter.query('UPDATE nft_tokens SET burned = TRUE WHERE token_id = ? AND collection_symbol = ?', [tokenId, collectionSymbol]);
336
+ await adapter.query('UPDATE nft_collections SET current_supply = current_supply - 1 WHERE symbol = ?', [collectionSymbol]);
337
+ await adapter.query(`
338
+ INSERT INTO nft_transfers (token_id, collection_symbol, from_account, to_account, transfer_type, block_number, transaction_id, timestamp)
339
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
340
+ `, [tokenId, collectionSymbol, sender, 'null', 'burn', this.blockNumber, this.transactionId, new Date()]);
341
+ await adapter.addEvent(new Date(), CONTRACT_NAME, 'burnNFT', payload, {
342
+ action: 'nft_burned',
343
+ data: {
344
+ tokenId,
345
+ collectionSymbol,
346
+ burnedBy: sender
347
+ }
348
+ });
331
349
  });
332
350
  console.log(`[NFTContract] NFT ${tokenId} burned by ${sender}`);
333
351
  }
@@ -354,20 +372,22 @@ class NFTContract {
354
372
  if (!['HIVE', 'HBD'].includes(currency)) {
355
373
  throw new Error('Currency must be HIVE or HBD');
356
374
  }
357
- await this.adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ? AND seller = ?', [tokenId, collectionSymbol, sender]);
358
- await this.adapter.query(`
359
- INSERT INTO nft_listings (token_id, collection_symbol, seller, price, currency, listed_at)
360
- VALUES (?, ?, ?, ?, ?, ?)
361
- `, [tokenId, collectionSymbol, sender, price, currency, new Date()]);
362
- await this.adapter.addEvent(new Date(), CONTRACT_NAME, 'listNFT', payload, {
363
- action: 'nft_listed',
364
- data: {
365
- tokenId,
366
- collectionSymbol,
367
- seller: sender,
368
- price,
369
- currency
370
- }
375
+ await this.withTransaction(async (adapter) => {
376
+ await adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ? AND seller = ?', [tokenId, collectionSymbol, sender]);
377
+ await adapter.query(`
378
+ INSERT INTO nft_listings (token_id, collection_symbol, seller, price, currency, listed_at)
379
+ VALUES (?, ?, ?, ?, ?, ?)
380
+ `, [tokenId, collectionSymbol, sender, price, currency, new Date()]);
381
+ await adapter.addEvent(new Date(), CONTRACT_NAME, 'listNFT', payload, {
382
+ action: 'nft_listed',
383
+ data: {
384
+ tokenId,
385
+ collectionSymbol,
386
+ seller: sender,
387
+ price,
388
+ currency
389
+ }
390
+ });
371
391
  });
372
392
  console.log(`[NFTContract] NFT ${tokenId} listed by ${sender} for ${price} ${currency}`);
373
393
  }
@@ -383,14 +403,16 @@ class NFTContract {
383
403
  if (!listing || listing.length === 0) {
384
404
  throw new Error(`No active listing found for token ${tokenId}`);
385
405
  }
386
- await this.adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ? AND seller = ?', [tokenId, collectionSymbol, sender]);
387
- await this.adapter.addEvent(new Date(), CONTRACT_NAME, 'unlistNFT', payload, {
388
- action: 'nft_unlisted',
389
- data: {
390
- tokenId,
391
- collectionSymbol,
392
- seller: sender
393
- }
406
+ await this.withTransaction(async (adapter) => {
407
+ await adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ? AND seller = ?', [tokenId, collectionSymbol, sender]);
408
+ await adapter.addEvent(new Date(), CONTRACT_NAME, 'unlistNFT', payload, {
409
+ action: 'nft_unlisted',
410
+ data: {
411
+ tokenId,
412
+ collectionSymbol,
413
+ seller: sender
414
+ }
415
+ });
394
416
  });
395
417
  console.log(`[NFTContract] NFT ${tokenId} unlisted by ${sender}`);
396
418
  }
@@ -421,32 +443,42 @@ class NFTContract {
421
443
  const collection = await this.adapter.query('SELECT royalty, creator FROM nft_collections WHERE symbol = ?', [collectionSymbol]);
422
444
  let royaltyAmount = new bignumber_js_1.default(0);
423
445
  let sellerAmount = paidAmount;
446
+ let royaltyRecipient = null;
424
447
  if (collection && collection.length > 0 && collection[0].royalty > 0) {
425
448
  royaltyAmount = paidAmount.multipliedBy(collection[0].royalty);
426
449
  sellerAmount = paidAmount.minus(royaltyAmount);
427
450
  if (royaltyAmount.gt(0) && collection[0].creator !== listingData.seller) {
428
- console.log(`[NFTContract] Royalty payment: ${royaltyAmount.toFixed(3)} ${asset} to ${collection[0].creator}`);
451
+ royaltyRecipient = collection[0].creator;
429
452
  }
430
453
  }
431
- await this.adapter.query('UPDATE nft_tokens SET owner = ? WHERE token_id = ? AND collection_symbol = ?', [sender, tokenId, collectionSymbol]);
432
- await this.adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ?', [tokenId, collectionSymbol]);
433
- await this.adapter.query(`
434
- INSERT INTO nft_transfers (token_id, collection_symbol, from_account, to_account, transfer_type, price, currency, block_number, transaction_id, timestamp)
435
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
436
- `, [tokenId, collectionSymbol, listingData.seller, sender, 'sale', amount, asset, this.blockNumber, this.transactionId, new Date()]);
437
- await this.adapter.addEvent(new Date(), CONTRACT_NAME, 'buyNFT', payload, {
438
- action: 'nft_sold',
439
- data: {
440
- tokenId,
441
- collectionSymbol,
442
- seller: listingData.seller,
443
- buyer: sender,
444
- price: amount,
445
- currency: asset,
446
- royaltyAmount: royaltyAmount.toFixed(3),
447
- sellerAmount: sellerAmount.toFixed(3)
448
- }
454
+ await this.withTransaction(async (adapter) => {
455
+ await adapter.query('UPDATE nft_tokens SET owner = ? WHERE token_id = ? AND collection_symbol = ?', [sender, tokenId, collectionSymbol]);
456
+ await adapter.query('UPDATE nft_listings SET active = FALSE WHERE token_id = ? AND collection_symbol = ?', [tokenId, collectionSymbol]);
457
+ await adapter.query(`
458
+ INSERT INTO nft_transfers (token_id, collection_symbol, from_account, to_account, transfer_type, price, currency, block_number, transaction_id, timestamp)
459
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
460
+ `, [tokenId, collectionSymbol, listingData.seller, sender, 'sale', amount, asset, this.blockNumber, this.transactionId, new Date()]);
461
+ await adapter.addEvent(new Date(), CONTRACT_NAME, 'buyNFT', payload, {
462
+ action: 'nft_sold',
463
+ data: {
464
+ tokenId,
465
+ collectionSymbol,
466
+ seller: listingData.seller,
467
+ buyer: sender,
468
+ price: amount,
469
+ currency: asset,
470
+ royaltyAmount: royaltyAmount.toFixed(3),
471
+ sellerAmount: sellerAmount.toFixed(3)
472
+ }
473
+ });
449
474
  });
475
+ // Pay the seller using the Streamer's transfer method
476
+ const contractAccount = this._instance.config?.HIVE_ACCOUNT || 'beggars';
477
+ await this._instance.transferHiveTokens(contractAccount, listingData.seller, sellerAmount.toFixed(3), asset, `NFT sale: ${tokenId} from ${collectionSymbol}`);
478
+ // Pay royalty to creator if applicable
479
+ if (royaltyRecipient && royaltyAmount.gt(0)) {
480
+ await this._instance.transferHiveTokens(contractAccount, royaltyRecipient, royaltyAmount.toFixed(3), asset, `NFT royalty: ${tokenId} from ${collectionSymbol}`);
481
+ }
450
482
  console.log(`[NFTContract] NFT ${tokenId} sold to ${sender} for ${amount} ${asset}`);
451
483
  }
452
484
  catch (error) {
@@ -572,13 +604,20 @@ function createNFTContract(options = {}) {
572
604
  unlistNFT: (0, contract_1.action)((payload, ctx) => callWithContext(payload, ctx, instance.unlistNFT.bind(instance), { sender: ctx.sender }), {
573
605
  trigger: 'custom_json'
574
606
  }),
575
- buyNFT: (0, contract_1.action)((payload, ctx) => {
607
+ buyNFT: (0, contract_1.action)(async (payload, ctx) => {
576
608
  const amountRaw = ctx.transfer?.rawAmount || '';
577
- const [amount, asset] = amountRaw.split(' ');
609
+ const parsedAmount = (0, helpers_1.parseBlockchainAmount)(amountRaw);
610
+ // Verify the transfer actually happened on-chain
611
+ const transaction = await utils_1.Utils.getTransaction(instance._instance['client'], ctx.block.number, ctx.transaction.id);
612
+ const contractAccount = instance._instance.config?.HIVE_ACCOUNT || 'beggars';
613
+ const verified = await utils_1.Utils.verifyTransfer(transaction, ctx.sender, contractAccount, amountRaw);
614
+ if (!verified) {
615
+ throw new Error('Transfer verification failed');
616
+ }
578
617
  return callWithContext(payload, ctx, instance.buyNFT.bind(instance), {
579
618
  sender: ctx.sender,
580
- amount,
581
- asset
619
+ amount: parsedAmount.amount,
620
+ asset: parsedAmount.asset
582
621
  });
583
622
  }, {
584
623
  trigger: 'transfer'