stxer 0.4.2 → 0.4.5

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.
@@ -1,12 +1,14 @@
1
1
  import { uintCV } from '@stacks/transactions';
2
2
  import { SimulationBuilder } from '..';
3
3
 
4
- const test = () => SimulationBuilder.new()
5
- .withSender('SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER')
6
- .inlineSimulation('1ab04a8d13d72a301b77b6af9d4f612b')
7
- .addContractDeploy({
8
- contract_name: 'test-simulation',
9
- source_code: `
4
+ const sender = 'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER';
5
+
6
+ const test = () =>
7
+ SimulationBuilder.new()
8
+ .withSender(sender)
9
+ .addContractDeploy({
10
+ contract_name: 'test-simulation',
11
+ source_code: `
10
12
  ;; counter example
11
13
  (define-data-var counter uint u0)
12
14
 
@@ -23,24 +25,18 @@ const test = () => SimulationBuilder.new()
23
25
  (define-read-only (get-counter)
24
26
  (ok (var-get counter)))
25
27
  `,
26
- })
27
- .addEvalCode(
28
- 'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER.test-simulation',
29
- '(get-counter)'
30
- )
31
- .addContractCall({
32
- contract_id: 'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER.test-simulation',
33
- function_name: 'increment',
34
- function_args: [uintCV(10)],
35
- })
36
- .addEvalCode(
37
- 'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER.test-simulation',
38
- '(get-counter)'
39
- )
40
- .run()
28
+ })
29
+ .addEvalCode(`${sender}.test-simulation`, '(get-counter)')
30
+ .addContractCall({
31
+ contract_id: `${sender}.test-simulation`,
32
+ function_name: 'increment',
33
+ function_args: [uintCV(10)],
34
+ })
35
+ .addEvalCode(`${sender}.test-simulation`, '(get-counter)')
36
+ .run();
41
37
 
42
38
  if (require.main === module) {
43
- ; (async () => {
44
- await test()
39
+ (async () => {
40
+ await test();
45
41
  })().catch(console.error);
46
- }
42
+ }
@@ -4,11 +4,11 @@ import {
4
4
  tupleCV,
5
5
  uintCV,
6
6
  } from '@stacks/transactions';
7
- import { SIP010TraitABI } from 'clarity-abi/abis'
7
+ import { SIP010TraitABI } from 'clarity-abi/abis';
8
+ import { unwrapResponse } from 'ts-clarity';
8
9
  import { batchRead } from '../BatchAPI';
9
10
  import { BatchProcessor } from '../BatchProcessor';
10
11
  import { callReadonly, readMap, readVariable } from '../clarity-api';
11
- import { unwrapResponse } from 'ts-clarity';
12
12
 
13
13
  async function batchReadsExample() {
14
14
  const rs = await batchRead({
@@ -18,21 +18,21 @@ async function batchReadsExample() {
18
18
  {
19
19
  contract: contractPrincipalCV(
20
20
  'SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275',
21
- 'liquidity-token-v5kbe3oqvac'
21
+ 'liquidity-token-v5kbe3oqvac',
22
22
  ),
23
23
  variableName: 'balance-x',
24
24
  },
25
25
  {
26
26
  contract: contractPrincipalCV(
27
27
  'SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275',
28
- 'liquidity-token-v5kbe3oqvac'
28
+ 'liquidity-token-v5kbe3oqvac',
29
29
  ),
30
30
  variableName: 'balance-y',
31
31
  },
32
32
  {
33
33
  contract: contractPrincipalCV(
34
34
  'SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275',
35
- 'liquidity-token-v5kbe3oqvac'
35
+ 'liquidity-token-v5kbe3oqvac',
36
36
  ),
37
37
  variableName: 'something-not-exists',
38
38
  },
@@ -41,15 +41,15 @@ async function batchReadsExample() {
41
41
  {
42
42
  contract: contractPrincipalCV(
43
43
  'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM',
44
- 'amm-registry-v2-01'
44
+ 'amm-registry-v2-01',
45
45
  ),
46
46
  mapName: 'pools-data-map',
47
47
  mapKey: tupleCV({
48
48
  'token-x': principalCV(
49
- 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wstx-v2'
49
+ 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wstx-v2',
50
50
  ),
51
51
  'token-y': principalCV(
52
- 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex'
52
+ 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
53
53
  ),
54
54
  factor: uintCV(1e8),
55
55
  }),
@@ -57,7 +57,7 @@ async function batchReadsExample() {
57
57
  {
58
58
  contract: contractPrincipalCV(
59
59
  'SP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1',
60
- 'univ2-core'
60
+ 'univ2-core',
61
61
  ),
62
62
  mapName: 'pools',
63
63
  mapKey: uintCV(1),
@@ -65,7 +65,7 @@ async function batchReadsExample() {
65
65
  {
66
66
  contract: contractPrincipalCV(
67
67
  'SP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1',
68
- 'contract-not-exists'
68
+ 'contract-not-exists',
69
69
  ),
70
70
  mapName: 'pools',
71
71
  mapKey: uintCV(1),
@@ -81,7 +81,6 @@ async function batchQueueProcessorExample() {
81
81
  batchDelayMs: 1000,
82
82
  });
83
83
 
84
-
85
84
  const promiseA = processor.read({
86
85
  mode: 'variable',
87
86
  contractAddress: 'SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275',
@@ -105,7 +104,7 @@ async function batchSip010Example() {
105
104
  abi: SIP010TraitABI.functions,
106
105
  functionName: 'get-total-supply',
107
106
  contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
108
- }).then(unwrapResponse)
107
+ }).then(unwrapResponse);
109
108
  const balance = callReadonly({
110
109
  abi: SIP010TraitABI.functions,
111
110
  functionName: 'get-balance',
@@ -113,20 +112,18 @@ async function batchSip010Example() {
113
112
  args: {
114
113
  who: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
115
114
  },
116
- }).then(unwrapResponse)
115
+ }).then(unwrapResponse);
117
116
  const paused = readVariable({
118
- abi: [{ name: 'paused', type: 'bool', access: 'variable' },],
117
+ abi: [{ name: 'paused', type: 'bool', access: 'variable' }],
119
118
  variableName: 'paused',
120
119
  contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
121
120
  });
122
121
  const approved = readMap({
123
- abi: [
124
- { key: 'principal', name: 'approved-tokens', value: 'bool' },
125
- ],
122
+ abi: [{ key: 'principal', name: 'approved-tokens', value: 'bool' }],
126
123
  mapName: 'approved-tokens',
127
124
  key: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
128
125
  contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
129
- })
126
+ });
130
127
  const result = await Promise.all([supply, balance, paused, approved]);
131
128
  console.log(result);
132
129
  }
package/src/simulation.ts CHANGED
@@ -1,27 +1,55 @@
1
- import { type AccountDataResponse, getNodeInfo, richFetch } from 'ts-clarity';
2
- import type { Block } from '@stacks/stacks-blockchain-api-types';
3
1
  import {
2
+ AddressVersion,
4
3
  STACKS_MAINNET,
5
4
  STACKS_TESTNET,
6
5
  type StacksNetworkName,
7
6
  } from '@stacks/network';
7
+ import type { Block } from '@stacks/stacks-blockchain-api-types';
8
8
  import {
9
+ AddressHashMode,
10
+ bufferCV,
9
11
  type ClarityValue,
10
12
  ClarityVersion,
11
- PostConditionMode,
12
- type StacksTransactionWire,
13
- bufferCV,
14
13
  contractPrincipalCV,
15
14
  deserializeTransaction,
15
+ type MultiSigSpendingCondition,
16
16
  makeUnsignedContractCall,
17
17
  makeUnsignedContractDeploy,
18
18
  makeUnsignedSTXTokenTransfer,
19
+ PostConditionMode,
20
+ type StacksTransactionWire,
19
21
  serializeCVBytes,
20
22
  stringAsciiCV,
21
23
  tupleCV,
22
24
  uintCV,
23
25
  } from '@stacks/transactions';
24
26
  import { c32addressDecode } from 'c32check';
27
+ import { type AccountDataResponse, getNodeInfo, richFetch } from 'ts-clarity';
28
+
29
+ function setSender(tx: StacksTransactionWire, sender: string) {
30
+ const [addressVersion, signer] = c32addressDecode(sender);
31
+ switch (addressVersion) {
32
+ case AddressVersion.MainnetSingleSig:
33
+ case AddressVersion.TestnetSingleSig:
34
+ tx.auth.spendingCondition.hashMode = AddressHashMode.P2PKH;
35
+ tx.auth.spendingCondition.signer = signer;
36
+ break;
37
+ case AddressVersion.MainnetMultiSig:
38
+ case AddressVersion.TestnetMultiSig: {
39
+ const sc = tx.auth.spendingCondition;
40
+ tx.auth.spendingCondition = {
41
+ hashMode: AddressHashMode.P2SH,
42
+ signer,
43
+ fields: [],
44
+ signaturesRequired: 0,
45
+ nonce: sc.nonce,
46
+ fee: sc.fee,
47
+ } as MultiSigSpendingCondition;
48
+ break;
49
+ }
50
+ }
51
+ return tx;
52
+ }
25
53
 
26
54
  function runTx(tx: StacksTransactionWire) {
27
55
  // type 0: run transaction
@@ -43,8 +71,8 @@ export function runEval({ contract_id, code }: SimulationEval) {
43
71
  tupleCV({
44
72
  contract: contractPrincipalCV(contract_address, contract_name),
45
73
  code: stringAsciiCV(code),
46
- })
47
- )
74
+ }),
75
+ ),
48
76
  ),
49
77
  });
50
78
  }
@@ -53,7 +81,7 @@ export async function runSimulation(
53
81
  apiEndpoint: string,
54
82
  block_hash: string,
55
83
  block_height: number,
56
- txs: (StacksTransactionWire | SimulationEval)[]
84
+ txs: (StacksTransactionWire | SimulationEval)[],
57
85
  ) {
58
86
  // Convert 'sim-v1' to Uint8Array
59
87
  const header = new TextEncoder().encode('sim-v1');
@@ -73,7 +101,7 @@ export async function runSimulation(
73
101
  throw new Error('Invalid block hash format');
74
102
  }
75
103
  const hashBytes = new Uint8Array(
76
- matches.map((byte) => Number.parseInt(byte, 16))
104
+ matches.map((byte) => Number.parseInt(byte, 16)),
77
105
  );
78
106
 
79
107
  // Convert transactions to bytes
@@ -128,10 +156,12 @@ export class SimulationBuilder {
128
156
  private constructor(options: SimulationBuilderOptions = {}) {
129
157
  this.network = options.network ?? 'mainnet';
130
158
  const isTestnet = this.network === 'testnet';
131
-
132
- this.apiEndpoint = options.apiEndpoint ??
159
+
160
+ this.apiEndpoint =
161
+ options.apiEndpoint ??
133
162
  (isTestnet ? 'https://testnet-api.stxer.xyz' : 'https://api.stxer.xyz');
134
- this.stacksNodeAPI = options.stacksNodeAPI ??
163
+ this.stacksNodeAPI =
164
+ options.stacksNodeAPI ??
135
165
  (isTestnet ? 'https://api.testnet.hiro.so' : 'https://api.hiro.so');
136
166
  }
137
167
 
@@ -139,37 +169,37 @@ export class SimulationBuilder {
139
169
  return new SimulationBuilder(options);
140
170
  }
141
171
 
142
- // biome-ignore lint/style/useNumberNamespace: <explanation>
172
+ // biome-ignore lint/style/useNumberNamespace: ignore this
143
173
  private block = NaN;
144
174
  private sender = '';
145
175
  private steps: (
146
176
  | {
147
- // inline simulation
148
- simulationId: string;
149
- }
177
+ // inline simulation
178
+ simulationId: string;
179
+ }
150
180
  | {
151
- // contract call
152
- contract_id: string;
153
- function_name: string;
154
- function_args?: ClarityValue[];
155
- sender: string;
156
- fee: number;
157
- }
181
+ // contract call
182
+ contract_id: string;
183
+ function_name: string;
184
+ function_args?: ClarityValue[];
185
+ sender: string;
186
+ fee: number;
187
+ }
158
188
  | {
159
- // contract deploy
160
- contract_name: string;
161
- source_code: string;
162
- deployer: string;
163
- fee: number;
164
- clarity_version: ClarityVersion;
165
- }
189
+ // contract deploy
190
+ contract_name: string;
191
+ source_code: string;
192
+ deployer: string;
193
+ fee: number;
194
+ clarity_version: ClarityVersion;
195
+ }
166
196
  | {
167
- // STX transfer
168
- recipient: string;
169
- amount: number;
170
- sender: string;
171
- fee: number;
172
- }
197
+ // STX transfer
198
+ recipient: string;
199
+ amount: number;
200
+ sender: string;
201
+ fee: number;
202
+ }
173
203
  | SimulationEval
174
204
  )[] = [];
175
205
 
@@ -184,7 +214,7 @@ export class SimulationBuilder {
184
214
  public inlineSimulation(simulationId: string) {
185
215
  this.steps.push({
186
216
  simulationId,
187
- })
217
+ });
188
218
  return this;
189
219
  }
190
220
  public addSTXTransfer(params: {
@@ -195,7 +225,7 @@ export class SimulationBuilder {
195
225
  }) {
196
226
  if (params.sender == null && this.sender === '') {
197
227
  throw new Error(
198
- 'Please specify a sender with useSender or adding a sender paramenter'
228
+ 'Please specify a sender with useSender or adding a sender paramenter',
199
229
  );
200
230
  }
201
231
  this.steps.push({
@@ -214,7 +244,7 @@ export class SimulationBuilder {
214
244
  }) {
215
245
  if (params.sender == null && this.sender === '') {
216
246
  throw new Error(
217
- 'Please specify a sender with useSender or adding a sender paramenter'
247
+ 'Please specify a sender with useSender or adding a sender paramenter',
218
248
  );
219
249
  }
220
250
  this.steps.push({
@@ -233,14 +263,14 @@ export class SimulationBuilder {
233
263
  }) {
234
264
  if (params.deployer == null && this.sender === '') {
235
265
  throw new Error(
236
- 'Please specify a deployer with useSender or adding a deployer paramenter'
266
+ 'Please specify a deployer with useSender or adding a deployer paramenter',
237
267
  );
238
268
  }
239
269
  this.steps.push({
240
270
  ...params,
241
271
  deployer: params.deployer ?? this.sender,
242
272
  fee: params.fee ?? 0,
243
- clarity_version: params.clarity_version ?? ClarityVersion.Clarity3,
273
+ clarity_version: params.clarity_version ?? ClarityVersion.Clarity4,
244
274
  });
245
275
  return this;
246
276
  }
@@ -274,7 +304,7 @@ export class SimulationBuilder {
274
304
  this.block = stacks_tip_height;
275
305
  }
276
306
  const info: Block = await richFetch(
277
- `${this.stacksNodeAPI}/extended/v1/block/by_height/${this.block}?unanchored=true`
307
+ `${this.stacksNodeAPI}/extended/v1/block/by_height/${this.block}?unanchored=true`,
278
308
  ).then((r) => r.json());
279
309
  if (
280
310
  info.height !== this.block ||
@@ -282,7 +312,7 @@ export class SimulationBuilder {
282
312
  !info.hash.startsWith('0x')
283
313
  ) {
284
314
  throw new Error(
285
- `failed to get block info for block height ${this.block}`
315
+ `failed to get block info for block height ${this.block}`,
286
316
  );
287
317
  }
288
318
  return {
@@ -302,21 +332,22 @@ SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER
302
332
 
303
333
  Feedbacks and feature requests are welcome.
304
334
  To get in touch: contact@stxer.xyz
305
- --------------------------------`
335
+ --------------------------------`,
306
336
  );
307
337
  const block = await this.getBlockInfo();
308
338
  console.log(
309
- `Using block height ${block.block_height} hash 0x${block.block_hash} to run simulation.`
339
+ `Using block height ${block.block_height} hash 0x${block.block_hash} to run simulation.`,
310
340
  );
311
341
  const txs: (StacksTransactionWire | SimulationEval)[] = [];
312
342
  const nonce_by_address = new Map<string, number>();
313
343
  const nextNonce = async (sender: string) => {
314
344
  const nonce = nonce_by_address.get(sender);
315
345
  if (nonce == null) {
316
- const url = `${this.stacksNodeAPI
317
- }/v2/accounts/${sender}?proof=${false}&tip=${block.index_block_hash}`;
346
+ const url = `${
347
+ this.stacksNodeAPI
348
+ }/v2/accounts/${sender}?proof=${false}&tip=${block.index_block_hash}`;
318
349
  const account: AccountDataResponse = await richFetch(url).then((r) =>
319
- r.json()
350
+ r.json(),
320
351
  );
321
352
  nonce_by_address.set(sender, account.nonce + 1);
322
353
  return account.nonce;
@@ -336,7 +367,21 @@ To get in touch: contact@stxer.xyz
336
367
  }
337
368
  for (const step of this.steps) {
338
369
  if ('simulationId' in step) {
339
- const previousSimulation: {steps: ({tx: string} | {code: string, contract: string})[]} = await fetch(`https://api.stxer.xyz/simulations/${step.simulationId}/request`).then(x => x.json())
370
+ const previousSimulation: {
371
+ steps: ({ tx: string } | { code: string; contract: string })[];
372
+ } = await fetch(
373
+ `https://api.stxer.xyz/simulations/${step.simulationId}/request`,
374
+ ).then(async (rs) => {
375
+ const body = await rs.text();
376
+ if (!body.startsWith('{')) {
377
+ throw new Error(
378
+ `failed to get simulation ${step.simulationId}: ${body}`,
379
+ );
380
+ }
381
+ return JSON.parse(body) as {
382
+ steps: ({ tx: string } | { code: string; contract: string })[];
383
+ };
384
+ });
340
385
  for (const step of previousSimulation.steps) {
341
386
  if ('tx' in step) {
342
387
  txs.push(deserializeTransaction(step.tx));
@@ -361,7 +406,7 @@ To get in touch: contact@stxer.xyz
361
406
  postConditionMode: PostConditionMode.Allow,
362
407
  fee: step.fee,
363
408
  });
364
- tx.auth.spendingCondition.signer = c32addressDecode(step.sender)[1];
409
+ setSender(tx, step.sender);
365
410
  txs.push(tx);
366
411
  } else if ('sender' in step && 'recipient' in step) {
367
412
  const nonce = await nextNonce(step.sender);
@@ -373,7 +418,7 @@ To get in touch: contact@stxer.xyz
373
418
  publicKey: '',
374
419
  fee: step.fee,
375
420
  });
