stxer 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -126,46 +154,52 @@ export class SimulationBuilder {
126
154
  private network: StacksNetworkName | string;
127
155
 
128
156
  private constructor(options: SimulationBuilderOptions = {}) {
129
- this.apiEndpoint = options.apiEndpoint ?? 'https://api.stxer.xyz';
130
- this.stacksNodeAPI = options.stacksNodeAPI ?? 'https://api.hiro.so';
131
157
  this.network = options.network ?? 'mainnet';
158
+ const isTestnet = this.network === 'testnet';
159
+
160
+ this.apiEndpoint =
161
+ options.apiEndpoint ??
162
+ (isTestnet ? 'https://testnet-api.stxer.xyz' : 'https://api.stxer.xyz');
163
+ this.stacksNodeAPI =
164
+ options.stacksNodeAPI ??
165
+ (isTestnet ? 'https://api.testnet.hiro.so' : 'https://api.hiro.so');
132
166
  }
133
167
 
134
168
  public static new(options?: SimulationBuilderOptions) {
135
169
  return new SimulationBuilder(options);
136
170
  }
137
171
 
138
- // biome-ignore lint/style/useNumberNamespace: <explanation>
172
+ // biome-ignore lint/style/useNumberNamespace: ignore this
139
173
  private block = NaN;
140
174
  private sender = '';
141
175
  private steps: (
142
176
  | {
143
- // inline simulation
144
- simulationId: string;
145
- }
177
+ // inline simulation
178
+ simulationId: string;
179
+ }
146
180
  | {
147
- // contract call
148
- contract_id: string;
149
- function_name: string;
150
- function_args?: ClarityValue[];
151
- sender: string;
152
- fee: number;
153
- }
181
+ // contract call
182
+ contract_id: string;
183
+ function_name: string;
184
+ function_args?: ClarityValue[];
185
+ sender: string;
186
+ fee: number;
187
+ }
154
188
  | {
155
- // contract deploy
156
- contract_name: string;
157
- source_code: string;
158
- deployer: string;
159
- fee: number;
160
- clarity_version: ClarityVersion;
161
- }
189
+ // contract deploy
190
+ contract_name: string;
191
+ source_code: string;
192
+ deployer: string;
193
+ fee: number;
194
+ clarity_version: ClarityVersion;
195
+ }
162
196
  | {
163
- // STX transfer
164
- recipient: string;
165
- amount: number;
166
- sender: string;
167
- fee: number;
168
- }
197
+ // STX transfer
198
+ recipient: string;
199
+ amount: number;
200
+ sender: string;
201
+ fee: number;
202
+ }
169
203
  | SimulationEval
170
204
  )[] = [];
171
205
 
@@ -180,7 +214,7 @@ export class SimulationBuilder {
180
214
  public inlineSimulation(simulationId: string) {
181
215
  this.steps.push({
182
216
  simulationId,
183
- })
217
+ });
184
218
  return this;
185
219
  }
