stacksagent 1.4.0 → 1.5.2

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,3041 @@
1
+ id,domain,example_type,scenario,problem,solution_code,explanation,test_inputs,expected_outputs,pitfalls,live_example_url,related_snippets,tags,difficulty
2
+ 1,defi,integration,swap-with-slippage,Execute token swap on Alex DEX with slippage protection to prevent sandwich attacks,"// Production swap pattern from sbtc-market-frontend/src/lib/contract.ts
3
+ import { request } from '@stacks/connect';
4
+ import { uintCV, contractPrincipalCV, cvToHex } from '@stacks/transactions';
5
+
6
+ async function swapWithSlippageProtection() {
7
+ const { contractAddress, contractName } = {
8
+ contractAddress: 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9',
9
+ contractName: 'amm-swap-pool-v1-1'
10
+ };
11
+
12
+ // Token addresses
13
+ const tokenX = 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-wstx';
14
+ const tokenY = 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token';
15
+
16
+ // Swap 1 STX (1000000 micro-STX)
17
+ const amountIn = 1000000;
18
+
19
+ // Accept 1% slippage (minAmountOut = 99% of expected)
20
+ const minAmountOut = 990000;
21
+
22
+ // Build function arguments
23
+ const args = [
24
+ contractPrincipalCV(tokenX.split('.')[0], tokenX.split('.')[1]),
25
+ contractPrincipalCV(tokenY.split('.')[0], tokenY.split('.')[1]),
26
+ uintCV(amountIn),
27
+ uintCV(minAmountOut)
28
+ ];
29
+
30
+ // Execute contract call using NEW API
31
+ return request('stx_callContract', {
32
+ contract: `${contractAddress}.${contractName}`,
33
+ functionName: 'swap-helper',
34
+ functionArgs: args.map(cvToHex),
35
+ postConditionMode: 'deny', // Always use 'deny' for security
36
+ network: 'mainnet'
37
+ });
38
+ }
39
+
40
+ // Real-world usage from sbtc-market-frontend
41
+ export async function openSwapShares(
42
+ marketId: number | bigint,
43
+ fromSide: boolean,
44
+ amountIn: number | bigint
45
+ ) {
46
+ const args = [
47
+ uintCV(BigInt(marketId)),
48
+ boolCV(fromSide),
49
+ uintCV(BigInt(amountIn))
50
+ ];
51
+
52
+ return request('stx_callContract', {
53
+ contract: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.prediction-market-v1',
54
+ functionName: 'swap-shares',
55
+ functionArgs: args.map(cvToHex),
56
+ postConditionMode: 'allow', // Or 'deny' with post-conditions
57
+ network: 'mainnet'
58
+ });
59
+ }","Production swap implementation from sbtc-market-frontend. Demonstrates correct usage of request('stx_callContract', ...) with .map(cvToHex) for function arguments. Includes real contract addresses and slippage protection.","{""amountIn"": 1000000, ""minAmountOut"": 990000, ""tokenXContract"": ""SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-wstx"", ""tokenYContract"": ""SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token""}","{""txId"": ""0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"", ""success"": true, ""actualAmountOut"": 995000, ""slippagePercent"": 0.5}","- Forgetting .map(cvToHex) on functionArgs causes 'Invalid hex' errors
60
+ - Using deprecated openContractCall instead of request('stx_callContract', ...)
61
+ - Not setting minAmountOut allows unlimited slippage (sandwich attacks)
62
+ - Using PostConditionMode.Allow without post-conditions (security risk)
63
+ - Not using dynamic import: const { request } = await import('@stacks/connect')",https://github.com/your-username/sbtc-market-frontend/blob/main/src/lib/contract.ts,"defi-protocols.csv:1,stacks-js-core.csv:20,security-patterns.csv:5","alex,swap,slippage,security,mainnet",intermediate
64
+ 2,defi,quickstart,add-liquidity,Add liquidity to STX/ALEX pool and receive LP tokens,"// Production liquidity provision from prediction market
65
+ // From sbtc-market-frontend/src/lib/contract.ts
66
+
67
+ import { request } from '@stacks/connect';
68
+ import { uintCV, cvToHex } from '@stacks/transactions';
69
+
70
+ export async function openMintCompleteSet(marketId: number | bigint, amount: number | bigint) {
71
+ const { contractAddress, contractName } = {
72
+ contractAddress: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
73
+ contractName: 'prediction-market-v1'
74
+ };
75
+
76
+ // Mint complete set (adds liquidity to both YES and NO sides)
77
+ const args = [
78
+ uintCV(BigInt(marketId)),
79
+ uintCV(BigInt(amount))
80
+ ];
81
+
82
+ return request('stx_callContract', {
83
+ contract: `${contractAddress}.${contractName}`,
84
+ functionName: 'mint-complete-set',
85
+ functionArgs: args.map(cvToHex),
86
+ postConditionMode: 'allow',
87
+ network: 'mainnet'
88
+ });
89
+ }
90
+
91
+ // Example: Mint 10 complete sets for market ID 5
92
+ // This locks 10 sBTC and gives you 10 YES tokens + 10 NO tokens
93
+ async function mintLiquidityExample() {
94
+ const marketId = 5;
95
+ const amount = 10_000_000; // 10 sBTC (6 decimals)
96
+
97
+ const result = await openMintCompleteSet(marketId, amount);
98
+ console.log('Liquidity added:', result);
99
+ return result;
100
+ }",Production liquidity provision from sbtc-market-frontend. Minting complete sets adds liquidity to prediction markets by locking collateral and receiving both YES and NO tokens.,"{""marketId"": 5, ""amount"": 10000000, ""walletBalance"": 15000000}","{""txId"": ""0x1234567890abcdef..."", ""success"": true, ""yesTokensReceived"": 10000000, ""noTokensReceived"": 10000000, ""collateralLocked"": 10000000}","- Not having enough collateral balance
101
+ - Forgetting that you receive BOTH YES and NO tokens
102
+ - Not understanding that complete sets always maintain 1:1:1 ratio (collateral:yes:no)
103
+ - Using wrong contract address or function name",https://github.com/your-username/sbtc-market-frontend/blob/main/src/lib/contract.ts#L54,"defi-protocols.csv:3,fungible-tokens.csv:8","alex,liquidity,amm,lp-tokens",beginner
104
+ 3,defi,quickstart,remove-liquidity,Remove liquidity from pool and receive underlying tokens back,"// Production liquidity removal from prediction market
105
+ // From sbtc-market-frontend/src/lib/contract.ts
106
+
107
+ import { request } from '@stacks/connect';
108
+ import { uintCV, cvToHex } from '@stacks/transactions';
109
+
110
+ export async function openBurnCompleteSet(marketId: number | bigint, amount: number | bigint) {
111
+ const { contractAddress, contractName } = {
112
+ contractAddress: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
113
+ contractName: 'prediction-market-v1'
114
+ };
115
+
116
+ // Burn complete set (removes liquidity, requires equal YES + NO tokens)
117
+ const args = [
118
+ uintCV(BigInt(marketId)),
119
+ uintCV(BigInt(amount))
120
+ ];
121
+
122
+ return request('stx_callContract', {
123
+ contract: `${contractAddress}.${contractName}`,
124
+ functionName: 'burn-complete-set',
125
+ functionArgs: args.map(cvToHex),
126
+ postConditionMode: 'allow',
127
+ network: 'mainnet'
128
+ });
129
+ }
130
+
131
+ // Example: Burn 5 complete sets to get sBTC back
132
+ async function removeLiquidityExample() {
133
+ const marketId = 5;
134
+ const amount = 5_000_000; // 5 complete sets
135
+
136
+ // Must have 5 YES + 5 NO tokens to burn
137
+ const result = await openBurnCompleteSet(marketId, amount);
138
+ console.log('Liquidity removed, received sBTC:', result);
139
+ return result;
140
+ }",Production liquidity removal from sbtc-market-frontend. Burning complete sets removes liquidity by returning equal YES and NO tokens to get collateral back.,"{""marketId"": 5, ""amount"": 5000000, ""yesTokenBalance"": 8000000, ""noTokenBalance"": 7000000}","{""txId"": ""0xabcdef1234567890..."", ""success"": true, ""collateralReturned"": 5000000, ""yesTokensBurned"": 5000000, ""noTokensBurned"": 5000000}","- Trying to burn more than your lowest token balance (need equal YES + NO)
141
+ - Not understanding that you MUST have both token types to burn
142
+ - Forgetting to check token balances before calling
143
+ - Not realizing this only works before market resolution",https://github.com/your-username/sbtc-market-frontend/blob/main/src/lib/contract.ts#L59,"defi-protocols.csv:3,fungible-tokens.csv:10","alex,liquidity,remove,lp-tokens",beginner
144
+ 4,defi,quickstart,pyth-oracle-price-feed,Query current token reserves and price from an AMM pool,"// Production Pyth oracle integration from sbtc-market-frontend
145
+ // From sbtc-market-frontend/src/lib/contract.ts - openResolveMarket
146
+
147
+ import { request } from '@stacks/connect';
148
+ import { uintCV, bufferCV, tupleCV, contractPrincipalCV, cvToHex } from '@stacks/transactions';
149
+
150
+ interface ExecutionPlan {
151
+ pythStorageContract: string;
152
+ pythDecoderContract: string;
153
+ wormholeCoreContract: string;
154
+ }
155
+
156
+ export async function openResolveMarket(
157
+ marketId: number | bigint,
158
+ vaaHex: string,
159
+ executionPlan: ExecutionPlan
160
+ ) {
161
+ console.log('[openResolveMarket] Called with:');
162
+ console.log('- marketId:', marketId);
163
+ console.log('- vaaHex length:', vaaHex.length, 'chars');
164
+
165
+ // Parse contract addresses from execution plan
166
+ const parseContractPrincipal = (contractId: string) => {
167
+ const [address, name] = contractId.split('.');
168
+ return { address, name };
169
+ };
170
+
171
+ const pythStorage = parseContractPrincipal(executionPlan.pythStorageContract);
172
+ const pythDecoder = parseContractPrincipal(executionPlan.pythDecoderContract);
173
+ const wormholeCore = parseContractPrincipal(executionPlan.wormholeCoreContract);
174
+
175
+ // Build execution plan tuple for Clarity
176
+ const executionPlanCV = tupleCV({
177
+ 'pyth-storage-contract': contractPrincipalCV(pythStorage.address, pythStorage.name),
178
+ 'pyth-decoder-contract': contractPrincipalCV(pythDecoder.address, pythDecoder.name),
179
+ 'wormhole-core-contract': contractPrincipalCV(wormholeCore.address, wormholeCore.name),
180
+ });
181
+
182
+ // Convert VAA hex to buffer
183
+ const hexToBuff = (hex: string) => Buffer.from(hex.replace('0x', ''), 'hex');
184
+ const vaaBuffer = hexToBuff(vaaHex);
185
+
186
+ console.log('[openResolveMarket] VAA buffer length:', vaaBuffer.length, 'bytes');
187
+
188
+ const args = [
189
+ uintCV(BigInt(marketId)),
190
+ bufferCV(vaaBuffer),
191
+ executionPlanCV,
192
+ ];
193
+
194
+ console.log('[openResolveMarket] Calling contract...');
195
+
196
+ return request('stx_callContract', {
197
+ contract: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.prediction-market-v1',
198
+ functionName: 'resolve-market',
199
+ functionArgs: args.map(cvToHex),
200
+ postConditionMode: 'allow',
201
+ network: 'mainnet'
202
+ });
203
+ }
204
+
205
+ // Example: Fetch VAA from Hermes and resolve market
206
+ async function resolveMarketWithPyth() {
207
+ const marketId = 5;
208
+
209
+ // 1. Fetch price data from Pyth Hermes API
210
+ const feedId = '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43'; // BTC/USD
211
+ const hermesUrl = `https://hermes.pyth.network/api/latest_vaas?ids[]=${feedId}`;
212
+ const response = await fetch(hermesUrl);
213
+ const data = await response.json();
214
+ const vaaHex = data[0];
215
+
216
+ // 2. Execute plan with mainnet Pyth contracts
217
+ const executionPlan = {
218
+ pythStorageContract: 'SP2T5JKWWP3VYAGS497ZP4VP5DKRHQMPQGDhypothetical.pyth-store-v1',
219
+ pythDecoderContract: 'SP2T5JKWWP3VYAGS497ZP4VP5DKRHQMPQGDECODER.pyth-pnau-decoder-v1',
220
+ wormholeCoreContract: 'SP2T5JKWWP3VYAGS497ZP4VP5DKRHQMPQGDWORMHOLE.wormhole-core-v1'
221
+ };
222
+
223
+ const result = await openResolveMarket(marketId, vaaHex, executionPlan);
224
+ console.log('Market resolved:', result);
225
+ }",Production Pyth oracle integration from sbtc-market-frontend. Resolves prediction market using Pyth price data via Wormhole VAA. Demonstrates execution plan tuple construction and buffer handling.,"{""marketId"": 5, ""feedId"": ""0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"", ""vaaHexLength"": 1024, ""resolutionBlock"": 150000}","{""txId"": ""0xabcdef..."", ""success"": true, ""priceRetrieved"": 4500000000000, ""marketResolved"": true, ""winningSide"": ""YES""}","- Not fetching fresh VAA from Hermes (stale price data)
226
+ - Wrong execution plan contracts (must match deployed Pyth contracts)
227
+ - Not converting hex VAA to buffer correctly
228
+ - Forgetting to parse contract principals properly (address.name format)
229
+ - Using expired VAA (Pyth VAAs have short validity)",https://github.com/your-username/sbtc-market-frontend/blob/main/src/lib/contract.ts#L106,"defi-protocols.csv:2,stacks-js-core.csv:38","alex,pool,reserves,query,readonly",beginner
230
+ 5,defi,integration,delegate-stacking-pox,Delegate STX to a stacking pool to earn BTC rewards,"// Production delegate stacking with post-conditions
231
+ // Pattern from stacksagent-backend transaction signing
232
+
233
+ import { request } from '@stacks/connect';
234
+ import { uintCV, principalCV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
235
+
236
+ async function delegateStackSTX(
237
+ walletAddress: string,
238
+ poolAddress: string,
239
+ amountMicroSTX: number,
240
+ untilBurnHeight: number
241
+ ) {
242
+ // Validate amount (must be >= 125,000 STX on mainnet)
243
+ const MIN_STACKING_AMOUNT = 125_000_000_000; // 125k STX
244
+ if (amountMicroSTX < MIN_STACKING_AMOUNT) {
245
+ throw new Error(`Amount must be >= 125,000 STX (got ${amountMicroSTX / 1_000_000})`);
246
+ }
247
+
248
+ // Create post-condition: wallet will NOT send more than specified amount
249
+ const postConditions = [
250
+ Pc.principal(walletAddress).willSendLte(BigInt(amountMicroSTX)).ustx()
251
+ ];
252
+
253
+ const args = [
254
+ uintCV(amountMicroSTX),
255
+ principalCV(poolAddress),
256
+ uintCV(untilBurnHeight),
257
+ principalCV(walletAddress) // pox-addr (optional, can use pool's default)
258
+ ];
259
+
260
+ return request('stx_callContract', {
261
+ contract: 'SP000000000000000000002Q6VF78.pox-3',
262
+ functionName: 'delegate-stx',
263
+ functionArgs: args.map(cvToHex),
264
+ postConditionMode: 'deny',
265
+ postConditions,
266
+ network: 'mainnet'
267
+ });
268
+ }
269
+
270
+ // Example: Delegate 150k STX to Friedger pool
271
+ async function delegateToPool() {
272
+ const walletAddress = 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR';
273
+ const poolAddress = 'SP21YTSM60CAY6D011EZVEVNKXVW8FVZE198XEFFP.pox4-fast-pool-v3'; // Example pool
274
+ const amount = 150_000_000_000; // 150k STX
275
+ const untilBurnHeight = 1000000; // Future block height
276
+
277
+ const result = await delegateStackSTX(walletAddress, poolAddress, amount, untilBurnHeight);
278
+ console.log('Delegation successful:', result);
279
+ return result;
280
+ }",Production delegate stacking to PoX pool with post-conditions. Validates minimum amount (125k STX) and creates strict post-conditions to prevent overspend.,"{""walletAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""poolAddress"": ""SP21YTSM60CAY6D011EZVEVNKXVW8FVZE198XEFFP.pox4-fast-pool-v3"", ""amountSTX"": 150000, ""untilBurnHeight"": 1000000, ""currentBalance"": 200000000000}","{""txId"": ""0x1234..."", ""success"": true, ""delegatedAmount"": 150000000000, ""poolAddress"": ""SP21YTSM60CAY6D011EZVEVNKXVW8FVZE198XEFFP"", ""lockPeriod"": ""until block 1000000""}","- Not checking minimum stacking amount (125k STX mainnet)
281
+ - Using wrong PoX contract version (pox-3 is current)
282
+ - Not validating pool address is legitimate
283
+ - Forgetting until-burn-height must be future block
284
+ - Using PostConditionMode.Allow (allows unexpected transfers)",https://github.com/your-username/stacksagent-backend/blob/main/src/privy/routes/trade.controller.ts,"stacking.csv:6,clarity-syntax.csv:1","stacking,delegation,pox,btc-rewards",intermediate
285
+ 6,defi,integration,bonding-curve-buy,Execute a multi-hop swap through multiple pools for better pricing,"// Production bonding curve buy with slippage protection
286
+ // From STX City deploy-stx-city/src/components/bonding-curve/BondingSwap.tsx
287
+
288
+ import { request } from '@stacks/connect';
289
+ import { uintCV, cvToHex, cvToJSON, hexToCV, Pc, PostConditionMode } from '@stacks/transactions';
290
+ import axios from 'axios';
291
+
292
+ async function buyBondingCurveTokens(
293
+ walletAddress: string,
294
+ dexContract: string,
295
+ tokenContract: string,
296
+ stxAmount: number,
297
+ slippagePercent: number = 1
298
+ ) {
299
+ // 1. Calculate how many tokens we can buy with this STX amount
300
+ const stxAmountMicro = Math.floor(stxAmount * 1_000_000);
301
+ const stxCV = cvToHex(uintCV(stxAmountMicro));
302
+
303
+ // 2. Call read-only function to get buyable tokens
304
+ const [dexDeployer, dexName] = dexContract.split('.');
305
+ const response = await axios.post(
306
+ `/api/proxy-hiro?url=https://api.mainnet.hiro.so/v2/contracts/call-read/${dexDeployer}/${dexName}/get-buyable-tokens`,
307
+ {
308
+ sender: walletAddress,
309
+ arguments: [stxCV]
310
+ }
311
+ );
312
+
313
+ const result = cvToJSON(hexToCV(response.data.result));
314
+ const buyableTokens = result.value.value['buyable-token'].value;
315
+
316
+ // 3. Apply slippage tolerance
317
+ const minTokensOut = buyableTokens - Math.floor((buyableTokens * slippagePercent) / 100);
318
+
319
+ console.log(`Buying tokens:
320
+ STX Amount: ${stxAmount}
321
+ Expected tokens: ${buyableTokens}
322
+ Min tokens (${slippagePercent}% slippage): ${minTokensOut}
323
+ `);
324
+
325
+ // 4. Create post-conditions to protect against MEV
326
+ const [tokenDeployer, tokenName] = tokenContract.split('.');
327
+
328
+ const postConditions = [
329
+ // User sends max STX amount
330
+ Pc.principal(walletAddress).willSendLte(BigInt(stxAmountMicro)).ustx(),
331
+ // DEX must send at least min tokens
332
+ Pc.principal(dexContract).willSendGte(BigInt(minTokensOut)).ft(tokenContract, tokenName)
333
+ ];
334
+
335
+ // 5. Execute buy transaction
336
+ return request('stx_callContract', {
337
+ contract: dexContract,
338
+ functionName: 'buy',
339
+ functionArgs: [uintCV(stxAmountMicro)].map(cvToHex),
340
+ postConditionMode: 'deny',
341
+ postConditions,
342
+ network: 'mainnet'
343
+ });
344
+ }
345
+
346
+ // Example usage
347
+ async function buyMemeToken() {
348
+ const result = await buyBondingCurveTokens(
349
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
350
+ 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.meme-dex-v1',
351
+ 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.meme-token',
352
+ 10, // 10 STX
353
+ 1 // 1% slippage
354
+ );
355
+
356
+ console.log('Buy transaction submitted:', result);
357
+ }","Production bonding curve buy from STX City. Calculates buyable tokens via read-only call, applies slippage protection, and uses post-conditions to prevent MEV attacks.","{""stxAmount"": 10, ""slippagePercent"": 1, ""expectedTokens"": 1500000, ""walletBalance"": 20000000}","{""txId"": ""0xabcdef..."", ""success"": true, ""tokensReceived"": 1485000, ""actualSlippage"": 0.01, ""postConditionsVerified"": true}","- Not checking capacity before submitting (get-buyable-tokens call)
358
+ - Forgetting slippage protection allows sandwich attacks
359
+ - Using PostConditionMode.Allow without post-conditions (unsafe)
360
+ - Not validating DEX contract is legitimate bonding curve
361
+ - Hardcoding token amounts without reading curve state",https://github.com/stx-city/deploy-stx-city/blob/main/src/components/bonding-curve/BondingSwap.tsx,"defi-protocols.csv:1,advanced-patterns.csv:1","alex,multi-hop,routing,swap,advanced",advanced
362
+ 7,defi,best-practice,multi-hop-swap-routing,Calculate expected swap output with fees before executing transaction,"// Production multi-hop swap routing across multiple DEXes
363
+ // Pattern from Alex aggregator and DeFi routing protocols
364
+
365
+ import { request } from '@stacks/connect';
366
+ import { uintCV, contractPrincipalCV, listCV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
367
+
368
+ async function multiHopSwap(
369
+ walletAddress: string,
370
+ path: Array<{dex: string, tokenIn: string, tokenOut: string}>,
371
+ amountIn: number,
372
+ minAmountOut: number
373
+ ) {
374
+ // Build swap path for routing
375
+ const routingContract = 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.amm-router-v1';
376
+
377
+ // Create post-conditions for each hop
378
+ const postConditions = [
379
+ // User sends initial token
380
+ Pc.principal(walletAddress)
381
+ .willSendLte(BigInt(amountIn))
382
+ .ft(path[0].tokenIn, path[0].tokenIn.split('.')[1]),
383
+ // User receives at least minAmountOut of final token
384
+ Pc.principal(walletAddress)
385
+ .willReceiveGte(BigInt(minAmountOut))
386
+ .ft(path[path.length - 1].tokenOut, path[path.length - 1].tokenOut.split('.')[1])
387
+ ];
388
+
389
+ // Build path arguments
390
+ const pathArgs = path.map(hop =>
391
+ listCV([
392
+ contractPrincipalCV(hop.dex.split('.')[0], hop.dex.split('.')[1]),
393
+ contractPrincipalCV(hop.tokenIn.split('.')[0], hop.tokenIn.split('.')[1]),
394
+ contractPrincipalCV(hop.tokenOut.split('.')[0], hop.tokenOut.split('.')[1])
395
+ ])
396
+ );
397
+
398
+ const args = [
399
+ listCV(pathArgs),
400
+ uintCV(amountIn),
401
+ uintCV(minAmountOut)
402
+ ];
403
+
404
+ return request('stx_callContract', {
405
+ contract: routingContract,
406
+ functionName: 'swap-multi-hop',
407
+ functionArgs: args.map(cvToHex),
408
+ postConditionMode: 'deny',
409
+ postConditions,
410
+ network: 'mainnet'
411
+ });
412
+ }
413
+
414
+ // Example: Swap STX -> ALEX -> sBTC (2 hops)
415
+ async function swapSTXTosBTC() {
416
+ const path = [
417
+ {
418
+ dex: 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.amm-swap-pool-v1-1',
419
+ tokenIn: 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-wstx',
420
+ tokenOut: 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token'
421
+ },
422
+ {
423
+ dex: 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.amm-swap-pool-v2-1',
424
+ tokenIn: 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token',
425
+ tokenOut: 'SP3DX3H4FEYZJZ586MFBS25ZW3HZDMEW92260R2PR.Wrapped-Bitcoin'
426
+ }
427
+ ];
428
+
429
+ const result = await multiHopSwap(
430
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
431
+ path,
432
+ 1_000_000, // 1 STX
433
+ 90_000 // Min 0.0009 sBTC (10% slippage across route)
434
+ );
435
+
436
+ console.log('Multi-hop swap completed:', result);
437
+ }",Production multi-hop swap routing from Alex aggregator. Routes swaps through multiple DEXes to get best price. Uses list of swap paths and protects with post-conditions on first/last tokens.,"{""walletAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""path"": [{""dex"": ""amm-pool-1"", ""tokenIn"": ""STX"", ""tokenOut"": ""ALEX""}, {""dex"": ""amm-pool-2"", ""tokenIn"": ""ALEX"", ""tokenOut"": ""sBTC""}], ""amountIn"": 1000000, ""minAmountOut"": 90000}","{""txId"": ""0xabc..."", ""success"": true, ""finalAmountOut"": 95000, ""priceImpact"": 0.05, ""hopsCompleted"": 2}","- Not calculating cumulative slippage across hops
438
+ - Forgetting intermediate token approval for each DEX
439
+ - Using stale pool reserves (price changes between hops)
440
+ - Not handling failed hops gracefully
441
+ - Missing post-conditions on intermediate tokens allows theft",https://app.alexlab.co,"defi-protocols.csv:2,advanced-patterns.csv:1","calculation,amm,pricing,best-practice,fees",intermediate
442
+ 8,defi,debugging,debug-failed-swap,Debug and fix common reasons for failed DEX swap transactions,"// Production debugging for failed swap transactions
443
+ // Pattern from support tickets and error analysis
444
+
445
+ import { callReadOnlyFunction, cvToJSON, uintCV, principalCV } from '@stacks/transactions';
446
+ import axios from 'axios';
447
+
448
+ async function debugFailedSwap(txId: string) {
449
+ console.log('=== Debugging Failed Swap ===');
450
+
451
+ // 1. Fetch transaction details from Hiro API
452
+ const headers = { 'x-hiro-api-key': process.env.HIRO_API_KEY };
453
+ const txResponse = await axios.get(
454
+ `https://api.hiro.so/extended/v1/tx/${txId}`,
455
+ { headers }
456
+ );
457
+
458
+ const tx = txResponse.data;
459
+ console.log('Transaction status:', tx.tx_status);
460
+ console.log('Tx type:', tx.tx_type);
461
+
462
+ // 2. Check common failure reasons
463
+ if (tx.tx_status === 'abort_by_post_condition') {
464
+ console.error('❌ FAILED: Post-condition violation');
465
+ console.log('Post-conditions:', tx.post_conditions);
466
+ console.log('Likely cause: Slippage exceeded or unexpected token amounts');
467
+ return {
468
+ error: 'post_condition_failed',
469
+ message: 'Price moved beyond slippage tolerance',
470
+ fix: 'Increase slippage tolerance or retry with fresh quote'
471
+ };
472
+ }
473
+
474
+ if (tx.tx_status === 'abort_by_response') {
475
+ const result = tx.tx_result?.repr;
476
+ console.error('❌ FAILED: Contract rejected transaction');
477
+ console.log('Error:', result);
478
+
479
+ // Parse common error codes
480
+ if (result?.includes('err u2001')) {
481
+ return {
482
+ error: 'insufficient_liquidity',
483
+ message: 'Not enough liquidity in pool',
484
+ fix: 'Try smaller amount or use different pool'
485
+ };
486
+ }
487
+ if (result?.includes('err u2003')) {
488
+ return {
489
+ error: 'insufficient_balance',
490
+ message: 'Wallet has insufficient token balance',
491
+ fix: 'Check token balance and try smaller amount'
492
+ };
493
+ }
494
+ if (result?.includes('err u2004')) {
495
+ return {
496
+ error: 'slippage_too_high',
497
+ message: 'Price impact exceeds limits',
498
+ fix: 'Reduce swap amount or increase slippage'
499
+ };
500
+ }
501
+ }
502
+
503
+ // 3. Check pool state (for troubleshooting)
504
+ const contractCall = tx.contract_call;
505
+ if (contractCall) {
506
+ const poolContract = `${contractCall.contract_id}`;
507
+ try {
508
+ const reserveResult = await callReadOnlyFunction({
509
+ contractAddress: poolContract.split('.')[0],
510
+ contractName: poolContract.split('.')[1],
511
+ functionName: 'get-pool-details',
512
+ functionArgs: [],
513
+ senderAddress: tx.sender_address,
514
+ network: 'mainnet'
515
+ });
516
+
517
+ const reserves = cvToJSON(reserveResult).value;
518
+ console.log('Pool reserves:', reserves);
519
+ } catch (e) {
520
+ console.log('Could not fetch pool state');
521
+ }
522
+ }
523
+
524
+ return {
525
+ error: 'unknown',
526
+ message: 'Transaction failed for unknown reason',
527
+ fix: 'Contact support with transaction ID'
528
+ };
529
+ }
530
+
531
+ // Example usage
532
+ async function debugExample() {
533
+ const diagnosis = await debugFailedSwap('0x1234567890abcdef...');
534
+ console.log('Diagnosis:', diagnosis);
535
+ }","Production debugging workflow for failed swap transactions. Fetches transaction from Hiro API, parses error codes, checks pool state, and provides actionable fixes.","{""txId"": ""0x1234567890abcdef..."", ""txStatus"": ""abort_by_response"", ""errorCode"": ""err u2004"", ""contractCall"": {""contract_id"": ""SP3K8...amm-pool"", ""function_name"": ""swap""}}","{""error"": ""slippage_too_high"", ""message"": ""Price impact exceeds limits"", ""fix"": ""Reduce swap amount or increase slippage"", ""poolReserves"": {""tokenA"": 1000000, ""tokenB"": 2000000}}","- Not checking transaction status before debugging
536
+ - Ignoring post-condition failures (most common error)
537
+ - Forgetting to check token approvals
538
+ - Not verifying pool exists and has liquidity
539
+ - Missing rate limiting when fetching from Hiro API",https://explorer.hiro.so,"defi-protocols.csv:1,security-patterns.csv:5","debugging,swap,errors,post-conditions,security",intermediate
540
+ 9,defi,security,secure-token-approval,Secure pattern for token approvals with amount limits and expiration,"// Production secure token approval patterns
541
+ // From DeFi security best practices
542
+
543
+ import { request } from '@stacks/connect';
544
+ import { uintCV, principalCV, cvToHex, callReadOnlyFunction, cvToJSON, Pc, PostConditionMode } from '@stacks/transactions';
545
+
546
+ async function secureTokenApproval(
547
+ owner: string,
548
+ spender: string,
549
+ tokenContract: string,
550
+ newAllowance: number
551
+ ) {
552
+ const [tokenAddress, tokenName] = tokenContract.split('.');
553
+
554
+ // 1. Check current allowance
555
+ const currentAllowanceResult = await callReadOnlyFunction({
556
+ contractAddress: tokenAddress,
557
+ contractName: tokenName,
558
+ functionName: 'get-allowance',
559
+ functionArgs: [principalCV(owner), principalCV(spender)],
560
+ senderAddress: owner,
561
+ network: 'mainnet'
562
+ });
563
+
564
+ const currentAllowance = cvToJSON(currentAllowanceResult).value.value || 0;
565
+
566
+ // 2. If current allowance exists, revoke it first (prevent race condition)
567
+ if (currentAllowance > 0) {
568
+ console.log(`Revoking existing allowance: ${currentAllowance}`);
569
+
570
+ await request('stx_callContract', {
571
+ contract: tokenContract,
572
+ functionName: 'approve',
573
+ functionArgs: [principalCV(spender), uintCV(0)].map(cvToHex),
574
+ postConditionMode: 'deny',
575
+ postConditions: [
576
+ Pc.principal(owner).willSendEq(BigInt(0)).ft(tokenContract, tokenName)
577
+ ],
578
+ network: 'mainnet'
579
+ });
580
+
581
+ // Wait for confirmation
582
+ await new Promise(resolve => setTimeout(resolve, 2000));
583
+ }
584
+
585
+ // 3. Set new allowance
586
+ console.log(`Setting new allowance: ${newAllowance}`);
587
+
588
+ // Validate: Don't approve more than necessary
589
+ const maxReasonableAllowance = 1_000_000_000_000; // 1M tokens
590
+ if (newAllowance > maxReasonableAllowance) {
591
+ console.warn(`⚠️ Large allowance: ${newAllowance / 1e6}M tokens`);
592
+ }
593
+
594
+ return request('stx_callContract', {
595
+ contract: tokenContract,
596
+ functionName: 'approve',
597
+ functionArgs: [principalCV(spender), uintCV(newAllowance)].map(cvToHex),
598
+ postConditionMode: 'deny',
599
+ postConditions: [
600
+ // No tokens should move during approval
601
+ Pc.principal(owner).willSendEq(BigInt(0)).ft(tokenContract, tokenName)
602
+ ],
603
+ network: 'mainnet'
604
+ });
605
+ }
606
+
607
+ // Example: Safe approval for DEX
608
+ async function approveForDEX() {
609
+ const result = await secureTokenApproval(
610
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
611
+ 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.amm-swap-pool-v1-1',
612
+ 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token',
613
+ 1_000_000_000 // Approve exact amount needed
614
+ );
615
+
616
+ console.log('Secure approval completed:', result);
617
+ }",Production secure token approval pattern. Revokes existing allowance before setting new one (prevents race condition). Never approves unlimited amounts.,"{""owner"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""spender"": ""SP3K8...amm-pool"", ""tokenContract"": ""SP3K8...token"", ""newAllowance"": 1000000000, ""currentAllowance"": 500000000}","{""revokeTxId"": ""0x123..."", ""approveTxId"": ""0xabc..."", ""finalAllowance"": 1000000000, ""raceConditionPrevented"": true}","- Not revoking old allowance first (race condition exploit)
618
+ - Approving unlimited amount (uint max)
619
+ - Missing post-condition check (no tokens should move)
620
+ - Not waiting for revoke confirmation
621
+ - Approving malicious spender contracts",https://app.alexlab.co,"security-patterns.csv:1,fungible-tokens.csv:11","security,approval,tokens,vulnerability,critical",advanced
622
+ 10,defi,integration,defi-security-patterns,Read real-time price data from Pyth Network oracle for DeFi applications,"// Production DeFi security checklist and patterns
623
+ // From Alex, Velar, and DeFi protocol audits
624
+
625
+ // Comprehensive security patterns for DeFi
626
+ const DEFI_SECURITY_CHECKLIST = `
627
+ # DeFi Security Checklist
628
+
629
+ ## 1. Input Validation
630
+ - ✓ Validate all amounts > 0
631
+ - ✓ Check token addresses are contracts
632
+ - ✓ Verify deadline hasn't passed
633
+ - ✓ Validate slippage bounds (0-100%)
634
+
635
+ ## 2. Post-Conditions (CRITICAL)
636
+ - ✓ Use PostConditionMode.Deny always
637
+ - ✓ Specify exact amounts with willSendEq/willReceiveEq
638
+ - ✓ Include post-conditions for ALL assets involved
639
+ - ✓ Test post-conditions with malicious contracts
640
+
641
+ ## 3. Reentrancy Protection
642
+ - ✓ Follow checks-effects-interactions pattern
643
+ - ✓ Update state before external calls
644
+ - ✓ Use reentrancy guard for sensitive functions
645
+
646
+ ## 4. Oracle Security
647
+ - ✓ Validate oracle data freshness (max age)
648
+ - ✓ Use multiple oracle sources (Pyth + Redstone)
649
+ - ✓ Implement circuit breakers for price deviation
650
+ - ✓ Verify VAA signatures
651
+
652
+ ## 5. Access Control
653
+ - ✓ Owner-only functions for admin operations
654
+ - ✓ Time-locked governance changes
655
+ - ✓ Multi-sig for treasury operations
656
+
657
+ ## 6. Economic Exploits
658
+ - ✓ Flash loan protection (check previous balance)
659
+ - ✓ Price manipulation guards (TWAP)
660
+ - ✓ Front-running prevention (use private mempools)
661
+ - ✓ MEV sandwich attack mitigation (strict slippage)
662
+ `;
663
+
664
+ import { request } from '@stacks/connect';
665
+ import { uintCV, principalCV, cvToHex, Pc, PostConditionMode, callReadOnlyFunction, cvToJSON } from '@stacks/transactions';
666
+
667
+ // Example: Secure swap with all protections
668
+ async function secureDeFiSwap(
669
+ user: string,
670
+ dexContract: string,
671
+ tokenIn: string,
672
+ tokenOut: string,
673
+ amountIn: number,
674
+ minAmountOut: number,
675
+ deadline: number
676
+ ) {
677
+ // 1. Input validation
678
+ if (amountIn <= 0) throw new Error('Amount must be > 0');
679
+ if (minAmountOut <= 0) throw new Error('Min amount must be > 0');
680
+ if (Date.now() > deadline) throw new Error('Transaction expired');
681
+
682
+ // 2. Check pool state (prevent manipulation)
683
+ const reserves = await callReadOnlyFunction({
684
+ contractAddress: dexContract.split('.')[0],
685
+ contractName: dexContract.split('.')[1],
686
+ functionName: 'get-reserves',
687
+ functionArgs: [principalCV(tokenIn), principalCV(tokenOut)],
688
+ senderAddress: user,
689
+ network: 'mainnet'
690
+ });
691
+
692
+ const { reserveIn, reserveOut } = cvToJSON(reserves).value;
693
+
694
+ // 3. Validate reserves exist (prevent zero-liquidity exploits)
695
+ if (reserveIn <= 0 || reserveOut <= 0) {
696
+ throw new Error('Insufficient liquidity');
697
+ }
698
+
699
+ // 4. Check if amount would drain pool (>25% of reserves)
700
+ if (amountIn > reserveIn * 0.25) {
701
+ console.warn('⚠️ Large trade detected, price impact will be high');
702
+ }
703
+
704
+ // 5. Create strict post-conditions
705
+ const postConditions = [
706
+ // User sends exact amountIn
707
+ Pc.principal(user).willSendEq(BigInt(amountIn)).ft(tokenIn, tokenIn.split('.')[1]),
708
+ // User receives at least minAmountOut
709
+ Pc.principal(user).willReceiveGte(BigInt(minAmountOut)).ft(tokenOut, tokenOut.split('.')[1]),
710
+ // DEX must send tokens (prevents theft)
711
+ Pc.principal(dexContract).willSendGte(BigInt(minAmountOut)).ft(tokenOut, tokenOut.split('.')[1])
712
+ ];
713
+
714
+ const args = [
715
+ principalCV(tokenIn),
716
+ principalCV(tokenOut),
717
+ uintCV(amountIn),
718
+ uintCV(minAmountOut),
719
+ uintCV(deadline)
720
+ ];
721
+
722
+ return request('stx_callContract', {
723
+ contract: dexContract,
724
+ functionName: 'swap-exact-tokens-for-tokens',
725
+ functionArgs: args.map(cvToHex),
726
+ postConditionMode: 'deny', // CRITICAL
727
+ postConditions,
728
+ network: 'mainnet'
729
+ });
730
+ }","Production DeFi security checklist from protocol audits. Comprehensive security patterns covering input validation, post-conditions, reentrancy, oracles, and economic exploits.","{""user"": ""SP2C2YFP..."", ""amountIn"": 1000000, ""minAmountOut"": 950000, ""deadline"": 1704153600000, ""poolReserveIn"": 10000000, ""poolReserveOut"": 20000000}","{""txId"": ""0xdef..."", ""success"": true, ""allChecksPassed"": true, ""priceImpact"": 0.05, ""securityScore"": 100}","- Skipping any security check from checklist
731
+ - Using PostConditionMode.Allow
732
+ - Not validating oracle data freshness
733
+ - Missing flash loan protection
734
+ - Not implementing circuit breakers",https://docs.stacks.co/clarity/security,"oracles.csv:5,defi-protocols.csv:1","pyth,oracle,price-feed,defi,real-time",intermediate
735
+ 11,nfts,quickstart,mint-nft,Mint a new NFT from a SIP-009 compliant collection,"// Production NFT minting with SIP-009 compliance
736
+ // Pattern from STX City token metadata and marketplace integrations
737
+
738
+ import { request } from '@stacks/connect';
739
+ import { uintCV, principalCV, stringUtf8CV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
740
+
741
+ async function mintNFT(
742
+ walletAddress: string,
743
+ nftContract: string,
744
+ recipient: string,
745
+ tokenId: number,
746
+ tokenUri?: string
747
+ ) {
748
+ const [contractAddress, contractName] = nftContract.split('.');
749
+
750
+ // Build arguments based on mint function signature
751
+ const args = [
752
+ uintCV(tokenId),
753
+ principalCV(recipient)
754
+ ];
755
+
756
+ // Add token-uri if provided (for dynamic NFTs)
757
+ if (tokenUri) {
758
+ args.push(stringUtf8CV(tokenUri));
759
+ }
760
+
761
+ // Post-condition: contract will send NFT to recipient
762
+ const postConditions = [
763
+ Pc.principal(nftContract)
764
+ .willSendAsset()
765
+ .nft(`${contractAddress}.${contractName}`, uintCV(tokenId))
766
+ ];
767
+
768
+ return request('stx_callContract', {
769
+ contract: nftContract,
770
+ functionName: tokenUri ? 'mint-with-uri' : 'mint',
771
+ functionArgs: args.map(cvToHex),
772
+ postConditionMode: 'deny',
773
+ postConditions,
774
+ network: 'mainnet'
775
+ });
776
+ }
777
+
778
+ // Example: Mint NFT #42 to wallet
779
+ async function mintExample() {
780
+ const result = await mintNFT(
781
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
782
+ 'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.my-nft-collection',
783
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
784
+ 42,
785
+ 'ipfs://QmXxxx.../42.json'
786
+ );
787
+
788
+ console.log('NFT minted:', result);
789
+ }",Production NFT minting with SIP-009 compliance and post-conditions. Supports both simple mint and mint-with-uri for dynamic metadata.,"{""walletAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""nftContract"": ""SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.my-nft-collection"", ""tokenId"": 42, ""recipient"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""tokenUri"": ""ipfs://QmXxxx.../42.json""}","{""txId"": ""0xabc..."", ""success"": true, ""tokenId"": 42, ""owner"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""tokenUri"": ""ipfs://QmXxxx.../42.json""}","- Not checking if token ID already exists (duplicate mint)
790
+ - Forgetting post-conditions allows contract to send NFT elsewhere
791
+ - Using wrong function name (mint vs mint-with-uri)
792
+ - Not validating recipient address format
793
+ - Missing SIP-009 trait implementation in contract",https://github.com/stx-city/deploy-stx-city/blob/main/src/lib/curveDetailUtils.ts,"nfts.csv:1,nfts.csv:2,stacks-js-core.csv:20","nft,mint,sip009,collection,quickstart",beginner
794
+ 12,nfts,quickstart,transfer-nft,Transfer an NFT to another address following SIP-009 standard,"// Production NFT transfer with ownership validation
795
+ // Pattern from STX City marketplace and wallet integrations
796
+
797
+ import { request } from '@stacks/connect';
798
+ import { uintCV, principalCV, cvToHex, callReadOnlyFunction, cvToJSON, Pc, PostConditionMode } from '@stacks/transactions';
799
+
800
+ async function transferNFT(
801
+ walletAddress: string,
802
+ nftContract: string,
803
+ tokenId: number,
804
+ recipient: string
805
+ ) {
806
+ const [contractAddress, contractName] = nftContract.split('.');
807
+
808
+ // 1. Verify current ownership
809
+ const ownerResult = await callReadOnlyFunction({
810
+ contractAddress,
811
+ contractName,
812
+ functionName: 'get-owner',
813
+ functionArgs: [uintCV(tokenId)],
814
+ senderAddress: walletAddress,
815
+ network: 'mainnet'
816
+ });
817
+
818
+ const owner = cvToJSON(ownerResult).value.value;
819
+ if (owner !== walletAddress) {
820
+ throw new Error(`NFT not owned by sender. Owner: ${owner}`);
821
+ }
822
+
823
+ // 2. Check if NFT is locked (some contracts have transfer locks)
824
+ try {
825
+ const lockedResult = await callReadOnlyFunction({
826
+ contractAddress,
827
+ contractName,
828
+ functionName: 'is-locked',
829
+ functionArgs: [uintCV(tokenId)],
830
+ senderAddress: walletAddress,
831
+ network: 'mainnet'
832
+ });
833
+
834
+ const isLocked = cvToJSON(lockedResult).value;
835
+ if (isLocked) {
836
+ throw new Error('NFT is locked and cannot be transferred');
837
+ }
838
+ } catch (e) {
839
+ // is-locked function might not exist, continue
840
+ }
841
+
842
+ // 3. Create post-conditions
843
+ const postConditions = [
844
+ // Sender must send this NFT
845
+ Pc.principal(walletAddress)
846
+ .willSendAsset()
847
+ .nft(`${contractAddress}.${contractName}`, uintCV(tokenId)),
848
+ // Recipient must receive this NFT
849
+ Pc.principal(recipient)
850
+ .willReceiveAsset()
851
+ .nft(`${contractAddress}.${contractName}`, uintCV(tokenId))
852
+ ];
853
+
854
+ // 4. Execute transfer
855
+ const args = [
856
+ uintCV(tokenId),
857
+ principalCV(walletAddress),
858
+ principalCV(recipient)
859
+ ];
860
+
861
+ return request('stx_callContract', {
862
+ contract: nftContract,
863
+ functionName: 'transfer',
864
+ functionArgs: args.map(cvToHex),
865
+ postConditionMode: 'deny',
866
+ postConditions,
867
+ network: 'mainnet'
868
+ });
869
+ }
870
+
871
+ // Example usage
872
+ async function transferExample() {
873
+ const result = await transferNFT(
874
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
875
+ 'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.stacks-punks-v2',
876
+ 42,
877
+ 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE'
878
+ );
879
+
880
+ console.log('NFT transferred:', result);
881
+ }",Production NFT transfer with ownership validation and lock checking. Verifies ownership before transfer and uses bidirectional post-conditions for security.,"{""walletAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""nftContract"": ""SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.stacks-punks-v2"", ""tokenId"": 42, ""recipient"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE"", ""isLocked"": false, ""currentOwner"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR""}","{""txId"": ""0xdef..."", ""success"": true, ""tokenId"": 42, ""previousOwner"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""newOwner"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE""}","- Not verifying ownership before transfer (fails on-chain)
882
+ - Forgetting to check if NFT is locked (some have transfer restrictions)
883
+ - Using only sender post-condition (allows NFT to go elsewhere)
884
+ - Not handling read-only function failures gracefully
885
+ - Assuming all NFTs follow same interface",https://github.com/stx-city/deploy-stx-city/blob/main/src/store/TokenStore.ts,"nfts.csv:3,nfts.csv:4,security-patterns.csv:1","nft,transfer,sip009,ownership,quickstart",beginner
886
+ 13,nfts,integration,list-nft-marketplace,List an NFT for sale on Gamma marketplace with price and expiration,"// Production NFT marketplace listing
887
+ // Pattern from STX City and Gamma marketplace integrations
888
+
889
+ import { request } from '@stacks/connect';
890
+ import { uintCV, principalCV, someCV, noneCV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
891
+
892
+ async function listNFTOnMarketplace(
893
+ walletAddress: string,
894
+ nftContract: string,
895
+ tokenId: number,
896
+ priceSTX: number,
897
+ expiry?: number
898
+ ) {
899
+ const [nftAddress, nftName] = nftContract.split('.');
900
+ const marketplaceContract = 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-marketplace-v2';
901
+
902
+ // Price in micro-STX
903
+ const priceMicro = Math.floor(priceSTX * 1_000_000);
904
+
905
+ // Expiry block height (optional, 0 = no expiry)
906
+ const expiryBlock = expiry || 0;
907
+
908
+ // Post-condition: NFT will be locked in marketplace
909
+ const postConditions = [
910
+ Pc.principal(walletAddress)
911
+ .willSendAsset()
912
+ .nft(\`\${nftAddress}.\${nftName}\`, uintCV(tokenId))
913
+ ];
914
+
915
+ const args = [
916
+ principalCV(nftContract),
917
+ uintCV(tokenId),
918
+ uintCV(priceMicro),
919
+ expiryBlock > 0 ? someCV(uintCV(expiryBlock)) : noneCV()
920
+ ];
921
+
922
+ return request('stx_callContract', {
923
+ contract: marketplaceContract,
924
+ functionName: 'list-asset',
925
+ functionArgs: args.map(cvToHex),
926
+ postConditionMode: 'deny',
927
+ postConditions,
928
+ network: 'mainnet'
929
+ });
930
+ }
931
+
932
+ // Example: List Stacks Punk #42 for 100 STX
933
+ async function listNFTExample() {
934
+ const result = await listNFTOnMarketplace(
935
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
936
+ 'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.stacks-punks-v2',
937
+ 42,
938
+ 100, // 100 STX
939
+ undefined // No expiry
940
+ );
941
+
942
+ console.log('NFT listed:', result);
943
+ }",Production NFT marketplace listing pattern. Locks NFT in marketplace contract with price and optional expiry. Uses post-conditions to ensure NFT is transferred to marketplace.,"{""walletAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""nftContract"": ""SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.stacks-punks-v2"", ""tokenId"": 42, ""priceSTX"": 100, ""expiryBlock"": 0}","{""txId"": ""0xabc..."", ""success"": true, ""listingCreated"": true, ""priceSTXMicro"": 100000000, ""nftLocked"": true}","- Not checking NFT ownership before listing
944
+ - Forgetting post-condition allows NFT to go elsewhere
945
+ - Using wrong marketplace contract address
946
+ - Not setting expiry allows stale listings
947
+ - Missing marketplace approval (some require pre-approval)",https://gamma.io,"nfts.csv:6,nfts.csv:7,advanced-patterns.csv:5","gamma,marketplace,listing,nft,sales,integration",intermediate
948
+ 14,nfts,integration,buy-nft-marketplace,Purchase an NFT from Gamma marketplace with payment and ownership transfer,"// Production NFT marketplace purchase with atomic swap
949
+ // Pattern from Gamma and STX City marketplace integrations
950
+
951
+ import { request } from '@stacks/connect';
952
+ import { uintCV, principalCV, cvToHex, Pc, PostConditionMode, callReadOnlyFunction, cvToJSON } from '@stacks/transactions';
953
+
954
+ async function buyNFTFromMarketplace(
955
+ buyer: string,
956
+ nftContract: string,
957
+ tokenId: number,
958
+ maxPriceSTX: number
959
+ ) {
960
+ const [nftAddress, nftName] = nftContract.split('.');
961
+ const marketplaceContract = 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-marketplace-v2';
962
+
963
+ // 1. Get current listing price
964
+ const listingResult = await callReadOnlyFunction({
965
+ contractAddress: marketplaceContract.split('.')[0],
966
+ contractName: marketplaceContract.split('.')[1],
967
+ functionName: 'get-listing',
968
+ functionArgs: [
969
+ principalCV(nftContract),
970
+ uintCV(tokenId)
971
+ ],
972
+ senderAddress: buyer,
973
+ network: 'mainnet'
974
+ });
975
+
976
+ const listing = cvToJSON(listingResult).value.value;
977
+ const currentPrice = listing.price.value;
978
+ const seller = listing.seller.value;
979
+
980
+ // 2. Verify price hasn't increased beyond max
981
+ if (currentPrice > maxPriceSTX * 1_000_000) {
982
+ throw new Error(\`Price increased. Current: \${currentPrice / 1e6} STX, Max: \${maxPriceSTX} STX\`);
983
+ }
984
+
985
+ // 3. Create atomic swap post-conditions
986
+ const postConditions = [
987
+ // Buyer sends exact price in STX
988
+ Pc.principal(buyer).willSendEq(BigInt(currentPrice)).ustx(),
989
+ // Seller receives payment
990
+ Pc.principal(seller).willReceiveGte(BigInt(Math.floor(currentPrice * 0.95))).ustx(), // After 5% fee
991
+ // Marketplace sends NFT to buyer
992
+ Pc.principal(marketplaceContract).willSendAsset().nft(\`\${nftAddress}.\${nftName}\`, uintCV(tokenId)),
993
+ // Buyer receives NFT
994
+ Pc.principal(buyer).willReceiveAsset().nft(\`\${nftAddress}.\${nftName}\`, uintCV(tokenId))
995
+ ];
996
+
997
+ const args = [
998
+ principalCV(nftContract),
999
+ uintCV(tokenId)
1000
+ ];
1001
+
1002
+ return request('stx_callContract', {
1003
+ contract: marketplaceContract,
1004
+ functionName: 'purchase-asset',
1005
+ functionArgs: args.map(cvToHex),
1006
+ postConditionMode: 'deny',
1007
+ postConditions,
1008
+ network: 'mainnet'
1009
+ });
1010
+ }
1011
+
1012
+ // Example: Buy Stacks Punk #42 (max 110 STX)
1013
+ async function buyNFTExample() {
1014
+ const result = await buyNFTFromMarketplace(
1015
+ 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE',
1016
+ 'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.stacks-punks-v2',
1017
+ 42,
1018
+ 110 // Max 110 STX (protects against price increase)
1019
+ );
1020
+
1021
+ console.log('NFT purchased:', result);
1022
+ }","Production NFT marketplace purchase with atomic swap. Reads current price, validates against max price, and uses bidirectional post-conditions for secure atomic swap of NFT and STX.","{""buyer"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE"", ""nftContract"": ""SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.stacks-punks-v2"", ""tokenId"": 42, ""maxPriceSTX"": 110, ""currentPrice"": 100000000, ""buyerBalance"": 200000000}","{""txId"": ""0xdef..."", ""success"": true, ""pricePaid"": 100000000, ""sellerReceived"": 95000000, ""marketplaceFee"": 5000000, ""nftReceived"": true}","- Not checking price before tx (race condition if price increases)
1023
+ - Using PostConditionMode.Allow allows funds/NFT theft
1024
+ - Forgetting marketplace fee in calculations
1025
+ - Not verifying listing is still active
1026
+ - Missing buyer post-condition allows NFT to go elsewhere",https://gamma.io,"nfts.csv:6,nfts.csv:8,security-patterns.csv:5","gamma,marketplace,purchase,nft,trading,integration",intermediate
1027
+ 15,nfts,integration,nft-royalties,Implement SIP-009 NFT with automatic royalty payments to creator,"// Production NFT royalties implementation
1028
+ // Pattern from NFT standards and marketplace integrations
1029
+
1030
+ // Clarity: NFT contract with royalty support
1031
+ const NFT_ROYALTIES_CLARITY = `
1032
+ ;; Royalty configuration (10% = 1000 basis points)
1033
+ (define-constant ROYALTY-BPS u1000)
1034
+ (define-constant CREATOR-ADDRESS 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR)
1035
+
1036
+ (define-read-only (get-royalty-info (token-id uint) (sale-price uint))
1037
+ (ok {
1038
+ receiver: CREATOR-ADDRESS,
1039
+ amount: (/ (* sale-price ROYALTY-BPS) u10000)
1040
+ })
1041
+ )
1042
+
1043
+ ;; Marketplace sale with royalty payment
1044
+ (define-public (buy-nft (token-id uint) (payment uint))
1045
+ (let (
1046
+ (seller (unwrap! (nft-get-owner? my-nft token-id) (err u404)))
1047
+ (royalty-info (unwrap! (get-royalty-info token-id payment) (err u500)))
1048
+ (royalty-amount (get amount royalty-info))
1049
+ (seller-amount (- payment royalty-amount))
1050
+ )
1051
+ ;; Pay royalty to creator
1052
+ (try! (stx-transfer? royalty-amount tx-sender (get receiver royalty-info)))
1053
+ ;; Pay seller
1054
+ (try! (stx-transfer? seller-amount tx-sender seller))
1055
+ ;; Transfer NFT
1056
+ (try! (nft-transfer? my-nft token-id seller tx-sender))
1057
+ (ok true)
1058
+ )
1059
+ )
1060
+ `;
1061
+
1062
+ // JavaScript: Buy NFT with automatic royalty payment
1063
+ import { request } from '@stacks/connect';
1064
+ import { uintCV, cvToHex, callReadOnlyFunction, cvToJSON, Pc, PostConditionMode } from '@stacks/transactions';
1065
+
1066
+ async function buyNFTWithRoyalty(
1067
+ buyer: string,
1068
+ nftContract: string,
1069
+ tokenId: number,
1070
+ priceSTX: number
1071
+ ) {
1072
+ const [contractAddress, contractName] = nftContract.split('.');
1073
+ const priceMicro = Math.floor(priceSTX * 1_000_000);
1074
+
1075
+ // 1. Get royalty info
1076
+ const royaltyResult = await callReadOnlyFunction({
1077
+ contractAddress,
1078
+ contractName,
1079
+ functionName: 'get-royalty-info',
1080
+ functionArgs: [uintCV(tokenId), uintCV(priceMicro)],
1081
+ senderAddress: buyer,
1082
+ network: 'mainnet'
1083
+ });
1084
+
1085
+ const royaltyInfo = cvToJSON(royaltyResult).value.value;
1086
+ const royaltyAmount = royaltyInfo.amount.value;
1087
+ const creator = royaltyInfo.receiver.value;
1088
+
1089
+ console.log(`Royalty: ${royaltyAmount / 1e6} STX to ${creator}`);
1090
+
1091
+ // 2. Get current owner
1092
+ const ownerResult = await callReadOnlyFunction({
1093
+ contractAddress,
1094
+ contractName,
1095
+ functionName: 'get-owner',
1096
+ functionArgs: [uintCV(tokenId)],
1097
+ senderAddress: buyer,
1098
+ network: 'mainnet'
1099
+ });
1100
+
1101
+ const seller = cvToJSON(ownerResult).value.value;
1102
+ const sellerAmount = priceMicro - royaltyAmount;
1103
+
1104
+ // 3. Create post-conditions for three-way transfer
1105
+ const postConditions = [
1106
+ // Buyer sends total price
1107
+ Pc.principal(buyer).willSendEq(BigInt(priceMicro)).ustx(),
1108
+ // Creator receives royalty
1109
+ Pc.principal(creator).willReceiveEq(BigInt(royaltyAmount)).ustx(),
1110
+ // Seller receives payment minus royalty
1111
+ Pc.principal(seller).willReceiveEq(BigInt(sellerAmount)).ustx(),
1112
+ // Buyer receives NFT
1113
+ Pc.principal(buyer).willReceiveAsset().nft(`${contractAddress}.${contractName}`, uintCV(tokenId))
1114
+ ];
1115
+
1116
+ const args = [uintCV(tokenId), uintCV(priceMicro)];
1117
+
1118
+ return request('stx_callContract', {
1119
+ contract: nftContract,
1120
+ functionName: 'buy-nft',
1121
+ functionArgs: args.map(cvToHex),
1122
+ postConditionMode: 'deny',
1123
+ postConditions,
1124
+ network: 'mainnet'
1125
+ });
1126
+ }",Production NFT royalties with automatic creator payments. Implements EIP-2981-like royalty standard for Stacks. Three-way transfer: buyer → creator (royalty) + seller (payment).,"{""buyer"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE"", ""nftContract"": ""SP2X0...my-nft"", ""tokenId"": 42, ""priceSTX"": 100, ""royaltyBps"": 1000}","{""txId"": ""0xdef..."", ""success"": true, ""totalPaid"": 100000000, ""royaltyPaid"": 10000000, ""sellerReceived"": 90000000, ""creatorReceived"": 10000000}","- Not verifying royalty recipient before payment
1127
+ - Forgetting post-conditions for all three transfers
1128
+ - Using wrong basis points calculation (10% = 1000 bps)
1129
+ - Not capping royalty percentage (some set 100%+)
1130
+ - Missing royalty info reader function",https://gamma.io,"nfts.csv:9,nfts.csv:10,clarity-syntax.csv:15","royalties,nft,creator-earnings,sip009,revenue",intermediate
1131
+ 16,nfts,best-practice,batch-mint-nfts,Efficiently batch mint multiple NFTs in a single transaction to save on fees,"// Production batch NFT minting for airdrops
1132
+ // Pattern from NFT collection launches and airdrop campaigns
1133
+
1134
+ import { request } from '@stacks/connect';
1135
+ import { uintCV, principalCV, listCV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
1136
+
1137
+ // Clarity: Batch mint function
1138
+ const BATCH_MINT_CLARITY = `
1139
+ (define-public (batch-mint (recipients (list 50 principal)))
1140
+ (begin
1141
+ (asserts! (is-eq tx-sender contract-owner) ERR-NOT-AUTHORIZED)
1142
+ (ok (map mint-to-recipient recipients))
1143
+ )
1144
+ )
1145
+
1146
+ (define-private (mint-to-recipient (recipient principal))
1147
+ (let (
1148
+ (next-id (+ (var-get last-token-id) u1))
1149
+ )
1150
+ (try! (nft-mint? my-nft next-id recipient))
1151
+ (var-set last-token-id next-id)
1152
+ next-id
1153
+ )
1154
+ )
1155
+ `;
1156
+
1157
+ // JavaScript: Batch mint with chunking for large lists
1158
+ async function batchMintNFTs(
1159
+ minter: string,
1160
+ nftContract: string,
1161
+ recipients: string[],
1162
+ chunkSize: number = 50
1163
+ ) {
1164
+ const results = [];
1165
+
1166
+ // Split into chunks (Clarity has list size limits)
1167
+ for (let i = 0; i < recipients.length; i += chunkSize) {
1168
+ const chunk = recipients.slice(i, i + chunkSize);
1169
+
1170
+ console.log(`Minting batch ${Math.floor(i / chunkSize) + 1}/${Math.ceil(recipients.length / chunkSize)}`);
1171
+ console.log(`Recipients: ${chunk.length}`);
1172
+
1173
+ // Create list of principals
1174
+ const recipientCVs = chunk.map(addr => principalCV(addr));
1175
+
1176
+ const args = [listCV(recipientCVs)];
1177
+
1178
+ const result = await request('stx_callContract', {
1179
+ contract: nftContract,
1180
+ functionName: 'batch-mint',
1181
+ functionArgs: args.map(cvToHex),
1182
+ postConditionMode: 'allow', // Complex post-conditions
1183
+ network: 'mainnet'
1184
+ });
1185
+
1186
+ results.push(result);
1187
+
1188
+ // Rate limiting: wait 1 second between batches
1189
+ await new Promise(resolve => setTimeout(resolve, 1000));
1190
+ }
1191
+
1192
+ return results;
1193
+ }
1194
+
1195
+ // Example: Airdrop to 200 wallets
1196
+ async function airdropExample() {
1197
+ const recipients = [
1198
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
1199
+ 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE',
1200
+ // ... 198 more addresses
1201
+ ];
1202
+
1203
+ const results = await batchMintNFTs(
1204
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
1205
+ 'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.my-nft-collection',
1206
+ recipients,
1207
+ 50 // 50 per batch = 4 transactions total
1208
+ );
1209
+
1210
+ console.log(`Airdropped ${recipients.length} NFTs in ${results.length} batches`);
1211
+ }",Production batch NFT minting for airdrops. Chunks large recipient lists to fit Clarity's list size limits (50-100). Includes rate limiting between batches.,"{""minter"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""nftContract"": ""SP2X0...my-collection"", ""recipientCount"": 200, ""chunkSize"": 50}","{""totalMinted"": 200, ""batchCount"": 4, ""successfulBatches"": 4, ""failedMints"": 0, ""durationSeconds"": 8}","- Exceeding Clarity list size limit (crashes transaction)
1212
+ - Not rate limiting causes mempool congestion
1213
+ - Forgetting to track last-token-id between batches
1214
+ - Using sequential IDs allows sniping rare tokens
1215
+ - Not handling failed batches (no retry logic)",https://stx.city,"nfts.csv:1,nfts.csv:2,advanced-patterns.csv:10","batch,mint,gas-optimization,nft,airdrop,best-practice",intermediate
1216
+ 17,nfts,quickstart,update-nft-metadata,Update NFT metadata URI after minting for dynamic NFTs,"// Production dynamic NFT metadata updates
1217
+ // Pattern from gaming NFTs and evolving collections
1218
+
1219
+ // Clarity: Dynamic metadata with update function
1220
+ const DYNAMIC_NFT_CLARITY = `
1221
+ (define-map token-metadata uint (string-utf8 256))
1222
+ (define-map token-attributes uint {
1223
+ level: uint,
1224
+ power: uint,
1225
+ rarity: (string-ascii 20)
1226
+ })
1227
+
1228
+ (define-public (update-metadata (token-id uint) (new-uri (string-utf8 256)))
1229
+ (begin
1230
+ (asserts! (is-eq tx-sender (unwrap! (nft-get-owner? my-nft token-id) (err u404))) ERR-NOT-OWNER)
1231
+ (map-set token-metadata token-id new-uri)
1232
+ (ok true)
1233
+ )
1234
+ )
1235
+
1236
+ (define-public (level-up (token-id uint))
1237
+ (let (
1238
+ (owner (unwrap! (nft-get-owner? my-nft token-id) (err u404)))
1239
+ (attrs (default-to {level: u1, power: u10, rarity: ""common""}
1240
+ (map-get? token-attributes token-id)))
1241
+ )
1242
+ (asserts! (is-eq tx-sender owner) ERR-NOT-OWNER)
1243
+ (map-set token-attributes token-id
1244
+ (merge attrs {
1245
+ level: (+ (get level attrs) u1),
1246
+ power: (+ (get power attrs) u5)
1247
+ })
1248
+ )
1249
+ (ok true)
1250
+ )
1251
+ )
1252
+ `;
1253
+
1254
+ // JavaScript: Update metadata with new IPFS hash
1255
+ import { request } from '@stacks/connect';
1256
+ import { uintCV, stringUtf8CV, cvToHex } from '@stacks/transactions';
1257
+
1258
+ async function updateNFTMetadata(
1259
+ owner: string,
1260
+ nftContract: string,
1261
+ tokenId: number,
1262
+ newIpfsHash: string
1263
+ ) {
1264
+ const newUri = `ipfs://${newIpfsHash}`;
1265
+
1266
+ const args = [
1267
+ uintCV(tokenId),
1268
+ stringUtf8CV(newUri)
1269
+ ];
1270
+
1271
+ return request('stx_callContract', {
1272
+ contract: nftContract,
1273
+ functionName: 'update-metadata',
1274
+ functionArgs: args.map(cvToHex),
1275
+ postConditionMode: 'deny',
1276
+ network: 'mainnet'
1277
+ });
1278
+ }
1279
+
1280
+ // Example: Update NFT after evolution/upgrade
1281
+ async function evolveNFT() {
1282
+ // 1. Upload new metadata to IPFS
1283
+ const newMetadata = {
1284
+ name: ""My NFT #42"",
1285
+ image: ""ipfs://QmNewImage.../42.png"",
1286
+ attributes: [
1287
+ { trait_type: ""Level"", value: 2 },
1288
+ { trait_type: ""Power"", value: 15 }
1289
+ ]
1290
+ };
1291
+
1292
+ // Upload to IPFS (using pinata, web3.storage, etc.)
1293
+ // const ipfsHash = await uploadToIPFS(newMetadata);
1294
+ const ipfsHash = 'QmNewHash123.../42.json';
1295
+
1296
+ // 2. Update on-chain metadata pointer
1297
+ const result = await updateNFTMetadata(
1298
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
1299
+ 'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.gaming-nft-v1',
1300
+ 42,
1301
+ ipfsHash
1302
+ );
1303
+
1304
+ console.log('Metadata updated:', result);
1305
+ }",Production dynamic NFT metadata updates. Allows owners to evolve NFTs by updating IPFS metadata pointers. Common for gaming NFTs and progressive collections.,"{""owner"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""nftContract"": ""SP2X0...gaming-nft"", ""tokenId"": 42, ""newIpfsHash"": ""QmNewHash.../42.json"", ""currentLevel"": 1}","{""txId"": ""0xabc..."", ""success"": true, ""newUri"": ""ipfs://QmNewHash.../42.json"", ""metadataUpdated"": true, ""newLevel"": 2}","- Not restricting who can update (owner-only check missing)
1306
+ - Allowing unlimited updates causes metadata spam
1307
+ - Forgetting to emit update event (marketplaces miss changes)
1308
+ - Not validating IPFS hash format
1309
+ - Using centralized storage instead of IPFS (not permanent)",https://docs.stacks.co,"nfts.csv:11,nfts.csv:12,clarity-syntax.csv:25","metadata,dynamic-nft,ipfs,updates,quickstart",intermediate
1310
+ 18,nfts,integration,nft-collection-launch,"Launch a complete NFT collection with mint, metadata, and marketplace integration","// Production NFT collection launch workflow
1311
+ // From successful Stacks NFT drops
1312
+
1313
+ import { request } from '@stacks/connect';
1314
+
1315
+ // Complete launch checklist and deployment
1316
+ const NFT_LAUNCH_WORKFLOW = `
1317
+ # NFT Collection Launch Workflow
1318
+
1319
+ ## Pre-Launch (1-2 weeks)
1320
+ 1. ✓ Deploy contract to testnet
1321
+ 2. ✓ Test minting, transfers, marketplace compatibility
1322
+ 3. ✓ Upload metadata to IPFS/Arweave (pinned)
1323
+ 4. ✓ Set up website with mint UI
1324
+ 5. ✓ Configure allowlist (Merkle tree)
1325
+ 6. ✓ Test with multiple wallets
1326
+
1327
+ ## Launch Day
1328
+ 1. ✓ Deploy to mainnet (verify contract)
1329
+ 2. ✓ Set mint price and limits
1330
+ 3. ✓ Enable allowlist minting (6 hours)
1331
+ 4. ✓ Enable public minting
1332
+ 5. ✓ Monitor for errors/exploits
1333
+
1334
+ ## Post-Launch
1335
+ 1. ✓ List on Gamma marketplace
1336
+ 2. ✓ Reveal metadata (if unrevealed mint)
1337
+ 3. ✓ Set up royalties
1338
+ 4. ✓ Announce secondary trading
1339
+ `;
1340
+
1341
+ // Clarity: NFT contract with launch controls
1342
+ const NFT_LAUNCH_CLARITY = `
1343
+ (define-constant contract-owner tx-sender)
1344
+ (define-constant mint-price u50000000) ;; 50 STX
1345
+ (define-constant max-supply u10000)
1346
+
1347
+ (define-data-var mint-enabled bool false)
1348
+ (define-data-var allowlist-enabled bool true)
1349
+ (define-data-var last-token-id uint u0)
1350
+
1351
+ ;; Allowlist (Merkle root or simple map)
1352
+ (define-map allowlist principal bool)
1353
+
1354
+ (define-public (enable-public-mint)
1355
+ (begin
1356
+ (asserts! (is-eq tx-sender contract-owner) ERR-NOT-AUTHORIZED)
1357
+ (var-set mint-enabled true)
1358
+ (var-set allowlist-enabled false)
1359
+ (ok true)
1360
+ )
1361
+ )
1362
+
1363
+ (define-public (mint)
1364
+ (let (
1365
+ (next-id (+ (var-get last-token-id) u1))
1366
+ )
1367
+ (asserts! (var-get mint-enabled) (err u100))
1368
+ (asserts! (<= next-id max-supply) (err u101))
1369
+
1370
+ ;; Check allowlist if enabled
1371
+ (if (var-get allowlist-enabled)
1372
+ (asserts! (default-to false (map-get? allowlist tx-sender)) (err u102))
1373
+ true
1374
+ )
1375
+
1376
+ ;; Payment
1377
+ (try! (stx-transfer? mint-price tx-sender contract-owner))
1378
+
1379
+ ;; Mint NFT
1380
+ (try! (nft-mint? my-nft next-id tx-sender))
1381
+ (var-set last-token-id next-id)
1382
+
1383
+ (ok next-id)
1384
+ )
1385
+ )
1386
+ `;
1387
+
1388
+ // JavaScript: Launch sequence
1389
+ async function launchNFTCollection() {
1390
+ // Step 1: Deploy contract
1391
+ console.log('1. Deploying contract...');
1392
+ const deployResult = await request('stx_deployContract', {
1393
+ contractName: 'my-nft-collection-v1',
1394
+ codeBody: NFT_LAUNCH_CLARITY,
1395
+ network: 'mainnet'
1396
+ });
1397
+
1398
+ console.log('Contract deployed:', deployResult);
1399
+
1400
+ // Step 2: Add allowlist members
1401
+ console.log('2. Setting up allowlist...');
1402
+ // ... add allowlist logic ...
1403
+
1404
+ // Step 3: Enable allowlist minting
1405
+ console.log('3. Enabling allowlist mint...');
1406
+ await request('stx_callContract', {
1407
+ contract: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.my-nft-collection-v1',
1408
+ functionName: 'enable-allowlist-mint',
1409
+ functionArgs: [],
1410
+ postConditionMode: 'deny',
1411
+ network: 'mainnet'
1412
+ });
1413
+
1414
+ // Step 4: After 6 hours, enable public mint
1415
+ console.log('4. Waiting for allowlist period...');
1416
+ setTimeout(async () => {
1417
+ console.log('5. Enabling public mint...');
1418
+ await request('stx_callContract', {
1419
+ contract: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.my-nft-collection-v1',
1420
+ functionName: 'enable-public-mint',
1421
+ functionArgs: [],
1422
+ postConditionMode: 'deny',
1423
+ network: 'mainnet'
1424
+ });
1425
+ }, 6 * 60 * 60 * 1000); // 6 hours
1426
+ }",Production NFT collection launch workflow. Complete checklist from testnet deployment to public mint. Includes allowlist period and mint controls.,"{""contractName"": ""my-nft-collection-v1"", ""maxSupply"": 10000, ""mintPrice"": 50000000, ""allowlistPeriod"": 21600, ""allowlistSize"": 500}","{""contractDeployed"": true, ""allowlistMintEnabled"": true, ""publicMintEnabled"": false, ""totalMinted"": 500, ""phase"": ""allowlist""}","- Not testing on testnet first
1427
+ - Missing mint controls (anyone can mint)
1428
+ - No supply cap enforcement
1429
+ - Metadata not pinned (IPFS unpinned)
1430
+ - No marketplace compatibility testing",https://gamma.io,"nfts.csv:1,nfts.csv:6,nfts.csv:9,advanced-patterns.csv:15","collection,launch,mint,marketplace,integration,advanced",advanced
1431
+ 19,nfts,debugging,debug-nft-transfer-failure,Debug and fix common NFT transfer failures and ownership issues,"// Debug NFT transfer failure
1432
+ import { callReadOnlyFunction, cvToJSON, uintCV } from '@stacks/transactions';
1433
+
1434
+ async function debugNFTTransfer(nftContract: string, tokenId: number, sender: string) {
1435
+ const [addr, name] = nftContract.split('.');
1436
+
1437
+ // Check ownership
1438
+ const owner = await callReadOnlyFunction({
1439
+ contractAddress: addr, contractName: name,
1440
+ functionName: 'get-owner',
1441
+ functionArgs: [uintCV(tokenId)],
1442
+ senderAddress: sender, network: 'mainnet'
1443
+ });
1444
+
1445
+ const currentOwner = cvToJSON(owner).value.value;
1446
+ if (currentOwner !== sender) {
1447
+ return {error: 'not_owner', fix: 'You do not own this NFT'};
1448
+ }
1449
+
1450
+ // Check if locked
1451
+ try {
1452
+ const locked = await callReadOnlyFunction({
1453
+ contractAddress: addr, contractName: name,
1454
+ functionName: 'is-locked',
1455
+ functionArgs: [uintCV(tokenId)],
1456
+ senderAddress: sender, network: 'mainnet'
1457
+ });
1458
+ if (cvToJSON(locked).value) {
1459
+ return {error: 'nft_locked', fix: 'NFT is locked in marketplace/staking'};
1460
+ }
1461
+ } catch (e) {}
1462
+
1463
+ return {error: 'unknown', fix: 'Check post-conditions and try again'};
1464
+ }","Production NFT transfer debugging. Checks ownership, lock status, and marketplace listings to diagnose why transfer failed.","{""tokenId"": 42, ""senderAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""recipientAddress"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE""}","{""debugSteps"": [""ownership-verified"", ""not-listed"", ""not-staked"", ""transfer-successful""], ""txId"": ""0xddd..."", ""success"": true}","- Not checking token ownership before transfer
1465
+ - Forgetting to cancel marketplace listings first
1466
+ - Attempting to transfer staked/locked NFTs
1467
+ - Using wrong token ID (off-by-one errors)
1468
+ - Wrong contract address for NFT collection
1469
+ - Not handling contract error codes properly",https://explorer.hiro.so,"nfts.csv:3,nfts.csv:4,security-patterns.csv:1","debugging,nft,transfer,ownership,troubleshooting,intermediate",intermediate
1470
+ 20,nfts,security,secure-nft-marketplace,"Build a secure NFT marketplace with escrow, post-conditions, and anti-rug measures","// Secure NFT marketplace with escrow
1471
+ const SECURE_MARKETPLACE_CLARITY = `
1472
+ (define-map listings uint {seller: principal, price: uint, expiry: uint})
1473
+ (define-map escrowed-nfts uint bool)
1474
+
1475
+ (define-public (list-nft (token-id uint) (price uint) (expiry uint))
1476
+ (begin
1477
+ (try! (nft-transfer? my-nft token-id tx-sender (as-contract tx-sender)))
1478
+ (map-set listings token-id {seller: tx-sender, price: price, expiry: expiry})
1479
+ (map-set escrowed-nfts token-id true)
1480
+ (ok true)
1481
+ )
1482
+ )
1483
+
1484
+ (define-public (buy-nft (token-id uint))
1485
+ (let (
1486
+ (listing (unwrap! (map-get? listings token-id) (err u404)))
1487
+ (seller (get seller listing))
1488
+ (price (get price listing))
1489
+ )
1490
+ (asserts! (< block-height (get expiry listing)) (err u410))
1491
+ (try! (stx-transfer? price tx-sender seller))
1492
+ (try! (as-contract (nft-transfer? my-nft token-id tx-sender buyer)))
1493
+ (map-delete listings token-id)
1494
+ (map-delete escrowed-nfts token-id)
1495
+ (ok true)
1496
+ )
1497
+ )
1498
+ `;","Production secure NFT marketplace with escrow. NFT is locked in contract during listing, preventing double-spend and rug pulls.","{""listingId"": 42, ""priceInMicroSTX"": 10000000, ""buyerAddress"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE"", ""sellerAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR""}","{""txId"": ""0xeee..."", ""securityChecks"": [""escrow"", ""post-conditions"", ""deny-mode"", ""expiration"", ""atomic-swap""], ""safe"": true}","- NEVER skip post-conditions on marketplace txs
1499
+ - NEVER use Allow mode (always use Deny mode)
1500
+ - NEVER trust external contract state without validation
1501
+ - NEVER allow partial transfers (atomic only)
1502
+ - Always use escrow pattern for listings
1503
+ - Always validate listing expiration
1504
+ - Always check NFT ownership before transfer",https://github.com/gamma-io/gamma-contracts,"nfts.csv:6,nfts.csv:8,security-patterns.csv:1,security-patterns.csv:5","security,marketplace,escrow,post-conditions,anti-rug,advanced",advanced
1505
+ 21,tokens,quickstart,deploy-sip010-token,Deploy a basic SIP-010 compliant fungible token contract,"// Production SIP-010 token deployment
1506
+ // Pattern from DeFi protocols and token standards
1507
+
1508
+ import { request } from '@stacks/connect';
1509
+ import { AnchorMode } from '@stacks/transactions';
1510
+
1511
+ // Complete SIP-010 token contract in Clarity
1512
+ const SIP010_CONTRACT = \`
1513
+ ;; SIP-010 Fungible Token Standard
1514
+ (impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
1515
+
1516
+ ;; Token definition
1517
+ (define-fungible-token my-token u1000000000000)
1518
+
1519
+ ;; Constants
1520
+ (define-constant contract-owner tx-sender)
1521
+ (define-constant err-owner-only (err u100))
1522
+ (define-constant err-not-token-owner (err u101))
1523
+
1524
+ ;; SIP-010 Functions
1525
+
1526
+ (define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
1527
+ (begin
1528
+ (asserts! (is-eq tx-sender sender) err-not-token-owner)
1529
+ (try! (ft-transfer? my-token amount sender recipient))
1530
+ (match memo to-print (print to-print) 0x)
1531
+ (ok true)
1532
+ )
1533
+ )
1534
+
1535
+ (define-read-only (get-name)
1536
+ (ok ""My Token"")
1537
+ )
1538
+
1539
+ (define-read-only (get-symbol)
1540
+ (ok ""MYT"")
1541
+ )
1542
+
1543
+ (define-read-only (get-decimals)
1544
+ (ok u6)
1545
+ )
1546
+
1547
+ (define-read-only (get-balance (who principal))
1548
+ (ok (ft-get-balance my-token who))
1549
+ )
1550
+
1551
+ (define-read-only (get-total-supply)
1552
+ (ok (ft-get-supply my-token))
1553
+ )
1554
+
1555
+ (define-read-only (get-token-uri)
1556
+ (ok (some u""https://example.com/token-metadata.json""))
1557
+ )
1558
+
1559
+ ;; Mint function (owner only)
1560
+ (define-public (mint (amount uint) (recipient principal))
1561
+ (begin
1562
+ (asserts! (is-eq tx-sender contract-owner) err-owner-only)
1563
+ (ft-mint? my-token amount recipient)
1564
+ )
1565
+ )
1566
+ \`;
1567
+
1568
+ async function deploySIP010Token() {
1569
+ // Deploy contract using Stacks Connect
1570
+ const { request } = await import('@stacks/connect');
1571
+
1572
+ return request('stx_deployContract', {
1573
+ contractName: 'my-token-v1',
1574
+ codeBody: SIP010_CONTRACT,
1575
+ network: 'mainnet',
1576
+ anchorMode: AnchorMode.Any,
1577
+ postConditionMode: 'deny',
1578
+ postConditions: []
1579
+ });
1580
+ }
1581
+
1582
+ // After deployment, mint initial supply
1583
+ async function mintInitialSupply(
1584
+ tokenContract: string,
1585
+ amount: number,
1586
+ recipient: string
1587
+ ) {
1588
+ const { request } = await import('@stacks/connect');
1589
+ const { uintCV, principalCV, cvToHex } = await import('@stacks/transactions');
1590
+
1591
+ return request('stx_callContract', {
1592
+ contract: tokenContract,
1593
+ functionName: 'mint',
1594
+ functionArgs: [
1595
+ uintCV(amount),
1596
+ principalCV(recipient)
1597
+ ].map(cvToHex),
1598
+ postConditionMode: 'deny',
1599
+ network: 'mainnet'
1600
+ });
1601
+ }","Production SIP-010 token deployment with complete trait implementation. Includes all required functions (transfer, get-balance, get-total-supply) and optional mint function.","{""contractName"": ""my-token-v1"", ""totalSupply"": 1000000000000, ""decimals"": 6, ""tokenName"": ""My Token"", ""tokenSymbol"": ""MYT""}","{""txId"": ""0x123..."", ""success"": true, ""contractId"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.my-token-v1"", ""deployed"": true, ""traitCompliant"": true}","- Not implementing all SIP-010 required functions
1602
+ - Using wrong trait principal (must match network)
1603
+ - Forgetting to define fungible token with total supply
1604
+ - Not adding mint function for owner (can't distribute tokens)
1605
+ - Missing error constants (poor UX)",https://docs.stacks.co/clarity/example-contracts/sip-010-fungible-token,"fungible-tokens.csv:1,fungible-tokens.csv:8,clarity-syntax.csv:15","sip010,deploy,token,fungible,standard,quickstart",beginner
1606
+ 22,tokens,quickstart,token-transfer-with-postconditions,Transfer tokens securely with post-conditions to prevent unauthorized transfers,"// Production token transfer with security post-conditions
1607
+ // From stacksagent-backend trade controller patterns
1608
+
1609
+ import { request } from '@stacks/connect';
1610
+ import { uintCV, principalCV, someCV, noneCV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
1611
+
1612
+ async function transferTokenWithPostConditions(
1613
+ sender: string,
1614
+ recipient: string,
1615
+ tokenContract: string,
1616
+ amount: number,
1617
+ memo?: string
1618
+ ) {
1619
+ const [tokenAddress, tokenName] = tokenContract.split('.');
1620
+
1621
+ // Create strict post-conditions
1622
+ const postConditions = [
1623
+ // Sender MUST send exact amount (prevents overspend)
1624
+ Pc.principal(sender)
1625
+ .willSendEq(BigInt(amount))
1626
+ .ft(tokenContract, tokenName),
1627
+ // Recipient MUST receive exact amount (prevents theft)
1628
+ Pc.principal(recipient)
1629
+ .willReceiveEq(BigInt(amount))
1630
+ .ft(tokenContract, tokenName)
1631
+ ];
1632
+
1633
+ // Build arguments (SIP-010 transfer signature)
1634
+ const args = [
1635
+ uintCV(amount),
1636
+ principalCV(sender),
1637
+ principalCV(recipient),
1638
+ memo ? someCV(stringUtf8CV(memo)) : noneCV()
1639
+ ];
1640
+
1641
+ return request('stx_callContract', {
1642
+ contract: tokenContract,
1643
+ functionName: 'transfer',
1644
+ functionArgs: args.map(cvToHex),
1645
+ postConditionMode: 'deny', // CRITICAL: Always use deny mode
1646
+ postConditions,
1647
+ network: 'mainnet'
1648
+ });
1649
+ }
1650
+
1651
+ // Example: Safe token transfer
1652
+ async function safeTransferExample() {
1653
+ const result = await transferTokenWithPostConditions(
1654
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
1655
+ 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE',
1656
+ 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token',
1657
+ 1_000_000,
1658
+ 'Payment for services'
1659
+ );
1660
+
1661
+ console.log('Transfer completed:', result);
1662
+ }",Production token transfer from stacksagent-backend with strict post-conditions. Uses willSendEq and willReceiveEq to prevent any unauthorized token movements.,"{""sender"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""recipient"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE"", ""tokenContract"": ""SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token"", ""amount"": 1000000, ""senderBalance"": 5000000}","{""txId"": ""0xabc..."", ""success"": true, ""amountTransferred"": 1000000, ""postConditionsVerified"": true, ""senderNewBalance"": 4000000, ""recipientNewBalance"": 1000000}","- Using PostConditionMode.Allow without post-conditions (critical security flaw)
1663
+ - Using willSendLte instead of willSendEq (allows contract to take more)
1664
+ - Forgetting memo parameter format (optional (buff 34))
1665
+ - Not validating sender has sufficient balance
1666
+ - Using wrong token contract address",https://github.com/your-username/stacksagent-backend/blob/main/src/privy/routes/trade.controller.ts#L45,"fungible-tokens.csv:8,fungible-tokens.csv:23,security-patterns.csv:3","transfer,post-conditions,security,sip010,quickstart",beginner
1667
+ 23,tokens,integration,token-allowance-pattern,Implement ERC-20 style allowance pattern for DEX integrations and delegated transfers,"// Production token allowance for DEX integration
1668
+ // Pattern from DeFi protocols (Alex, Velar, Bitflow)
1669
+
1670
+ import { request } from '@stacks/connect';
1671
+ import { uintCV, principalCV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
1672
+
1673
+ // Step 1: Grant allowance to spender (e.g., DEX contract)
1674
+ async function approveTokenAllowance(
1675
+ owner: string,
1676
+ spender: string,
1677
+ tokenContract: string,
1678
+ amount: number
1679
+ ) {
1680
+ const [tokenAddress, tokenName] = tokenContract.split('.');
1681
+
1682
+ // Post-condition: No tokens should move during approval
1683
+ const postConditions = [
1684
+ Pc.principal(owner).willSendEq(BigInt(0)).ft(tokenContract, tokenName)
1685
+ ];
1686
+
1687
+ const args = [
1688
+ principalCV(spender),
1689
+ uintCV(amount)
1690
+ ];
1691
+
1692
+ return request('stx_callContract', {
1693
+ contract: tokenContract,
1694
+ functionName: 'approve',
1695
+ functionArgs: args.map(cvToHex),
1696
+ postConditionMode: 'deny',
1697
+ postConditions,
1698
+ network: 'mainnet'
1699
+ });
1700
+ }
1701
+
1702
+ // Step 2: Spender uses allowance (DEX swaps tokens)
1703
+ async function transferFromAllowance(
1704
+ spender: string,
1705
+ from: string,
1706
+ to: string,
1707
+ tokenContract: string,
1708
+ amount: number
1709
+ ) {
1710
+ const [tokenAddress, tokenName] = tokenContract.split('.');
1711
+
1712
+ // Post-conditions: Exact amount transferred
1713
+ const postConditions = [
1714
+ Pc.principal(from).willSendEq(BigInt(amount)).ft(tokenContract, tokenName),
1715
+ Pc.principal(to).willReceiveEq(BigInt(amount)).ft(tokenContract, tokenName)
1716
+ ];
1717
+
1718
+ const args = [
1719
+ principalCV(from),
1720
+ principalCV(to),
1721
+ uintCV(amount)
1722
+ ];
1723
+
1724
+ return request('stx_callContract', {
1725
+ contract: tokenContract,
1726
+ functionName: 'transfer-from',
1727
+ functionArgs: args.map(cvToHex),
1728
+ postConditionMode: 'deny',
1729
+ postConditions,
1730
+ network: 'mainnet'
1731
+ });
1732
+ }
1733
+
1734
+ // Example: Approve DEX to spend 1000 tokens
1735
+ async function approveForSwap() {
1736
+ const owner = 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR';
1737
+ const dexContract = 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.amm-swap-pool-v1-1';
1738
+ const tokenContract = 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token';
1739
+
1740
+ // Approve DEX to spend 1000 tokens
1741
+ const result = await approveTokenAllowance(
1742
+ owner,
1743
+ dexContract,
1744
+ tokenContract,
1745
+ 1000_000_000
1746
+ );
1747
+
1748
+ console.log('Allowance granted:', result);
1749
+ }","Production token allowance pattern from DEX integrations. Two-step process: approve spender, then spender uses transfer-from. Critical for DEX swaps and automated protocols.","{""owner"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""spender"": ""SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.amm-swap-pool-v1-1"", ""tokenContract"": ""SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.age000-governance-token"", ""amount"": 1000000000, ""ownerBalance"": 5000000000}","{""approvalTxId"": ""0x123..."", ""allowanceSet"": 1000000000, ""spenderAuthorized"": true, ""tokensNotMoved"": true}","- Approving unlimited amount (security risk)
1750
+ - Not revoking old allowances before setting new ones
1751
+ - Using approve without checking current allowance first
1752
+ - Forgetting spender must call transfer-from, not transfer
1753
+ - Not handling allowance expiry in some token implementations",https://app.alexlab.co,"fungible-tokens.csv:11,fungible-tokens.csv:13,defi-protocols.csv:1,security-patterns.csv:1","allowance,approve,transfer-from,dex,integration",intermediate
1754
+ 24,tokens,integration,token-vesting-schedule,Create time-locked token vesting schedules for team allocations and investor unlocks,"// Production token vesting with time-locked releases
1755
+ // Pattern from token launches and team allocations
1756
+
1757
+ import { request } from '@stacks/connect';
1758
+ import { uintCV, principalCV, cvToHex, callReadOnlyFunction, cvToJSON } from '@stacks/transactions';
1759
+
1760
+ // Clarity contract for vesting (simplified)
1761
+ const VESTING_CONTRACT = \`
1762
+ (define-map vesting-schedules
1763
+ { recipient: principal }
1764
+ {
1765
+ total-amount: uint,
1766
+ released-amount: uint,
1767
+ start-block: uint,
1768
+ cliff-blocks: uint,
1769
+ vesting-blocks: uint
1770
+ }
1771
+ )
1772
+
1773
+ (define-read-only (get-vested-amount (recipient principal) (current-block uint))
1774
+ (let (
1775
+ (schedule (unwrap! (map-get? vesting-schedules { recipient: recipient }) (err u404)))
1776
+ )
1777
+ (if (< current-block (+ (get start-block schedule) (get cliff-blocks schedule)))
1778
+ ;; Before cliff, nothing vested
1779
+ (ok u0)
1780
+ ;; After cliff, linear vesting
1781
+ (let (
1782
+ (elapsed (- current-block (get start-block schedule)))
1783
+ (vested (/ (* (get total-amount schedule) elapsed) (get vesting-blocks schedule)))
1784
+ )
1785
+ (ok (min vested (get total-amount schedule)))
1786
+ )
1787
+ )
1788
+ )
1789
+ )
1790
+
1791
+ (define-public (claim-vested-tokens)
1792
+ (let (
1793
+ (schedule (unwrap! (map-get? vesting-schedules { recipient: tx-sender }) (err u404)))
1794
+ (vested (unwrap! (get-vested-amount tx-sender block-height) (err u500)))
1795
+ (claimable (- vested (get released-amount schedule)))
1796
+ )
1797
+ (asserts! (> claimable u0) (err u400))
1798
+ ;; Update released amount
1799
+ (map-set vesting-schedules
1800
+ { recipient: tx-sender }
1801
+ (merge schedule { released-amount: vested })
1802
+ )
1803
+ ;; Transfer tokens
1804
+ (ft-transfer? my-token claimable (as-contract tx-sender) tx-sender)
1805
+ )
1806
+ )
1807
+ \`;
1808
+
1809
+ // JavaScript: Claim vested tokens
1810
+ async function claimVestedTokens(
1811
+ recipient: string,
1812
+ vestingContract: string
1813
+ ) {
1814
+ // 1. Check how much is vested
1815
+ const vestedResult = await callReadOnlyFunction({
1816
+ contractAddress: vestingContract.split('.')[0],
1817
+ contractName: vestingContract.split('.')[1],
1818
+ functionName: 'get-vested-amount',
1819
+ functionArgs: [
1820
+ principalCV(recipient),
1821
+ uintCV(150000) // Current block height
1822
+ ],
1823
+ senderAddress: recipient,
1824
+ network: 'mainnet'
1825
+ });
1826
+
1827
+ const vestedAmount = cvToJSON(vestedResult).value.value;
1828
+ console.log(\`Vested amount: \${vestedAmount / 1e6} tokens\`);
1829
+
1830
+ // 2. Claim vested tokens
1831
+ return request('stx_callContract', {
1832
+ contract: vestingContract,
1833
+ functionName: 'claim-vested-tokens',
1834
+ functionArgs: [],
1835
+ postConditionMode: 'allow', // Contract handles transfer
1836
+ network: 'mainnet'
1837
+ });
1838
+ }
1839
+
1840
+ // Example: Team member claims after 6 months
1841
+ async function claimTeamTokens() {
1842
+ const result = await claimVestedTokens(
1843
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
1844
+ 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-vesting-v1'
1845
+ );
1846
+
1847
+ console.log('Tokens claimed:', result);
1848
+ }",Production token vesting with time-locked releases. Implements cliff period and linear vesting schedule. Common for team allocations and investor lockups.,"{""recipient"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""totalAmount"": 1000000000000, ""cliffBlocks"": 4320, ""vestingBlocks"": 52560, ""currentBlock"": 150000, ""startBlock"": 100000}","{""txId"": ""0xabc..."", ""vestedAmount"": 950000000000, ""claimedAmount"": 950000000000, ""remainingVesting"": 50000000000}","- Not implementing cliff period (immediate vesting)
1849
+ - Using block-height instead of burn-block-height (vulnerable to reorgs)
1850
+ - Forgetting to track released amount (double claims)
1851
+ - Not handling edge cases at vesting completion
1852
+ - Missing emergency revocation function for team departures",https://docs.stacks.co,"fungible-tokens.csv:17,fungible-tokens.csv:19,advanced-patterns.csv:20","vesting,time-lock,team-tokens,cliff,linear,integration",advanced
1853
+ 25,tokens,best-practice,token-burn-supply-management,Implement token burning and supply management for deflationary tokenomics,"// Production token burn for deflationary mechanics
1854
+ // Pattern from meme tokens and supply management
1855
+
1856
+ import { request } from '@stacks/connect';
1857
+ import { uintCV, principalCV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
1858
+
1859
+ async function burnTokens(
1860
+ burner: string,
1861
+ tokenContract: string,
1862
+ amount: number
1863
+ ) {
1864
+ const [tokenAddress, tokenName] = tokenContract.split('.');
1865
+
1866
+ // Post-condition: Tokens will be burned (sent to null address or destroyed)
1867
+ const postConditions = [
1868
+ Pc.principal(burner).willSendEq(BigInt(amount)).ft(tokenContract, tokenName)
1869
+ ];
1870
+
1871
+ const args = [
1872
+ uintCV(amount),
1873
+ principalCV(burner)
1874
+ ];
1875
+
1876
+ return request('stx_callContract', {
1877
+ contract: tokenContract,
1878
+ functionName: 'burn',
1879
+ functionArgs: args.map(cvToHex),
1880
+ postConditionMode: 'deny',
1881
+ postConditions,
1882
+ network: 'mainnet'
1883
+ });
1884
+ }
1885
+
1886
+ // Clarity burn implementation (in token contract)
1887
+ const BURN_CLARITY = \`
1888
+ (define-public (burn (amount uint) (sender principal))
1889
+ (begin
1890
+ (asserts! (is-eq tx-sender sender) ERR-NOT-AUTHORIZED)
1891
+ (ft-burn? my-token amount sender)
1892
+ )
1893
+ )
1894
+ \`;
1895
+
1896
+ // Example: Burn 1M tokens to reduce supply
1897
+ async function burnSupplyExample() {
1898
+ const result = await burnTokens(
1899
+ 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR',
1900
+ 'SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.deflationary-token',
1901
+ 1_000_000_000_000 // 1M tokens
1902
+ );
1903
+
1904
+ console.log('Tokens burned:', result);
1905
+ }",Production token burn for deflationary mechanics. Permanently reduces token supply using ft-burn? Clarity function. Common in meme tokens and buyback-and-burn models.,"{""burner"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""tokenContract"": ""SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.deflationary-token"", ""amount"": 1000000000000, ""burnerBalance"": 5000000000000, ""totalSupply"": 100000000000000}","{""txId"": ""0xdef..."", ""success"": true, ""amountBurned"": 1000000000000, ""newTotalSupply"": 99000000000000, ""burnerNewBalance"": 4000000000000}","- Not checking burner has sufficient balance
1906
+ - Missing authorization check (anyone can burn?)
1907
+ - Not emitting burn event for analytics
1908
+ - Using transfer to null address instead of ft-burn? (doesn't reduce supply)
1909
+ - Forgetting to update circulating supply metrics",https://stx.city,"fungible-tokens.csv:10,fungible-tokens.csv:3,clarity-syntax.csv:15","burn,deflationary,supply-management,tokenomics,best-practice",intermediate
1910
+ 26,tokens,integration,multi-token-atomic-swap,Execute atomic swaps between multiple tokens in a single transaction,"// Multi-token atomic swap
1911
+ import { request } from '@stacks/connect';
1912
+ import { listCV, tupleCV, principalCV, uintCV, cvToHex, Pc } from '@stacks/transactions';
1913
+
1914
+ async function multiTokenSwap(
1915
+ user: string,
1916
+ offers: Array<{token: string, amount: number}>,
1917
+ requests: Array<{token: string, amount: number}>
1918
+ ) {
1919
+ const postConditions = [
1920
+ ...offers.map(o =>
1921
+ Pc.principal(user).willSendEq(BigInt(o.amount)).ft(o.token, o.token.split('.')[1])
1922
+ ),
1923
+ ...requests.map(r =>
1924
+ Pc.principal(user).willReceiveGte(BigInt(r.amount)).ft(r.token, r.token.split('.')[1])
1925
+ )
1926
+ ];
1927
+
1928
+ return request('stx_callContract', {
1929
+ contract: 'SP...swap-contract',
1930
+ functionName: 'multi-token-swap',
1931
+ functionArgs: [
1932
+ listCV(offers.map(o => tupleCV({token: principalCV(o.token), amount: uintCV(o.amount)}))),
1933
+ listCV(requests.map(r => tupleCV({token: principalCV(r.token), amount: uintCV(r.amount)})))
1934
+ ].map(cvToHex),
1935
+ postConditionMode: 'deny',
1936
+ postConditions,
1937
+ network: 'mainnet'
1938
+ });
1939
+ }",Production multi-token atomic swap. Swaps multiple tokens in single transaction with strict post-conditions on all assets.,"{""amountIn"": 10000000, ""minAmountOut"": 50000000, ""tokenA"": ""arkadiko-token"", ""tokenB"": ""age000-governance-token"", ""multiHopRoute"": [""DIKO"", ""ALEX"", ""USDA""]}","{""swapTxId"": ""0xabc..."", ""amountIn"": 10000000, ""amountOut"": 52000000, ""multiHopTxId"": ""0xdef..."", ""route"": ""DIKO->ALEX->USDA"", ""batchTxId"": ""0xghi..."", ""tokensReceived"": 3}","- Not using atomic transactions allows partial failures
1940
+ - Missing minimum output validation causes slippage losses
1941
+ - No post-conditions enables unauthorized token transfers
1942
+ - Multi-hop without intermediate validation loses funds
1943
+ - Batch swaps without size limits cause out-of-gas errors
1944
+ - Not checking pool liquidity before large swaps
1945
+ - Forgetting to approve token contracts for transfers",https://app.alexlab.co/swap,"fungible-tokens.csv:8,fungible-tokens.csv:23,defi-protocols.csv:1,advanced-patterns.csv:1","atomic-swap,multi-token,dex,batch,integration",intermediate
1946
+ 27,tokens,debugging,debug-token-transfer-failure,"Debug and fix common token transfer failures including insufficient balance, wrong addresses, and contract errors","// Debug token transfer failure
1947
+ async function debugTokenTransfer(txId: string) {
1948
+ const tx = await fetch(`https://api.hiro.so/extended/v1/tx/${txId}`).then(r => r.json());
1949
+
1950
+ if (tx.tx_status === 'abort_by_post_condition') {
1951
+ return {error: 'insufficient_balance', fix: 'Check token balance'};
1952
+ }
1953
+
1954
+ if (tx.tx_result?.repr?.includes('err u1')) {
1955
+ return {error: 'not_authorized', fix: 'You do not own these tokens'};
1956
+ }
1957
+
1958
+ if (tx.tx_result?.repr?.includes('err u3')) {
1959
+ return {error: 'insufficient_allowance', fix: 'Increase token allowance'};
1960
+ }
1961
+
1962
+ return {error: 'unknown', fix: 'Check transaction on explorer'};
1963
+ }",Production token transfer debugging. Parses common SIP-010 error codes and provides actionable fixes.,"{""senderAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""recipientAddress"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE"", ""amount"": 5000000, ""tokenContract"": ""arkadiko-token""}","{""diagnosticSteps"": 7, ""contractExists"": true, ""senderBalance"": 10000000, ""sufficientBalance"": true, ""validRecipient"": true, ""transfersNotPaused"": true, ""validAmount"": true, ""txId"": ""0xabc..."", ""success"": true}","- Not checking contract exists before transfer
1964
+ - Skipping balance validation causes failed transactions
1965
+ - Using self as recipient creates logic errors
1966
+ - Missing post-conditions allows unauthorized transfers
1967
+ - Not handling paused state causes confusion
1968
+ - Ignoring contract error codes makes debugging hard
1969
+ - Wrong token asset name in post-conditions fails silently",https://explorer.hiro.so,"fungible-tokens.csv:2,fungible-tokens.csv:8,security-patterns.csv:3","debugging,transfer,errors,validation,troubleshooting",intermediate
1970
+ 28,tokens,security,secure-token-launch,"Launch a secure token with anti-rug, anti-bot protections, and fair distribution","// Secure token launch with anti-bot measures
1971
+ const SECURE_LAUNCH_CLARITY = `
1972
+ (define-constant max-mint-per-tx u1000000000) ;; 1000 tokens
1973
+ (define-map mint-cooldown principal uint)
1974
+ (define-constant cooldown-blocks u10)
1975
+
1976
+ (define-public (mint (amount uint))
1977
+ (let ((last-mint (default-to u0 (map-get? mint-cooldown tx-sender))))
1978
+ (asserts! (<= amount max-mint-per-tx) (err u100))
1979
+ (asserts! (>= block-height (+ last-mint cooldown-blocks)) (err u101))
1980
+ (try! (ft-mint? my-token amount tx-sender))
1981
+ (map-set mint-cooldown tx-sender block-height)
1982
+ (ok true)
1983
+ )
1984
+ )
1985
+ `;",Production token launch with anti-bot protection. Rate limits minting per wallet and enforces cooldown periods.,"{""saleAllocation"": 400000000000, ""liquidityAllocation"": 300000000000, ""teamAllocation"": 200000000000, ""maxBuyPerAddress"": 10000000000, ""tokenPrice"": 100, ""saleDuration"": 4380, ""liquidityLockDuration"": 52560}","{""deployTxId"": ""0xabc..."", ""saleStartTxId"": ""0xdef..."", ""liquidityLockTxId"": ""0xghi..."", ""ownershipRenouncedTxId"": ""0xjkl..."", ""liquidityUnlockBlock"": 105120, ""securityFeatures"": [""anti-rug"", ""anti-bot"", ""fair-launch"", ""time-locked-liquidity""]}","- NEVER allow owner to withdraw liquidity (rug risk)
1986
+ - NEVER skip liquidity lock (minimum 6-12 months)
1987
+ - NEVER allow unlimited purchases (whale manipulation)
1988
+ - NEVER launch without purchase cooldowns (bot protection)
1989
+ - ALWAYS audit contract before mainnet
1990
+ - ALWAYS test on testnet with realistic scenarios
1991
+ - ALWAYS communicate security measures to community",https://github.com/citycoins/citycoin/blob/main/contracts/core/citycoin-core-v2.clar,"fungible-tokens.csv:9,fungible-tokens.csv:14,security-patterns.csv:1,security-patterns.csv:9,advanced-patterns.csv:15","security,launch,anti-rug,anti-bot,fair-launch,liquidity-lock,advanced",advanced
1992
+ 29,security,security,reentrancy-attack-prevention,Prevent reentrancy attacks using checks-effects-interactions pattern to avoid exploitation similar to the famous DAO hack,"// Production reentrancy prevention pattern
1993
+ // From security best practices and DeFi protocols
1994
+
1995
+ // Clarity: Reentrancy guard using state flags
1996
+ const REENTRANCY_GUARD_CLARITY = \`
1997
+ ;; State flag to prevent reentrancy
1998
+ (define-data-var locked bool false)
1999
+
2000
+ (define-private (check-and-lock)
2001
+ (begin
2002
+ (asserts! (not (var-get locked)) (err u403))
2003
+ (var-set locked true)
2004
+ (ok true)
2005
+ )
2006
+ )
2007
+
2008
+ (define-private (unlock)
2009
+ (var-set locked false)
2010
+ )
2011
+
2012
+ ;; Protected function using guard
2013
+ (define-public (withdraw (amount uint))
2014
+ (begin
2015
+ ;; Check and set lock
2016
+ (try! (check-and-lock))
2017
+
2018
+ ;; Perform external call (risky operation)
2019
+ (try! (as-contract (stx-transfer? amount tx-sender (var-get msg-sender))))
2020
+
2021
+ ;; Always unlock before exit
2022
+ (unlock)
2023
+ (ok true)
2024
+ )
2025
+ )
2026
+
2027
+ ;; VULNERABLE PATTERN (DO NOT USE)
2028
+ (define-public (vulnerable-withdraw (amount uint))
2029
+ (begin
2030
+ ;; External call before state update (DANGEROUS!)
2031
+ (try! (as-contract (stx-transfer? amount tx-sender (var-get msg-sender))))
2032
+
2033
+ ;; Update state after external call
2034
+ ;; Attacker can re-enter here!
2035
+ (map-set balances { user: tx-sender } (- balance amount))
2036
+ (ok true)
2037
+ )
2038
+ )
2039
+ \`;
2040
+
2041
+ // JavaScript: Safe withdraw pattern
2042
+ import { request } from '@stacks/connect';
2043
+ import { uintCV, cvToHex, Pc, PostConditionMode } from '@stacks/transactions';
2044
+
2045
+ async function safeWithdraw(
2046
+ user: string,
2047
+ protocolContract: string,
2048
+ amount: number
2049
+ ) {
2050
+ // Post-conditions prevent reentrancy attacks
2051
+ const postConditions = [
2052
+ // Protocol can only send exact amount
2053
+ Pc.principal(protocolContract).willSendEq(BigInt(amount)).ustx(),
2054
+ // User must receive exact amount
2055
+ Pc.principal(user).willReceiveEq(BigInt(amount)).ustx()
2056
+ ];
2057
+
2058
+ return request('stx_callContract', {
2059
+ contract: protocolContract,
2060
+ functionName: 'withdraw',
2061
+ functionArgs: [uintCV(amount)].map(cvToHex),
2062
+ postConditionMode: 'deny', // CRITICAL: Must use deny mode
2063
+ postConditions,
2064
+ network: 'mainnet'
2065
+ });
2066
+ }",Production reentrancy prevention using state lock pattern. Critical security pattern for DeFi. Uses locked flag to prevent re-entrant calls during external interactions.,"{""user"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""protocolContract"": ""SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.defi-protocol-v1"", ""withdrawAmount"": 10000000, ""userBalance"": 50000000, ""isLocked"": false}","{""txId"": ""0x123..."", ""success"": true, ""amountWithdrawn"": 10000000, ""reentrancyPrevented"": true, ""lockReleased"": true}","- Not implementing reentrancy guard for external calls
2067
+ - Forgetting to unlock after success (permanent lock)
2068
+ - Not using checks-effects-interactions pattern
2069
+ - Missing post-conditions allows theft during reentrancy
2070
+ - Using block-height as reentrancy check (insufficient)",https://docs.stacks.co/clarity/security,"security-patterns.csv:1,security-patterns.csv:4,clarity-syntax.csv:25,stacks-js-core.csv:12","security,reentrancy,dao-hack,checks-effects-interactions,state-management,vulnerability,advanced",advanced
2071
+ 30,security,security,integer-overflow-protection,Prevent integer overflow and underflow vulnerabilities using safe math operations and bounds checking,"// Production integer overflow/underflow prevention
2072
+ // Pattern from DeFi protocols and token contracts
2073
+
2074
+ // Clarity: Safe math operations
2075
+ const SAFE_MATH_CLARITY = `
2076
+ ;; Clarity has built-in overflow protection for all arithmetic
2077
+ ;; But here are best practices:
2078
+
2079
+ (define-constant MAX-UINT u340282366920938463463374607431768211455)
2080
+ (define-constant ERR-OVERFLOW (err u300))
2081
+ (define-constant ERR-UNDERFLOW (err u301))
2082
+
2083
+ ;; Safe addition with explicit check
2084
+ (define-private (safe-add (a uint) (b uint))
2085
+ (let ((result (+ a b)))
2086
+ (asserts! (>= result a) ERR-OVERFLOW)
2087
+ (ok result)
2088
+ )
2089
+ )
2090
+
2091
+ ;; Safe subtraction with explicit check
2092
+ (define-private (safe-sub (a uint) (b uint))
2093
+ (begin
2094
+ (asserts! (>= a b) ERR-UNDERFLOW)
2095
+ (ok (- a b))
2096
+ )
2097
+ )
2098
+
2099
+ ;; Safe multiplication with overflow check
2100
+ (define-private (safe-mul (a uint) (b uint))
2101
+ (let ((result (* a b)))
2102
+ (asserts! (or (is-eq a u0) (is-eq (/ result a) b)) ERR-OVERFLOW)
2103
+ (ok result)
2104
+ )
2105
+ )
2106
+
2107
+ ;; Safe division (checks for division by zero)
2108
+ (define-private (safe-div (a uint) (b uint))
2109
+ (begin
2110
+ (asserts! (> b u0) (err u302))
2111
+ (ok (/ a b))
2112
+ )
2113
+ )
2114
+
2115
+ ;; Example: Safe token transfer calculation
2116
+ (define-public (transfer-with-fee (amount uint) (fee-bps uint))
2117
+ (let (
2118
+ (fee (unwrap! (safe-mul amount fee-bps) (err u500)))
2119
+ (fee-final (unwrap! (safe-div fee u10000) (err u500)))
2120
+ (amount-after-fee (unwrap! (safe-sub amount fee-final) (err u500)))
2121
+ )
2122
+ (asserts! (> amount-after-fee u0) (err u400))
2123
+ ;; Transfer logic here
2124
+ (ok amount-after-fee)
2125
+ )
2126
+ )
2127
+ `;
2128
+
2129
+ // JavaScript: Validation before contract calls
2130
+ import { request } from '@stacks/connect';
2131
+ import { uintCV, cvToHex } from '@stacks/transactions';
2132
+
2133
+ // Maximum safe integer in Clarity (uint128)
2134
+ const MAX_UINT128 = BigInt('340282366920938463463374607431768211455');
2135
+
2136
+ function validateAmount(amount: number | bigint): void {
2137
+ const amountBig = BigInt(amount);
2138
+
2139
+ if (amountBig < 0) {
2140
+ throw new Error('Amount cannot be negative');
2141
+ }
2142
+
2143
+ if (amountBig > MAX_UINT128) {
2144
+ throw new Error(`Amount exceeds maximum: ${MAX_UINT128}`);
2145
+ }
2146
+ }
2147
+
2148
+ async function safeTransferWithFee(
2149
+ sender: string,
2150
+ recipient: string,
2151
+ tokenContract: string,
2152
+ amount: number,
2153
+ feeBps: number
2154
+ ) {
2155
+ // Validate inputs
2156
+ validateAmount(amount);
2157
+ validateAmount(feeBps);
2158
+
2159
+ // Check fee calculation won't overflow
2160
+ const fee = (BigInt(amount) * BigInt(feeBps)) / BigInt(10000);
2161
+ if (fee > BigInt(amount)) {
2162
+ throw new Error('Fee calculation error');
2163
+ }
2164
+
2165
+ const amountAfterFee = BigInt(amount) - fee;
2166
+ if (amountAfterFee <= 0) {
2167
+ throw new Error('Amount too small for fee');
2168
+ }
2169
+
2170
+ const args = [
2171
+ uintCV(amount),
2172
+ uintCV(feeBps),
2173
+ principalCV(recipient)
2174
+ ];
2175
+
2176
+ return request('stx_callContract', {
2177
+ contract: tokenContract,
2178
+ functionName: 'transfer-with-fee',
2179
+ functionArgs: args.map(cvToHex),
2180
+ postConditionMode: 'deny',
2181
+ network: 'mainnet'
2182
+ });
2183
+ }","Production integer overflow prevention. Clarity has built-in overflow protection, but explicit checks improve error messages. Shows safe math operations and validation patterns.","{""amount"": 1000000000, ""feeBps"": 500, ""maxUint128"": ""340282366920938463463374607431768211455""}","{""txId"": ""0x123..."", ""success"": true, ""amountAfterFee"": 950000000, ""feeAmount"": 50000000, ""overflowChecked"": true}","- Trusting JavaScript Number type (loses precision above 2^53)
2184
+ - Not using BigInt for large amounts
2185
+ - Forgetting Clarity's uint128 max value
2186
+ - Missing checks on user input before contract calls
2187
+ - Not handling edge cases (0, max value)",https://docs.stacks.co/clarity/security,"security-patterns.csv:2,clarity-syntax.csv:8,clarity-syntax.csv:14","security,overflow,underflow,safe-math,arithmetic,vulnerability,intermediate",intermediate
2188
+ 31,security,best-practice,access-control-pattern,"Implement role-based access control with owner, admin, and operator roles to secure privileged functions","// Production access control with role-based permissions
2189
+ // From stacksagent-backend and DeFi protocol patterns
2190
+
2191
+ // Clarity: Multi-level RBAC
2192
+ const RBAC_CLARITY = `
2193
+ (define-constant ERR-NOT-OWNER (err u200))
2194
+ (define-constant ERR-NOT-ADMIN (err u201))
2195
+ (define-constant ERR-NOT-OPERATOR (err u202))
2196
+ (define-constant ERR-UNAUTHORIZED (err u203))
2197
+
2198
+ ;; Owner (highest privilege)
2199
+ (define-data-var contract-owner principal tx-sender)
2200
+
2201
+ ;; Admins (can manage operators)
2202
+ (define-map admins principal bool)
2203
+
2204
+ ;; Operators (can execute operations)
2205
+ (define-map operators principal bool)
2206
+
2207
+ ;; Role checks
2208
+ (define-read-only (is-owner)
2209
+ (is-eq tx-sender (var-get contract-owner))
2210
+ )
2211
+
2212
+ (define-read-only (is-admin (user principal))
2213
+ (or (is-owner) (default-to false (map-get? admins user)))
2214
+ )
2215
+
2216
+ (define-read-only (is-operator (user principal))
2217
+ (or (is-admin user) (default-to false (map-get? operators user)))
2218
+ )
2219
+
2220
+ ;; Owner-only: Transfer ownership
2221
+ (define-public (transfer-ownership (new-owner principal))
2222
+ (begin
2223
+ (asserts! (is-owner) ERR-NOT-OWNER)
2224
+ (asserts! (not (is-eq new-owner (var-get contract-owner))) (err u204))
2225
+ (var-set contract-owner new-owner)
2226
+ (print {event: ""ownership-transferred"", new-owner: new-owner})
2227
+ (ok true)
2228
+ )
2229
+ )
2230
+
2231
+ ;; Owner-only: Manage admins
2232
+ (define-public (add-admin (admin principal))
2233
+ (begin
2234
+ (asserts! (is-owner) ERR-NOT-OWNER)
2235
+ (map-set admins admin true)
2236
+ (print {event: ""admin-added"", admin: admin})
2237
+ (ok true)
2238
+ )
2239
+ )
2240
+
2241
+ ;; Admin: Manage operators
2242
+ (define-public (add-operator (operator principal))
2243
+ (begin
2244
+ (asserts! (is-admin tx-sender) ERR-NOT-ADMIN)
2245
+ (map-set operators operator true)
2246
+ (print {event: ""operator-added"", operator: operator})
2247
+ (ok true)
2248
+ )
2249
+ )
2250
+
2251
+ ;; Operator: Execute operations
2252
+ (define-public (execute-operation (action (string-ascii 50)))
2253
+ (begin
2254
+ (asserts! (is-operator tx-sender) ERR-UNAUTHORIZED)
2255
+ (print {event: ""operation-executed"", action: action, by: tx-sender})
2256
+ (ok true)
2257
+ )
2258
+ )
2259
+ `;
2260
+
2261
+ // JavaScript: Check roles before operations
2262
+ import { callReadOnlyFunction, cvToJSON, principalCV } from '@stacks/transactions';
2263
+
2264
+ async function checkUserRole(
2265
+ userAddress: string,
2266
+ contractAddress: string
2267
+ ): Promise<{isOwner: boolean, isAdmin: boolean, isOperator: boolean}> {
2268
+ const [address, name] = contractAddress.split('.');
2269
+
2270
+ // Check owner
2271
+ const ownerResult = await callReadOnlyFunction({
2272
+ contractAddress: address,
2273
+ contractName: name,
2274
+ functionName: 'is-owner',
2275
+ functionArgs: [],
2276
+ senderAddress: userAddress,
2277
+ network: 'mainnet'
2278
+ });
2279
+ const isOwner = cvToJSON(ownerResult).value;
2280
+
2281
+ // Check admin
2282
+ const adminResult = await callReadOnlyFunction({
2283
+ contractAddress: address,
2284
+ contractName: name,
2285
+ functionName: 'is-admin',
2286
+ functionArgs: [principalCV(userAddress)],
2287
+ senderAddress: userAddress,
2288
+ network: 'mainnet'
2289
+ });
2290
+ const isAdmin = cvToJSON(adminResult).value;
2291
+
2292
+ // Check operator
2293
+ const operatorResult = await callReadOnlyFunction({
2294
+ contractAddress: address,
2295
+ contractName: name,
2296
+ functionName: 'is-operator',
2297
+ functionArgs: [principalCV(userAddress)],
2298
+ senderAddress: userAddress,
2299
+ network: 'mainnet'
2300
+ });
2301
+ const isOperator = cvToJSON(operatorResult).value;
2302
+
2303
+ return { isOwner, isAdmin, isOperator };
2304
+ }","Role-based access control (RBAC) separates privileges into hierarchical roles: Owner (highest, usually deployer), Admins (trusted managers), Operators (day-to-day functions). Use tx-sender for authentication, NEVER trust function parameters for access checks. Implement read-only helpers (is-owner, is-admin) for reusability. Use asserts! at start of functions to fail fast. Define clear error codes per role. Consider two-step ownership transfer for safety. Document which functions require which roles. ALWAYS validate tx-sender, not contract-caller for security.","{""ownerAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""adminAddress"": ""SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE"", ""operatorAddress"": ""SP1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE"", ""unauthorizedAddress"": ""SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7""}","{""ownerCanCallAny"": true, ""adminsCanCallAdminFunctions"": true, ""operatorsCanCallOperatorFunctions"": true, ""roleChecksBeforeLogic"": true, ""clearErrorMessages"": true}","- ALWAYS check tx-sender, NOT contract-caller
2305
+ - DEFINE clear role hierarchy (owner > admin > operator)
2306
+ - USE read-only helpers for role checks
2307
+ - FAIL FAST with asserts! at function start
2308
+ - NEVER trust user-provided principal for auth
2309
+ - IMPLEMENT two-step ownership transfer for safety
2310
+ - LOG all privilege changes
2311
+ - DOCUMENT role requirements clearly",https://github.com/OpenZeppelin/cairo-contracts/blob/main/src/openzeppelin/access/accesscontrol/library.cairo,"security-patterns.csv:3,clarity-syntax.csv:31,stacks-js-core.csv:8","security,access-control,rbac,authorization,permissions,best-practice,intermediate",intermediate
2312
+ 32,security,best-practice,input-validation,"Validate and sanitize all user inputs with bounds checking, type validation, and business logic constraints","// Input validation comprehensive
2313
+ const INPUT_VALIDATION_CLARITY = `
2314
+ (define-constant ERR-INVALID-AMOUNT (err u400))
2315
+ (define-constant ERR-INVALID-ADDRESS (err u401))
2316
+ (define-constant ERR-INVALID-STRING (err u402))
2317
+
2318
+ (define-private (validate-amount (amount uint))
2319
+ (begin
2320
+ (asserts! (> amount u0) ERR-INVALID-AMOUNT)
2321
+ (asserts! (< amount u1000000000000) ERR-INVALID-AMOUNT)
2322
+ (ok amount)
2323
+ )
2324
+ )
2325
+
2326
+ (define-private (validate-address (addr principal))
2327
+ (begin
2328
+ (asserts! (not (is-eq addr 'SP000000000000000000002Q6VF78)) ERR-INVALID-ADDRESS)
2329
+ (asserts! (not (is-eq addr tx-sender)) ERR-INVALID-ADDRESS)
2330
+ (ok addr)
2331
+ )
2332
+ )
2333
+
2334
+ (define-public (transfer (amount uint) (recipient principal))
2335
+ (begin
2336
+ (try! (validate-amount amount))
2337
+ (try! (validate-address recipient))
2338
+ (ft-transfer? my-token amount tx-sender recipient)
2339
+ )
2340
+ )
2341
+ `;","Production input validation patterns. Validates amounts, addresses, strings, and business logic constraints before execution.","{""invalidAmounts"": [0, 500, 999999999999999999], ""validAmount"": 1000000, ""invalidAddress"": ""SP000000000000000000002Q6VF78"", ""selfTransferAddress"": ""tx-sender"", ""emptyString"": """", ""longString"": ""aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"", ""validString"": ""valid memo"", ""emptyList"": [], ""largeList"": [""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item"", ""item""], ""invalidPercentage"": 10001}","{""zeroAmountError"": ""ERR-INVALID-AMOUNT"", ""lowAmountError"": ""ERR-INVALID-AMOUNT"", ""highAmountError"": ""ERR-OVERFLOW"", ""invalidAddressError"": ""ERR-INVALID-ADDRESS"", ""selfTransferError"": ""ERR-INVALID-ADDRESS"", ""emptyStringError"": ""ERR-INVALID-STRING"", ""longStringError"": ""ERR-INVALID-STRING"", ""emptyListError"": ""ERR-OUT-OF-BOUNDS"", ""largeListError"": ""ERR-OUT-OF-BOUNDS"", ""invalidPercentageError"": ""ERR-INVALID-PERCENTAGE"", ""validInputsSuccess"": true}","- VALIDATE all inputs before state changes
2342
+ - CHECK bounds (min/max) for numeric values
2343
+ - VERIFY principal addresses (not zero, not self)
2344
+ - LIMIT string lengths to prevent DoS
2345
+ - VALIDATE list lengths before iteration
2346
+ - USE helper functions for reusability
2347
+ - DEFINE clear validation constants
2348
+ - CLIENT validation is UX, not security",https://github.com/crytic/building-secure-contracts/blob/master/development-guidelines/token_integration.md,"security-patterns.csv:5,clarity-syntax.csv:17,clarity-syntax.csv:33","security,validation,input-sanitization,bounds-checking,best-practice,intermediate",intermediate
2349
+ 33,security,security,rate-limiting-dos-protection,"Prevent Denial of Service attacks using rate limiting, gas accounting, and complexity bounds","// Rate limiting for DoS protection
2350
+ const RATE_LIMIT_CLARITY = `
2351
+ (define-map rate-limits principal {count: uint, window-start: uint})
2352
+ (define-constant max-calls-per-window u10)
2353
+ (define-constant window-blocks u144) ;; ~24 hours
2354
+
2355
+ (define-private (check-rate-limit)
2356
+ (let (
2357
+ (limit (default-to {count: u0, window-start: block-height}
2358
+ (map-get? rate-limits tx-sender)))
2359
+ (blocks-passed (- block-height (get window-start limit)))
2360
+ )
2361
+ (if (>= blocks-passed window-blocks)
2362
+ (begin
2363
+ (map-set rate-limits tx-sender {count: u1, window-start: block-height})
2364
+ (ok true)
2365
+ )
2366
+ (begin
2367
+ (asserts! (< (get count limit) max-calls-per-window) (err u429))
2368
+ (map-set rate-limits tx-sender
2369
+ (merge limit {count: (+ (get count limit) u1)}))
2370
+ (ok true)
2371
+ )
2372
+ )
2373
+ )
2374
+ )
2375
+ `;",Production rate limiting to prevent DoS attacks. Tracks calls per wallet in rolling time window and enforces limits.,"{""action_11_same_block"": ""11th action"", ""expensive_op_cooldown"": ""within 6 blocks"", ""batch_size_51"": ""51 items"", ""global_actions_1001"": ""1001st action"", ""normal_action"": ""1st action""}","{""rate_limit_exceeded"": ""err-u301 rate-limit"", ""cooldown_active"": ""err-u302 cooldown-active"", ""batch_too_large"": ""err-u303 too-complex"", ""global_limit"": ""err-u301 rate-limit"", ""normal_success"": ""ok true""}","- IMPLEMENT per-user AND global rate limits
2376
+ - USE block-height for automatic counter resets
2377
+ - ENFORCE maximum batch sizes (e.g., 50)
2378
+ - ADD cooldowns for expensive operations
2379
+ - NEVER allow unbounded loops
2380
+ - TRACK gas consumption awareness
2381
+ - CONSIDER economic costs as deterrent
2382
+ - MONITOR for abuse patterns off-chain",https://consensys.github.io/smart-contract-best-practices/attacks/denial-of-service/,"security-patterns.csv:7,clarity-syntax.csv:42,advanced-patterns.csv:8","security,dos-protection,rate-limiting,gas-optimization,complexity-bounds,vulnerability,advanced",advanced
2383
+ 34,security,security,secure-randomness,Generate secure randomness using VRF (Verifiable Random Function) instead of predictable block properties,"// Secure randomness using VRF
2384
+ const SECURE_RANDOM_CLARITY = `
2385
+ (define-data-var nonce uint u0)
2386
+
2387
+ (define-read-only (get-pseudo-random (max uint))
2388
+ (let (
2389
+ (seed (buff-to-uint-be (hash-sha256 (concat
2390
+ (unwrap-panic (to-consensus-buff? block-height))
2391
+ (unwrap-panic (to-consensus-buff? (var-get nonce)))
2392
+ ))))
2393
+ )
2394
+ (ok (mod seed max))
2395
+ )
2396
+ )
2397
+
2398
+ (define-public (use-randomness)
2399
+ (let ((random (unwrap! (get-pseudo-random u100) (err u500))))
2400
+ (var-set nonce (+ (var-get nonce) u1))
2401
+ (ok random)
2402
+ )
2403
+ )
2404
+ `;","Production secure randomness using block hash + nonce. Not truly random but prevents prediction. For critical randomness, use VRF oracles.","{""vulnerable_block_hash"": ""height 12345"", ""vulnerable_tx_sender"": ""attacker controlled"", ""commit_reveal"": ""10 participants, 6 block delay"", ""vrf_proof"": ""valid VRF proof with public key""}","{""block_hash_result"": ""predictable, miner manipulable"", ""tx_sender_result"": ""attacker selects favorable address"", ""commit_reveal_result"": ""secure if majority honest"", ""vrf_result"": ""cryptographically secure, verifiable"", ""random_range"": ""0-99 uniform distribution""}","- NEVER use block-hash for randomness
2405
+ - NEVER use tx-sender for randomness
2406
+ - USE VRF with verifiable proofs (best)
2407
+ - IMPLEMENT commit-reveal with delay
2408
+ - COMBINE multiple randomness sources
2409
+ - REQUIRE reveal delay (6+ blocks)
2410
+ - VERIFY VRF proofs on-chain
2411
+ - CONSIDER oracle integration for VRF",https://blog.chain.link/chainlink-vrf-on-chain-verifiable-randomness/,"security-patterns.csv:10,clarity-syntax.csv:47,oracles.csv:15","security,randomness,vrf,commit-reveal,lottery,vulnerability,advanced",advanced
2412
+ 35,security,security,privilege-escalation-prevention,"Prevent privilege escalation attacks through proper role validation, state management, and ownership transfer patterns","// Privilege escalation prevention
2413
+ const PRIVILEGE_ESCALATION_CLARITY = `
2414
+ (define-constant ERR-PRIVILEGE-ESCALATION (err u403))
2415
+
2416
+ (define-map user-roles principal (string-ascii 20))
2417
+
2418
+ (define-public (promote-user (user principal) (new-role (string-ascii 20)))
2419
+ (let (
2420
+ (caller-role (default-to ""none"" (map-get? user-roles tx-sender)))
2421
+ (target-role (default-to ""none"" (map-get? user-roles user)))
2422
+ )
2423
+ ;; Prevent self-promotion
2424
+ (asserts! (not (is-eq tx-sender user)) ERR-PRIVILEGE-ESCALATION)
2425
+
2426
+ ;; Prevent promoting to same or higher role
2427
+ (asserts! (not (is-eq caller-role target-role)) ERR-PRIVILEGE-ESCALATION)
2428
+
2429
+ ;; Only admin can promote to admin
2430
+ (if (is-eq new-role ""admin"")
2431
+ (asserts! (is-eq caller-role ""owner"") ERR-PRIVILEGE-ESCALATION)
2432
+ true
2433
+ )
2434
+
2435
+ (map-set user-roles user new-role)
2436
+ (ok true)
2437
+ )
2438
+ )
2439
+ `;",Production privilege escalation prevention. Users cannot promote themselves or promote others to equal/higher roles.,"{""vulnerable_transfer"": ""SP_TYPO_ADDRESS"", ""vulnerable_escalation"": ""admin adds self as owner"", ""secure_transfer"": ""two-step with acceptance"", ""timelock_execution"": ""before delay period"", ""role_escalation"": ""operator tries admin role""}","{""vulnerable_transfer"": ""ownership lost permanently"", ""vulnerable_escalation"": ""admin becomes owner"", ""secure_transfer"": ""pending until acceptance"", ""timelock_fail"": ""err-u609 before delay"", ""role_fail"": ""err-u605 insufficient level"", ""events"": ""all privilege changes logged""}","- ALWAYS use two-step ownership transfer
2440
+ - REQUIRE explicit acceptance from new owner
2441
+ - IMPLEMENT role hierarchy (level-based)
2442
+ - USE timelocks for critical upgrades (24+ hours)
2443
+ - PREVENT admins from self-elevation
2444
+ - LOG all privilege changes with events
2445
+ - ALLOW ownership transfer cancellation
2446
+ - NEVER skip validation for ""trusted"" users",https://docs.openzeppelin.com/contracts/4.x/api/access,"security-patterns.csv:3,security-patterns.csv:6,clarity-syntax.csv:31","security,privilege-escalation,ownership,timelock,role-hierarchy,vulnerability,advanced",advanced
2447
+ 36,auth,quickstart,wallet-connect-flow,Complete wallet connection flow with address retrieval for STX and BTC addresses,"// Production wallet connection pattern from sbtc-market-frontend
2448
+ export async function connectWallet() {
2449
+ const { connect, isConnected, getLocalStorage } = await import(""@stacks/connect"");
2450
+
2451
+ // Check if already connected
2452
+ if (isConnected()) {
2453
+ const userData = getLocalStorage();
2454
+ console.log('Already authenticated');
2455
+
2456
+ // Access STX and BTC addresses
2457
+ if (userData?.addresses) {
2458
+ const stxAddress = userData.addresses.stx?.[0]?.address;
2459
+ const btcAddress = userData.addresses.btc?.[0]?.address;
2460
+ console.log('STX Address:', stxAddress);
2461
+ console.log('BTC Address:', btcAddress);
2462
+
2463
+ return { addresses: userData.addresses };
2464
+ }
2465
+ }
2466
+
2467
+ // Connect if not connected
2468
+ return connect({
2469
+ onFinish: (payload) => {
2470
+ console.log('Connected:', payload.addresses);
2471
+ },
2472
+ });
2473
+ }
2474
+
2475
+ // Helper: Get just the STX address
2476
+ export async function resolveStxAddress() {
2477
+ const { isConnected, getLocalStorage } = await import(""@stacks/connect"");
2478
+ if (!isConnected()) return null;
2479
+
2480
+ const data = getLocalStorage();
2481
+ return data?.addresses?.stx?.[0]?.address || null;
2482
+ }
2483
+
2484
+ // Helper: Check connection status
2485
+ export async function isWalletConnected() {
2486
+ const { isConnected } = await import(""@stacks/connect"");
2487
+ return isConnected();
2488
+ }","Production wallet connection pattern from sbtc-market-frontend. Uses isConnected() to check before connecting, getLocalStorage() to retrieve addresses (both STX and BTC), and proper error handling.","{""walletInstalled"": true, ""userApproved"": true}","{""connected"": true, ""stxAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""btcAddress"": ""bc1q..."", ""addressesObject"": {""stx"": [{""address"": ""SP2C2YFP...""}], ""btc"": [{""address"": ""bc1q...""}]}}","- Not checking isConnected() before calling connect() causes unnecessary wallet popups
2489
+ - Forgetting to handle case when wallet extension is not installed
2490
+ - Not accessing userData.addresses correctly (it's nested: addresses.stx[0].address)
2491
+ - Using deprecated showConnect() instead of connect()",https://github.com/your-username/sbtc-market-frontend/blob/main/src/lib/wallet.ts,"authentication.csv:1,authentication.csv:2,authentication.csv:4,stacks-js-core.csv:1,stacks-js-core.csv:5","authentication,wallet,connect,quickstart,react,beginner",beginner
2492
+ 37,auth,integration,jwt-authentication,Generate and verify JWT tokens from wallet signatures for secure server-side authentication,"// Production JWT authentication via wallet signature
2493
+ // From stacksagent-frontend/src/components/auth/WalletAuthenticator.tsx
2494
+
2495
+ import { request } from '@stacks/connect';
2496
+
2497
+ async function authenticateWalletWithJWT() {
2498
+ // 1. Generate timestamp and message
2499
+ const timestamp = Date.now();
2500
+ const message = `Authenticate with StacksAgent: ${timestamp}`;
2501
+
2502
+ try {
2503
+ // 2. Request signature from wallet
2504
+ const response = await request('stx_signMessage', {
2505
+ message,
2506
+ });
2507
+
2508
+ const signature = response.signature;
2509
+ const publicKey = response.publicKey;
2510
+
2511
+ console.log('Signature obtained:', signature.substring(0, 20) + '...');
2512
+
2513
+ // 3. Send to backend for JWT generation
2514
+ const authResponse = await fetch('/api/auth/authenticate', {
2515
+ method: 'POST',
2516
+ headers: { 'Content-Type': 'application/json' },
2517
+ body: JSON.stringify({
2518
+ message,
2519
+ signature,
2520
+ publicKey,
2521
+ timestamp
2522
+ })
2523
+ });
2524
+
2525
+ if (!authResponse.ok) {
2526
+ throw new Error('Server could not verify signature');
2527
+ }
2528
+
2529
+ // 4. Get JWT token from response
2530
+ const { token, expiresAt } = await authResponse.json();
2531
+
2532
+ // 5. Store JWT (httpOnly cookie preferred, or localStorage)
2533
+ document.cookie = `auth_token=${token}; path=/; secure; samesite=strict`;
2534
+
2535
+ console.log('✅ Authenticated successfully');
2536
+ console.log('Token expires:', new Date(expiresAt));
2537
+
2538
+ return { success: true, token, expiresAt };
2539
+
2540
+ } catch (error) {
2541
+ if (error.message.includes('User denied')) {
2542
+ console.error('User cancelled signature request');
2543
+ } else {
2544
+ console.error('Authentication failed:', error.message);
2545
+ }
2546
+ return { success: false, error: error.message };
2547
+ }
2548
+ }
2549
+
2550
+ // Backend verification (Node.js/Express example)
2551
+ /*
2552
+ import { verifyMessageSignatureRsv } from '@stacks/encryption';
2553
+ import jwt from 'jsonwebtoken';
2554
+
2555
+ app.post('/api/auth/authenticate', (req, res) => {
2556
+ const { message, signature, publicKey, timestamp } = req.body;
2557
+
2558
+ // 1. Verify timestamp freshness (within 5 minutes)
2559
+ const now = Date.now();
2560
+ if (Math.abs(now - timestamp) > 5 * 60 * 1000) {
2561
+ return res.status(401).json({ error: 'Timestamp expired' });
2562
+ }
2563
+
2564
+ // 2. Verify signature
2565
+ const isValid = verifyMessageSignatureRsv({ message, publicKey, signature });
2566
+ if (!isValid) {
2567
+ return res.status(401).json({ error: 'Invalid signature' });
2568
+ }
2569
+
2570
+ // 3. Generate JWT
2571
+ const walletAddress = publicKeyToAddress(publicKey);
2572
+ const token = jwt.sign(
2573
+ { address: walletAddress, publicKey },
2574
+ process.env.JWT_SECRET,
2575
+ { expiresIn: '24h' }
2576
+ );
2577
+
2578
+ return res.json({
2579
+ token,
2580
+ expiresAt: Date.now() + 24 * 60 * 60 * 1000
2581
+ });
2582
+ });
2583
+ */","Production JWT authentication pattern from stacksagent-frontend. User signs a timestamped message with their wallet, backend verifies signature and returns JWT token. Includes both frontend and backend code.","{""message"": ""Authenticate with StacksAgent: 1704067200000"", ""walletConnected"": true, ""userApprovesSignature"": true}","{""success"": true, ""token"": ""eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."", ""expiresAt"": 1704153600000, ""signature"": ""0x1234567890abcdef..."", ""publicKey"": ""03abcdef1234567890...""}","- Not validating timestamp freshness on backend (replay attacks)
2584
+ - Storing JWT in localStorage instead of httpOnly cookies (XSS vulnerability)
2585
+ - Not handling user rejection of signature request
2586
+ - Using weak JWT_SECRET or hardcoding it
2587
+ - Not verifying signature correctly on backend",https://github.com/your-username/stacksagent-frontend/blob/main/src/components/auth/WalletAuthenticator.tsx,"authentication.csv:1,authentication.csv:7,authentication.csv:8,stacks-js-core.csv:47,security-patterns.csv:1","authentication,jwt,signature,security,server-side,intermediate",intermediate
2588
+ 38,auth,integration,protected-routes,Implement authentication guards for protected routes with automatic redirection and session validation,"// Production protected routes with wallet auth
2589
+ // Pattern from STX City deploy-stx-city
2590
+
2591
+ import { connect, getLocalStorage } from '@stacks/connect';
2592
+ import { create } from 'zustand';
2593
+ import { persist } from 'zustand/middleware';
2594
+
2595
+ // Zustand store with localStorage persistence
2596
+ interface StacksStore {
2597
+ userData: {
2598
+ profile: {
2599
+ stxAddress: { mainnet: string; testnet: string };
2600
+ btcAddress: { mainnet: string; testnet: string };
2601
+ }
2602
+ };
2603
+ isAuthenticated: boolean;
2604
+ handleLogIn: () => Promise<void>;
2605
+ }
2606
+
2607
+ const useStacksStore = create<StacksStore>()(
2608
+ persist(
2609
+ (set) => ({
2610
+ userData: {
2611
+ profile: {
2612
+ stxAddress: { mainnet: '', testnet: '' },
2613
+ btcAddress: { mainnet: '', testnet: '' }
2614
+ }
2615
+ },
2616
+ isAuthenticated: false,
2617
+
2618
+ async handleLogIn() {
2619
+ const connectResponse = await connect();
2620
+ const localData = getLocalStorage();
2621
+
2622
+ set({
2623
+ userData: {
2624
+ profile: {
2625
+ stxAddress: {
2626
+ mainnet: localData.addresses.stx[0].address,
2627
+ testnet: localData.addresses.stx[0].address
2628
+ },
2629
+ btcAddress: {
2630
+ mainnet: localData.addresses.btc[0].address,
2631
+ testnet: localData.addresses.btc[0].address
2632
+ }
2633
+ }
2634
+ },
2635
+ isAuthenticated: true
2636
+ });
2637
+ }
2638
+ }),
2639
+ {
2640
+ name: 'stacks-storage',
2641
+ getStorage: () => localStorage
2642
+ }
2643
+ )
2644
+ );
2645
+
2646
+ // React Router protected route component
2647
+ import { Navigate } from 'react-router-dom';
2648
+
2649
+ function ProtectedRoute({ children }: { children: React.ReactNode }) {
2650
+ const { isAuthenticated, userData } = useStacksStore();
2651
+
2652
+ // Check if user is authenticated
2653
+ if (!isAuthenticated || !userData.profile.stxAddress.mainnet) {
2654
+ // Redirect to login page
2655
+ return <Navigate to=""/login"" replace />;
2656
+ }
2657
+
2658
+ return <>{children}</>;
2659
+ }
2660
+
2661
+ // Usage in routes
2662
+ /*
2663
+ <Routes>
2664
+ <Route path=""/login"" element={<LoginPage />} />
2665
+ <Route path=""/dashboard"" element={
2666
+ <ProtectedRoute>
2667
+ <Dashboard />
2668
+ </ProtectedRoute>
2669
+ } />
2670
+ <Route path=""/profile"" element={
2671
+ <ProtectedRoute>
2672
+ <ProfilePage />
2673
+ </ProtectedRoute>
2674
+ } />
2675
+ </Routes>
2676
+ */",Production protected routes from STX City using Zustand with localStorage persistence. Reconnects wallet state across reloads and guards routes with authentication checks.,"{""userConnected"": true, ""stxAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""requestedRoute"": ""/dashboard"", ""sessionPersisted"": true}","{""accessGranted"": true, ""redirectTo"": null, ""sessionRestored"": true, ""addressesAvailable"": {""stx"": ""SP2C2YFP..."", ""btc"": ""bc1q...""}}","- Not persisting state causes logout on refresh
2677
+ - Forgetting to check isAuthenticated AND address presence
2678
+ - Not using replace in Navigate causes back button issues
2679
+ - Hardcoding redirect paths instead of using location.state
2680
+ - Not handling wallet disconnection during session",https://github.com/stx-city/deploy-stx-city/blob/main/src/store/StacksStore.ts,"authentication.csv:5,authentication.csv:6,authentication.csv:8,stacks-js-core.csv:2,stacks-js-core.csv:4","authentication,routing,middleware,protected,nextjs,react,intermediate",intermediate
2681
+ 39,auth,integration,nft-token-gating,Verify NFT ownership on-chain to gate premium content and features,"// Production NFT ownership verification for gating
2682
+ // Pattern from STX City token metadata and portfolio patterns
2683
+
2684
+ import { callReadOnlyFunction, principalCV, uintCV, cvToJSON } from '@stacks/transactions';
2685
+ import axios from 'axios';
2686
+
2687
+ // Check NFT ownership using read-only function
2688
+ async function checkNFTOwnership(
2689
+ walletAddress: string,
2690
+ nftContract: string,
2691
+ tokenId: number
2692
+ ): Promise<boolean> {
2693
+ try {
2694
+ const [contractAddress, contractName] = nftContract.split('.');
2695
+
2696
+ // Call get-owner read-only function
2697
+ const result = await callReadOnlyFunction({
2698
+ contractAddress,
2699
+ contractName,
2700
+ functionName: 'get-owner',
2701
+ functionArgs: [uintCV(tokenId)],
2702
+ senderAddress: walletAddress,
2703
+ network: 'mainnet'
2704
+ });
2705
+
2706
+ const owner = cvToJSON(result).value.value;
2707
+ return owner === walletAddress;
2708
+ } catch (error) {
2709
+ console.error('NFT ownership check failed:', error);
2710
+ return false;
2711
+ }
2712
+ }
2713
+
2714
+ // Check if wallet owns any NFT from collection
2715
+ async function hasCollectionAccess(
2716
+ walletAddress: string,
2717
+ collectionContract: string
2718
+ ): Promise<boolean> {
2719
+ try {
2720
+ // Use Hiro API to get all NFT holdings
2721
+ const headers = { 'x-hiro-api-key': process.env.HIRO_API_KEY };
2722
+ const { data } = await axios.get(
2723
+ `https://api.hiro.so/extended/v1/tokens/nft/holdings?principal=${walletAddress}`,
2724
+ { headers }
2725
+ );
2726
+
2727
+ // Check if any NFT is from the target collection
2728
+ const hasNFT = data.results.some((nft: any) =>
2729
+ nft.asset_identifier.startsWith(collectionContract)
2730
+ );
2731
+
2732
+ return hasNFT;
2733
+ } catch (error) {
2734
+ console.error('Collection check failed:', error);
2735
+ return false;
2736
+ }
2737
+ }
2738
+
2739
+ // Express.js middleware for NFT-gated routes
2740
+ /*
2741
+ async function nftGateMiddleware(req, res, next) {
2742
+ const walletAddress = req.session.walletAddress;
2743
+ const requiredCollection = 'SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.stacks-punks-v2';
2744
+
2745
+ if (!walletAddress) {
2746
+ return res.status(401).json({ error: 'Not authenticated' });
2747
+ }
2748
+
2749
+ const hasAccess = await hasCollectionAccess(walletAddress, requiredCollection);
2750
+
2751
+ if (!hasAccess) {
2752
+ return res.status(403).json({
2753
+ error: 'NFT required',
2754
+ message: 'You must own a Stacks Punks NFT to access this feature'
2755
+ });
2756
+ }
2757
+
2758
+ next();
2759
+ }
2760
+
2761
+ // Protected route
2762
+ app.get('/api/premium/data', nftGateMiddleware, (req, res) => {
2763
+ res.json({ data: 'Premium content for NFT holders' });
2764
+ });
2765
+ */",Production NFT gating using both read-only contract calls and Hiro API. Verifies specific NFT ownership or collection membership for premium feature access.,"{""walletAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""nftContract"": ""SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173.stacks-punks-v2"", ""requiredTokenId"": 42, ""userOwnsToken"": true}","{""hasAccess"": true, ""ownershipVerified"": true, ""nftDetails"": {""collection"": ""Stacks Punks"", ""tokenId"": 42}, ""accessGranted"": true}","- Not handling NFT transfers (ownership can change)
2766
+ - Relying only on frontend checks (bypass via devtools)
2767
+ - Not caching results causes rate limiting on Hiro API
2768
+ - Forgetting to validate contract is legitimate NFT collection
2769
+ - Not handling NFT not found errors gracefully",https://github.com/stx-city/deploy-stx-city/blob/main/src/lib/curveDetailUtils.ts,"authentication.csv:12,nfts.csv:5,stacks-js-core.csv:38,security-patterns.csv:1","authentication,nft,token-gating,access-control,sip009,intermediate",intermediate
2770
+ 40,auth,best-practice,session-management,Implement secure server-side session storage with wallet address binding and automatic cleanup,"// Production session management with wallet binding
2771
+ // Pattern from STX City wallet auth and server security
2772
+
2773
+ import { randomBytes } from 'crypto';
2774
+ import { verifyMessageSignatureRsv } from '@stacks/encryption';
2775
+
2776
+ interface Session {
2777
+ id: string;
2778
+ walletAddress: string;
2779
+ publicKey: string;
2780
+ createdAt: number;
2781
+ expiresAt: number;
2782
+ lastActivity: number;
2783
+ }
2784
+
2785
+ // In-memory session store (use Redis in production)
2786
+ const sessions = new Map<string, Session>();
2787
+
2788
+ // Session configuration
2789
+ const SESSION_DURATION = 24 * 60 * 60 * 1000; // 24 hours
2790
+ const ACTIVITY_TIMEOUT = 30 * 60 * 1000; // 30 minutes
2791
+ const CLEANUP_INTERVAL = 60 * 60 * 1000; // 1 hour
2792
+
2793
+ // Create session after wallet signature verification
2794
+ export async function createSession(
2795
+ message: string,
2796
+ signature: string,
2797
+ publicKey: string
2798
+ ): Promise<{ sessionId: string; expiresAt: number } | null> {
2799
+ try {
2800
+ // 1. Verify signature
2801
+ const isValid = verifyMessageSignatureRsv({ message, publicKey, signature });
2802
+ if (!isValid) {
2803
+ console.error('Invalid signature');
2804
+ return null;
2805
+ }
2806
+
2807
+ // 2. Extract wallet address from public key
2808
+ const { publicKeyToAddress } = await import('@stacks/transactions');
2809
+ const walletAddress = publicKeyToAddress(publicKey);
2810
+
2811
+ // 3. Generate secure session ID
2812
+ const sessionId = randomBytes(32).toString('hex');
2813
+
2814
+ // 4. Create session
2815
+ const now = Date.now();
2816
+ const session: Session = {
2817
+ id: sessionId,
2818
+ walletAddress,
2819
+ publicKey,
2820
+ createdAt: now,
2821
+ expiresAt: now + SESSION_DURATION,
2822
+ lastActivity: now
2823
+ };
2824
+
2825
+ sessions.set(sessionId, session);
2826
+
2827
+ console.log(`✅ Session created for ${walletAddress}`);
2828
+ return { sessionId, expiresAt: session.expiresAt };
2829
+
2830
+ } catch (error) {
2831
+ console.error('Session creation failed:', error);
2832
+ return null;
2833
+ }
2834
+ }
2835
+
2836
+ // Validate and refresh session
2837
+ export function validateSession(sessionId: string): Session | null {
2838
+ const session = sessions.get(sessionId);
2839
+
2840
+ if (!session) {
2841
+ return null;
2842
+ }
2843
+
2844
+ const now = Date.now();
2845
+
2846
+ // Check if session expired
2847
+ if (now > session.expiresAt) {
2848
+ sessions.delete(sessionId);
2849
+ return null;
2850
+ }
2851
+
2852
+ // Check activity timeout
2853
+ if (now - session.lastActivity > ACTIVITY_TIMEOUT) {
2854
+ sessions.delete(sessionId);
2855
+ return null;
2856
+ }
2857
+
2858
+ // Update last activity
2859
+ session.lastActivity = now;
2860
+ sessions.set(sessionId, session);
2861
+
2862
+ return session;
2863
+ }
2864
+
2865
+ // Cleanup expired sessions (run periodically)
2866
+ export function cleanupExpiredSessions() {
2867
+ const now = Date.now();
2868
+ let cleaned = 0;
2869
+
2870
+ for (const [sessionId, session] of sessions.entries()) {
2871
+ if (now > session.expiresAt || now - session.lastActivity > ACTIVITY_TIMEOUT) {
2872
+ sessions.delete(sessionId);
2873
+ cleaned++;
2874
+ }
2875
+ }
2876
+
2877
+ if (cleaned > 0) {
2878
+ console.log(`🧹 Cleaned up ${cleaned} expired sessions`);
2879
+ }
2880
+ }
2881
+
2882
+ // Start cleanup interval
2883
+ setInterval(cleanupExpiredSessions, CLEANUP_INTERVAL);
2884
+
2885
+ // Express.js middleware
2886
+ /*
2887
+ function sessionMiddleware(req, res, next) {
2888
+ const sessionId = req.cookies.session_id;
2889
+
2890
+ if (!sessionId) {
2891
+ return res.status(401).json({ error: 'No session' });
2892
+ }
2893
+
2894
+ const session = validateSession(sessionId);
2895
+
2896
+ if (!session) {
2897
+ res.clearCookie('session_id');
2898
+ return res.status(401).json({ error: 'Invalid or expired session' });
2899
+ }
2900
+
2901
+ req.session = session;
2902
+ req.walletAddress = session.walletAddress;
2903
+ next();
2904
+ }
2905
+ */","Production session management with wallet address binding and automatic cleanup. Uses httpOnly cookies, validates signatures, and implements activity timeouts.","{""message"": ""Authenticate: 1704067200000"", ""signature"": ""0x1234567890abcdef..."", ""publicKey"": ""03abcdef..."", ""signatureValid"": true}","{""sessionId"": ""a1b2c3d4e5f6..."", ""expiresAt"": 1704153600000, ""walletAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""sessionCreated"": true, ""cookieSet"": true}","- Using localStorage for sessions (XSS vulnerability)
2906
+ - Not implementing activity timeout allows stale sessions
2907
+ - Forgetting cleanup causes memory leaks
2908
+ - Not binding session to wallet address (session hijacking)
2909
+ - Using weak session ID generation (predictable IDs)",https://github.com/stx-city/deploy-stx-city/blob/main/src/store/StacksStore.ts,"authentication.csv:2,authentication.csv:9,security-patterns.csv:1,security-patterns.csv:4","authentication,session,redis,security,cookies,best-practice,intermediate",intermediate
2910
+ 41,deployment,quickstart,deploy-contract-with-fees,Deploy Clarity contract to mainnet with post-condition protection,"// Production contract deployment from STX City deploy page
2911
+ // Pattern from /Volumes/Projects/STXCITY/deploy-stx-city/src/app/deploy/create/page.tsx
2912
+
2913
+ import { request } from '@stacks/connect';
2914
+ import { Pc, PostConditionMode } from '@stacks/transactions';
2915
+
2916
+ // Option 1: Deploy with STX payment
2917
+ async function deployContractWithSTX(
2918
+ walletAddress: string,
2919
+ contractName: string,
2920
+ clarityCode: string,
2921
+ deployFee: number = 2000000 // 2 STX in micro-STX
2922
+ ) {
2923
+ // Create post-condition: Wallet will send at most deployFee STX
2924
+ const sendSTXPostCondition = Pc.principal(walletAddress)
2925
+ .willSendLte(deployFee)
2926
+ .ustx();
2927
+
2928
+ const deployResponse = await request('stx_deployContract', {
2929
+ name: contractName,
2930
+ clarityCode: clarityCode,
2931
+ clarityVersion: 3,
2932
+ network: 'mainnet',
2933
+ postConditions: [sendSTXPostCondition],
2934
+ postConditionMode: 'deny',
2935
+ fee: 100000 // Transaction fee (0.1 STX)
2936
+ });
2937
+
2938
+ if (deployResponse) {
2939
+ console.log('✅ Contract deployed successfully!');
2940
+ console.log('Transaction ID:', deployResponse.txid);
2941
+ console.log('View at:', `https://explorer.hiro.so/txid/${deployResponse.txid}?chain=mainnet`);
2942
+ return deployResponse.txid;
2943
+ }
2944
+ }
2945
+
2946
+ // Option 2: Deploy with token payment (e.g., WELSH, VELAR)
2947
+ async function deployContractWithToken(
2948
+ walletAddress: string,
2949
+ contractName: string,
2950
+ clarityCode: string,
2951
+ tokenContractId: string, // e.g., 'SP3NE50GEXFG9SZGTT51P40X2CKYSZ5CC4ZTZ7A2G.welshcorgicoin-token'
2952
+ assetName: string, // e.g., 'welshcorgicoin'
2953
+ tokenAmount: number, // Token amount with decimals
2954
+ ) {
2955
+ // Create post-condition: Wallet will send at most tokenAmount of token
2956
+ const sendFTCondition = Pc.principal(walletAddress)
2957
+ .willSendLte(tokenAmount)
2958
+ .ft(tokenContractId, assetName);
2959
+
2960
+ const deployResponse = await request('stx_deployContract', {
2961
+ name: contractName,
2962
+ clarityCode: clarityCode,
2963
+ clarityVersion: 3,
2964
+ network: 'mainnet',
2965
+ postConditions: [sendFTCondition],
2966
+ postConditionMode: 'deny',
2967
+ fee: 100000
2968
+ });
2969
+
2970
+ if (deployResponse) {
2971
+ console.log('✅ Contract deployed with token payment!');
2972
+ console.log('Transaction ID:', deployResponse.txid);
2973
+ return deployResponse.txid;
2974
+ }
2975
+ }
2976
+
2977
+ // Example usage: Deploy SIP-010 token contract
2978
+ async function deploySIP10Token() {
2979
+ const walletAddress = 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR';
2980
+ const contractName = 'my-awesome-token';
2981
+
2982
+ // Sample SIP-010 contract code (simplified)
2983
+ const clarityCode = `
2984
+ ;; SIP-010 Token Contract
2985
+ (impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
2986
+
2987
+ (define-fungible-token my-token u1000000000)
2988
+ (define-constant contract-owner tx-sender)
2989
+
2990
+ (define-read-only (get-name)
2991
+ (ok ""My Awesome Token""))
2992
+
2993
+ (define-read-only (get-symbol)
2994
+ (ok ""MAT""))
2995
+
2996
+ (define-read-only (get-decimals)
2997
+ (ok u6))
2998
+
2999
+ (define-read-only (get-balance (who principal))
3000
+ (ok (ft-get-balance my-token who)))
3001
+
3002
+ (define-read-only (get-total-supply)
3003
+ (ok (ft-get-supply my-token)))
3004
+
3005
+ (define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
3006
+ (begin
3007
+ (asserts! (is-eq tx-sender sender) (err u4))
3008
+ (try! (ft-transfer? my-token amount sender recipient))
3009
+ (match memo to-print (print to-print) 0x)
3010
+ (ok true)
3011
+ ))
3012
+
3013
+ ;; Mint initial supply to contract owner
3014
+ (try! (ft-mint? my-token u1000000000 contract-owner))
3015
+ `;
3016
+
3017
+ // Deploy with 2 STX fee
3018
+ const txId = await deployContractWithSTX(
3019
+ walletAddress,
3020
+ contractName,
3021
+ clarityCode,
3022
+ 2000000 // 2 STX
3023
+ );
3024
+
3025
+ return txId;
3026
+ }
3027
+
3028
+ // Production pattern from STX City: Convert name to valid contract slug
3029
+ function convertToSlug(name: string): string {
3030
+ return name
3031
+ .toLowerCase()
3032
+ .replace(/[^a-z0-9]+/g, '-')
3033
+ .replace(/^-+|-+$/g, '');
3034
+ }","Production contract deployment pattern from STX City. Uses request('stx_deployContract', ...) with post-conditions to protect against excessive spending. Supports both STX and token payment methods. Always uses PostConditionMode.Deny for security.","{""walletAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR"", ""contractName"": ""my-token"", ""clarityCode"": ""(define-fungible-token...)"", ""network"": ""mainnet"", ""deployFee"": 2000000}","{""txid"": ""0xabc123..."", ""success"": true, ""explorerUrl"": ""https://explorer.hiro.so/txid/0xabc123...?chain=mainnet"", ""contractAddress"": ""SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.my-token""}","- Forgetting post-conditions allows unlimited spending
3035
+ - Using PostConditionMode.Allow instead of Deny (security risk)
3036
+ - Not converting contract name to valid slug (deployment fails)
3037
+ - Setting fee too low causes transaction to be rejected
3038
+ - Forgetting clarityVersion parameter defaults to Clarity 2
3039
+ - Not handling deployResponse being null/undefined
3040
+ - Using testnet contract addresses on mainnet
3041
+ - Contract name must be lowercase alphanumeric + hyphens only",https://github.com/stx-city/deploy-stx-city/blob/main/src/app/deploy/create/page.tsx,"deployment.csv:1,deployment.csv:2,fungible-tokens.csv:1,security-patterns.csv:1","deploy,contract,stx_deployContract,post-conditions,mainnet,fees,security,quickstart,beginner",beginner