hive-stream 3.0.2 → 3.0.3

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