186
220
  public addSTXTransfer(params: {
@@ -191,7 +225,7 @@ export class SimulationBuilder {
191
225
  }) {
192
226
  if (params.sender == null && this.sender === '') {
193
227
  throw new Error(
194
- 'Please specify a sender with useSender or adding a sender paramenter'
228
+ 'Please specify a sender with useSender or adding a sender paramenter',
195
229
  );
196
230
  }
197
231
  this.steps.push({
@@ -210,7 +244,7 @@ export class SimulationBuilder {
210
244
  }) {
211
245
  if (params.sender == null && this.sender === '') {
212
246
  throw new Error(
213
- 'Please specify a sender with useSender or adding a sender paramenter'
247
+ 'Please specify a sender with useSender or adding a sender paramenter',
214
248
  );
215
249
  }
216
250
  this.steps.push({
@@ -229,7 +263,7 @@ export class SimulationBuilder {
229
263
  }) {
230
264
  if (params.deployer == null && this.sender === '') {
231
265
  throw new Error(
232
- 'Please specify a deployer with useSender or adding a deployer paramenter'
266
+ 'Please specify a deployer with useSender or adding a deployer paramenter',
233
267
  );
234
268
  }
235
269
  this.steps.push({
@@ -270,7 +304,7 @@ export class SimulationBuilder {
270
304
  this.block = stacks_tip_height;
271
305
  }
272
306
  const info: Block = await richFetch(
273
- `${this.stacksNodeAPI}/extended/v1/block/by_height/${this.block}?unanchored=true`
307
+ `${this.stacksNodeAPI}/extended/v1/block/by_height/${this.block}?unanchored=true`,
274
308
  ).then((r) => r.json());
275
309
  if (
276
310
  info.height !== this.block ||
@@ -278,7 +312,7 @@ export class SimulationBuilder {
278
312
  !info.hash.startsWith('0x')
279
313
  ) {
280
314
  throw new Error(
281
- `failed to get block info for block height ${this.block}`
315
+ `failed to get block info for block height ${this.block}`,
282
316
  );
283
317
  }
284
318
  return {
@@ -298,21 +332,22 @@ SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER
298
332
 
299
333
  Feedbacks and feature requests are welcome.
300
334
  To get in touch: contact@stxer.xyz
301
- --------------------------------`
335
+ --------------------------------`,
302
336
  );
303
337
  const block = await this.getBlockInfo();
304
338
  console.log(
305
- `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.`,
306
340
  );
307
341
  const txs: (StacksTransactionWire | SimulationEval)[] = [];
308
342
  const nonce_by_address = new Map<string, number>();
309
343
  const nextNonce = async (sender: string) => {
310
344
  const nonce = nonce_by_address.get(sender);
311
345
  if (nonce == null) {
312
- const url = `${this.stacksNodeAPI
313
- }/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}`;
314
349
  const account: AccountDataResponse = await richFetch(url).then((r) =>
315
- r.json()
350
+ r.json(),
316
351
  );
317
352
  nonce_by_address.set(sender, account.nonce + 1);
318
353
  return account.nonce;
@@ -332,7 +367,21 @@ To get in touch: contact@stxer.xyz
332
367
  }
333
368
  for (const step of this.steps) {
334
369
  if ('simulationId' in step) {
335
- 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
+ });
336
385
  for (const step of previousSimulation.steps) {
337
386
  if ('tx' in step) {
338
387
  txs.push(deserializeTransaction(step.tx));
@@ -357,7 +406,7 @@ To get in touch: contact@stxer.xyz
357
406
  postConditionMode: PostConditionMode.Allow,
358
407
  fee: step.fee,
359
408
  });
360
- tx.auth.spendingCondition.signer = c32addressDecode(step.sender)[1];
409
+ setSender(tx, step.sender);
361
410
  txs.push(tx);
362
411
  } else if ('sender' in step && 'recipient' in step) {
363
412
  const nonce = await nextNonce(step.sender);
@@ -369,7 +418,7 @@ To get in touch: contact@stxer.xyz
369
418
  publicKey: '',
370
419
  fee: step.fee,
371
420
  });
372
- tx.auth.spendingCondition.signer = c32addressDecode(step.sender)[1];
421
+ setSender(tx, step.sender);
373
422
  txs.push(tx);
374
423
  } else if ('deployer' in step) {
375
424
  const nonce = await nextNonce(step.deployer);
@@ -382,28 +431,29 @@ To get in touch: contact@stxer.xyz
382
431
  postConditionMode: PostConditionMode.Allow,
383
432
  fee: step.fee,
384
433
  });
385
- tx.auth.spendingCondition.signer = c32addressDecode(step.deployer)[1];
434
+ setSender(tx, step.deployer);
386
435
  txs.push(tx);
387
436
  } else if ('code' in step) {
388
437
  txs.push(step);
389
438
  } else {
390
- // biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
391
- console.log(`Invalid simulation step:`, step);
439
+ console.log(`Invalid simulation step: ${step}`);
392
440
  }
393
441
  }
394
442
  const id = await runSimulation(
395
443
  `${this.apiEndpoint}/simulations`,
396
444
  block.block_hash,
397
445
  block.block_height,
398
- txs
446
+ txs,
399
447
  );
400
448
  console.log(
401
- `Simulation will be available at: https://stxer.xyz/simulations/${this.network}/${id}`
449
+ `Simulation will be available at: https://stxer.xyz/simulations/${this.network}/${id}`,
402
450
  );
403
451
  return id;
404
452
  }
405
453
 
406
- public pipe(transform: (builder: SimulationBuilder) => SimulationBuilder): SimulationBuilder {
454
+ public pipe(
455
+ transform: (builder: SimulationBuilder) => SimulationBuilder,
456
+ ): SimulationBuilder {
407
457
  return transform(this);
408
458
  }
409
459
  }