376
- tx.auth.spendingCondition.signer = c32addressDecode(step.sender)[1];
421
+ setSender(tx, step.sender);
377
422
  txs.push(tx);
378
423
  } else if ('deployer' in step) {
379
424
  const nonce = await nextNonce(step.deployer);
@@ -385,29 +430,31 @@ To get in touch: contact@stxer.xyz
385
430
  publicKey: '',
386
431
  postConditionMode: PostConditionMode.Allow,
387
432
  fee: step.fee,
433
+ clarityVersion: step.clarity_version
388
434
  });
389
- tx.auth.spendingCondition.signer = c32addressDecode(step.deployer)[1];
435
+ setSender(tx, step.deployer);
390
436
  txs.push(tx);
391
437
  } else if ('code' in step) {
392
438
  txs.push(step);
393
439
  } else {
394
- // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
395
- console.log(`Invalid simulation step:`, step);
440
+ console.log(`Invalid simulation step: ${step}`);
396
441
  }
397
442
  }
398
443
  const id = await runSimulation(
399
444
  `${this.apiEndpoint}/simulations`,
400
445
  block.block_hash,
401
446
  block.block_height,
402
- txs
447
+ txs,
403
448
  );
404
449
  console.log(
405
- `Simulation will be available at: https://stxer.xyz/simulations/${this.network}/${id}`
450
+ `Simulation will be available at: https://stxer.xyz/simulations/${this.network}/${id}`,
406
451
  );
407
452
  return id;
408
453
  }
409
454
 
410
- public pipe(transform: (builder: SimulationBuilder) => SimulationBuilder): SimulationBuilder {
455
+ public pipe(
456
+ transform: (builder: SimulationBuilder) => SimulationBuilder,
457
+ ): SimulationBuilder {
411
458
  return transform(this);
412
459
  }
413
460
  }