stxer 0.7.0 → 0.8.0
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.
- package/CHANGELOG.md +118 -0
- package/README.md +236 -55
- package/dist/ast.d.ts +1 -1
- package/dist/bitcoin.d.ts +121 -0
- package/dist/index.d.ts +2 -0
- package/dist/simulation-api.d.ts +52 -3
- package/dist/simulation.d.ts +70 -3
- package/dist/stxer.cjs.development.js +904 -101
- package/dist/stxer.cjs.development.js.map +1 -1
- package/dist/stxer.cjs.production.min.js +1 -1
- package/dist/stxer.cjs.production.min.js.map +1 -1
- package/dist/stxer.esm.js +885 -104
- package/dist/stxer.esm.js.map +1 -1
- package/dist/transaction.d.ts +71 -0
- package/dist/types.d.ts +246 -24
- package/package.json +6 -4
- package/src/ast.ts +1 -1
- package/src/bitcoin.ts +391 -0
- package/src/index.ts +2 -0
- package/src/simulation-api.ts +109 -12
- package/src/simulation.ts +245 -46
- package/src/transaction.ts +141 -0
- package/src/types.ts +253 -26
package/src/simulation.ts
CHANGED
|
@@ -1,54 +1,43 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AddressVersion,
|
|
3
2
|
STACKS_MAINNET,
|
|
4
3
|
STACKS_TESTNET,
|
|
5
4
|
type StacksNetworkName,
|
|
6
5
|
} from '@stacks/network';
|
|
7
6
|
import type { Block } from '@stacks/stacks-blockchain-api-types';
|
|
8
7
|
import {
|
|
9
|
-
|
|
8
|
+
Cl,
|
|
9
|
+
ClarityType,
|
|
10
10
|
type ClarityValue,
|
|
11
11
|
ClarityVersion,
|
|
12
|
-
|
|
12
|
+
cvToString,
|
|
13
|
+
deserializeCV,
|
|
13
14
|
makeUnsignedContractCall,
|
|
14
15
|
makeUnsignedContractDeploy,
|
|
15
16
|
makeUnsignedSTXTokenTransfer,
|
|
16
17
|
PostConditionMode,
|
|
17
|
-
|
|
18
|
+
serializeCV,
|
|
18
19
|
} from '@stacks/transactions';
|
|
19
|
-
import { c32addressDecode } from 'c32check';
|
|
20
20
|
import { type AccountDataResponse, getNodeInfo, richFetch } from 'ts-clarity';
|
|
21
|
+
import { bytesToHex } from './bitcoin';
|
|
21
22
|
import { STXER_API_MAINNET, STXER_API_TESTNET } from './constants';
|
|
22
23
|
import {
|
|
23
24
|
createSimulationSession,
|
|
25
|
+
type SimulationApiOptions,
|
|
26
|
+
simulationBatchReads,
|
|
24
27
|
submitSimulationSteps,
|
|
25
28
|
} from './simulation-api';
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const sc = tx.auth.spendingCondition;
|
|
39
|
-
tx.auth.spendingCondition = {
|
|
40
|
-
hashMode: AddressHashMode.P2SH,
|
|
41
|
-
signer,
|
|
42
|
-
fields: [],
|
|
43
|
-
signaturesRequired: 0,
|
|
44
|
-
nonce: sc.nonce,
|
|
45
|
-
fee: sc.fee,
|
|
46
|
-
} as MultiSigSpendingCondition;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return tx;
|
|
51
|
-
}
|
|
29
|
+
import {
|
|
30
|
+
buildUnsignedContractCallHex,
|
|
31
|
+
type ContractCallTxArgs,
|
|
32
|
+
setSender,
|
|
33
|
+
} from './transaction';
|
|
34
|
+
import type {
|
|
35
|
+
AdvanceBlocksRequest,
|
|
36
|
+
ReadStep,
|
|
37
|
+
SimulationStepInput,
|
|
38
|
+
TenureExtendCause,
|
|
39
|
+
TransactionReceipt,
|
|
40
|
+
} from './types';
|
|
52
41
|
|
|
53
42
|
export interface SimulationEval {
|
|
54
43
|
contract_id: string;
|
|
@@ -85,8 +74,7 @@ export class SimulationBuilder {
|
|
|
85
74
|
return new SimulationBuilder(options);
|
|
86
75
|
}
|
|
87
76
|
|
|
88
|
-
|
|
89
|
-
private block = NaN;
|
|
77
|
+
private block = Number.NaN;
|
|
90
78
|
private sender = '';
|
|
91
79
|
private steps: (
|
|
92
80
|
| {
|
|
@@ -132,6 +120,12 @@ export class SimulationBuilder {
|
|
|
132
120
|
| {
|
|
133
121
|
// TenureExtend - V2 native step type
|
|
134
122
|
type: 'TenureExtend';
|
|
123
|
+
cause: TenureExtendCause;
|
|
124
|
+
}
|
|
125
|
+
| {
|
|
126
|
+
// AdvanceBlocks - V2 native step type, synthesizes blocks
|
|
127
|
+
type: 'AdvanceBlocks';
|
|
128
|
+
request: AdvanceBlocksRequest;
|
|
135
129
|
}
|
|
136
130
|
)[] = [];
|
|
137
131
|
|
|
@@ -207,7 +201,7 @@ export class SimulationBuilder {
|
|
|
207
201
|
...params,
|
|
208
202
|
deployer: params.deployer ?? this.sender,
|
|
209
203
|
fee: params.fee ?? 0,
|
|
210
|
-
clarity_version: params.clarity_version ?? ClarityVersion.
|
|
204
|
+
clarity_version: params.clarity_version ?? ClarityVersion.Clarity5,
|
|
211
205
|
});
|
|
212
206
|
return this;
|
|
213
207
|
}
|
|
@@ -245,7 +239,7 @@ export class SimulationBuilder {
|
|
|
245
239
|
type: 'SetContractCode',
|
|
246
240
|
contract_id: params.contract_id,
|
|
247
241
|
source_code: params.source_code,
|
|
248
|
-
clarity_version: params.clarity_version ?? ClarityVersion.
|
|
242
|
+
clarity_version: params.clarity_version ?? ClarityVersion.Clarity5,
|
|
249
243
|
});
|
|
250
244
|
return this;
|
|
251
245
|
}
|
|
@@ -258,9 +252,47 @@ export class SimulationBuilder {
|
|
|
258
252
|
return this;
|
|
259
253
|
}
|
|
260
254
|
|
|
261
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Add a `TenureExtend` step. Defaults to `cause: 'Extended'` (full
|
|
257
|
+
* cost reset) — equivalent in server-side behavior to the legacy
|
|
258
|
+
* zero-arg call.
|
|
259
|
+
*
|
|
260
|
+
* Pass an explicit `TenureExtendCause` to reset only one SIP-034
|
|
261
|
+
* dimension (`ExtendedRuntime` / `ExtendedReadCount` / etc.).
|
|
262
|
+
*
|
|
263
|
+
* On the wire SDK 0.8.0 emits the modern `{ TenureExtend: { cause } }`
|
|
264
|
+
* shape. The server still parses the legacy `[]` shape for any
|
|
265
|
+
* caller emitting raw step objects.
|
|
266
|
+
*/
|
|
267
|
+
public addTenureExtend(cause: TenureExtendCause = 'Extended') {
|
|
262
268
|
this.steps.push({
|
|
263
269
|
type: 'TenureExtend',
|
|
270
|
+
cause,
|
|
271
|
+
});
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Synthesize bitcoin and stacks blocks on top of the simulation's
|
|
277
|
+
* pinned parent tip. Used to model burn-block / tenure boundaries
|
|
278
|
+
* (bridge contracts, time-locked redemptions, locked-STX unlock).
|
|
279
|
+
*
|
|
280
|
+
* The simulator validates the request shape; older simulator builds
|
|
281
|
+
* that don't yet support `AdvanceBlocks` reject this variant with
|
|
282
|
+
* HTTP 400.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```typescript
|
|
286
|
+
* builder.addAdvanceBlocks({
|
|
287
|
+
* bitcoin_blocks: 1,
|
|
288
|
+
* stacks_blocks_per_bitcoin: 1,
|
|
289
|
+
* });
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
public addAdvanceBlocks(request: AdvanceBlocksRequest) {
|
|
293
|
+
this.steps.push({
|
|
294
|
+
type: 'AdvanceBlocks',
|
|
295
|
+
request,
|
|
264
296
|
});
|
|
265
297
|
return this;
|
|
266
298
|
}
|
|
@@ -414,9 +446,15 @@ To get in touch: contact@stxer.xyz
|
|
|
414
446
|
Reads: step.reads,
|
|
415
447
|
});
|
|
416
448
|
} else if (step.type === 'TenureExtend') {
|
|
417
|
-
// TenureExtend -
|
|
449
|
+
// TenureExtend - modern wire shape (legacy `[]` is still parsed
|
|
450
|
+
// server-side for direct-emit consumers, but the builder always
|
|
451
|
+
// emits the explicit cause).
|
|
452
|
+
v2Steps.push({
|
|
453
|
+
TenureExtend: { cause: step.cause },
|
|
454
|
+
});
|
|
455
|
+
} else if (step.type === 'AdvanceBlocks') {
|
|
418
456
|
v2Steps.push({
|
|
419
|
-
|
|
457
|
+
AdvanceBlocks: step.request,
|
|
420
458
|
});
|
|
421
459
|
} else {
|
|
422
460
|
console.log(`Invalid simulation step: ${step}`);
|
|
@@ -453,13 +491,6 @@ To get in touch: contact@stxer.xyz
|
|
|
453
491
|
}
|
|
454
492
|
}
|
|
455
493
|
|
|
456
|
-
// Helper function to convert Uint8Array to hex string
|
|
457
|
-
function bytesToHex(bytes: Uint8Array): string {
|
|
458
|
-
return Array.from(bytes)
|
|
459
|
-
.map((b) => b.toString(16).padStart(2, '0'))
|
|
460
|
-
.join('');
|
|
461
|
-
}
|
|
462
|
-
|
|
463
494
|
// Helper function to convert ClarityVersion to number
|
|
464
495
|
function clarityVersionToNumber(version: ClarityVersion): number {
|
|
465
496
|
switch (version) {
|
|
@@ -471,9 +502,177 @@ function clarityVersionToNumber(version: ClarityVersion): number {
|
|
|
471
502
|
return 3;
|
|
472
503
|
case ClarityVersion.Clarity4:
|
|
473
504
|
return 4;
|
|
505
|
+
case ClarityVersion.Clarity5:
|
|
506
|
+
return 5;
|
|
474
507
|
default:
|
|
475
|
-
return
|
|
508
|
+
return 5;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// =============================================================================
|
|
513
|
+
// Session-bound helpers
|
|
514
|
+
// =============================================================================
|
|
515
|
+
//
|
|
516
|
+
// Higher-level helpers that bind to an existing simulation session.
|
|
517
|
+
// Wrap `submitSimulationSteps` / `simulationBatchReads` for the common
|
|
518
|
+
// patterns simulation tests reach for: read a fungible-token balance,
|
|
519
|
+
// read a principal's STX balance / nonce, send a contract call and
|
|
520
|
+
// decode the result.
|
|
521
|
+
//
|
|
522
|
+
// Errors:
|
|
523
|
+
// - Wire-level failures (HTTP non-2xx) propagate as the typed
|
|
524
|
+
// `SimulationError` from `./simulation-api`.
|
|
525
|
+
// - Per-step `Err` results (validation failures) throw a normal
|
|
526
|
+
// `Error` carrying the upstream message.
|
|
527
|
+
// - VM-level failures (`vm_error`) and post-condition aborts are
|
|
528
|
+
// returned via {@link CallContractResult.vmError} / `pcAborted` —
|
|
529
|
+
// they do NOT throw, so callers can assert on them.
|
|
530
|
+
|
|
531
|
+
export interface CallContractResult {
|
|
532
|
+
/** Decoded Clarity result (e.g. `(ok true)`, `(err u100)`, `u5`). */
|
|
533
|
+
result: string;
|
|
534
|
+
/** Hex-encoded raw Clarity result (SIP-005). */
|
|
535
|
+
resultHex: string;
|
|
536
|
+
/** VM-level error message, if any (e.g. arithmetic overflow). */
|
|
537
|
+
vmError: string | null;
|
|
538
|
+
/** True when one or more post-conditions tripped. */
|
|
539
|
+
pcAborted: boolean;
|
|
540
|
+
/** Full upstream receipt for callers that need more detail. */
|
|
541
|
+
receipt: TransactionReceipt;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Build, submit, and decode a contract-call transaction. The caller
|
|
546
|
+
* supplies the principal to use as `tx-sender`; nonce is auto-fetched
|
|
547
|
+
* via {@link getNonce} unless `nonce` is explicitly provided.
|
|
548
|
+
*/
|
|
549
|
+
export async function callContract(
|
|
550
|
+
sessionId: string,
|
|
551
|
+
args: Omit<ContractCallTxArgs, 'nonce'> & {
|
|
552
|
+
nonce?: number;
|
|
553
|
+
postConditionMode?: PostConditionMode;
|
|
554
|
+
},
|
|
555
|
+
options: SimulationApiOptions = {},
|
|
556
|
+
): Promise<CallContractResult> {
|
|
557
|
+
const nonce =
|
|
558
|
+
args.nonce ?? Number(await getNonce(sessionId, args.sender, options));
|
|
559
|
+
const txHex = await buildUnsignedContractCallHex({ ...args, nonce });
|
|
560
|
+
const r = await submitSimulationSteps(
|
|
561
|
+
sessionId,
|
|
562
|
+
{ steps: [{ Transaction: txHex }] },
|
|
563
|
+
options,
|
|
564
|
+
);
|
|
565
|
+
const step = r.steps[0];
|
|
566
|
+
if (!('Transaction' in step)) {
|
|
567
|
+
throw new Error(
|
|
568
|
+
`expected Transaction step, got ${JSON.stringify(step).slice(0, 200)}`,
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
if ('Err' in step.Transaction) {
|
|
572
|
+
throw new Error(`Transaction Err: ${step.Transaction.Err}`);
|
|
573
|
+
}
|
|
574
|
+
const receipt = step.Transaction.Ok;
|
|
575
|
+
return {
|
|
576
|
+
result: cvToString(deserializeCV(receipt.result)),
|
|
577
|
+
resultHex: receipt.result,
|
|
578
|
+
vmError: receipt.vm_error,
|
|
579
|
+
pcAborted: receipt.post_condition_aborted,
|
|
580
|
+
receipt,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Read the SIP-010 / hBTC-style `(get-balance principal)` value.
|
|
586
|
+
* Returns 0n if the read returns `(err …)`. Throws on infra failures.
|
|
587
|
+
*/
|
|
588
|
+
export async function getFtBalance(
|
|
589
|
+
sessionId: string,
|
|
590
|
+
contractId: string,
|
|
591
|
+
principal: string,
|
|
592
|
+
options: SimulationApiOptions = {},
|
|
593
|
+
): Promise<bigint> {
|
|
594
|
+
const r = await simulationBatchReads(
|
|
595
|
+
sessionId,
|
|
596
|
+
{
|
|
597
|
+
readonly: [
|
|
598
|
+
[contractId, 'get-balance', serializeCV(Cl.principal(principal))],
|
|
599
|
+
],
|
|
600
|
+
},
|
|
601
|
+
options,
|
|
602
|
+
);
|
|
603
|
+
const v = r.readonly?.[0];
|
|
604
|
+
if (!v || !('Ok' in v)) {
|
|
605
|
+
throw new Error(`get-balance(${contractId}) failed: ${JSON.stringify(v)}`);
|
|
606
|
+
}
|
|
607
|
+
const decoded = deserializeCV(v.Ok);
|
|
608
|
+
if (decoded.type === ClarityType.ResponseErr) {
|
|
609
|
+
return 0n;
|
|
610
|
+
}
|
|
611
|
+
if (decoded.type !== ClarityType.ResponseOk) {
|
|
612
|
+
throw new Error(`unexpected get-balance shape: ${cvToString(decoded)}`);
|
|
613
|
+
}
|
|
614
|
+
if (decoded.value.type !== ClarityType.UInt) {
|
|
615
|
+
throw new Error(`expected uint inside (ok …), got ${decoded.value.type}`);
|
|
616
|
+
}
|
|
617
|
+
return BigInt(decoded.value.value);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Read a principal's STX balance (uSTX) from a simulation session. */
|
|
621
|
+
export async function getStxBalance(
|
|
622
|
+
sessionId: string,
|
|
623
|
+
principal: string,
|
|
624
|
+
options: SimulationApiOptions = {},
|
|
625
|
+
): Promise<bigint> {
|
|
626
|
+
const r = await simulationBatchReads(
|
|
627
|
+
sessionId,
|
|
628
|
+
{ stx: [principal] },
|
|
629
|
+
options,
|
|
630
|
+
);
|
|
631
|
+
const v = r.stx?.[0];
|
|
632
|
+
if (!v || !('Ok' in v)) {
|
|
633
|
+
throw new Error(`stx balance read for ${principal} failed`);
|
|
634
|
+
}
|
|
635
|
+
return BigInt(v.Ok);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** Read a principal's current nonce. Returns 0n if never seen. */
|
|
639
|
+
export async function getNonce(
|
|
640
|
+
sessionId: string,
|
|
641
|
+
principal: string,
|
|
642
|
+
options: SimulationApiOptions = {},
|
|
643
|
+
): Promise<bigint> {
|
|
644
|
+
const r = await simulationBatchReads(
|
|
645
|
+
sessionId,
|
|
646
|
+
{ nonces: [principal] },
|
|
647
|
+
options,
|
|
648
|
+
);
|
|
649
|
+
const v = r.nonces?.[0];
|
|
650
|
+
if (!v || !('Ok' in v)) return 0n;
|
|
651
|
+
return BigInt(v.Ok);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Read a Clarity data variable. Returns the decoded `ClarityValue`,
|
|
656
|
+
* or throws if the read failed.
|
|
657
|
+
*/
|
|
658
|
+
export async function readDataVar(
|
|
659
|
+
sessionId: string,
|
|
660
|
+
contractId: string,
|
|
661
|
+
varName: string,
|
|
662
|
+
options: SimulationApiOptions = {},
|
|
663
|
+
): Promise<ClarityValue> {
|
|
664
|
+
const r = await simulationBatchReads(
|
|
665
|
+
sessionId,
|
|
666
|
+
{ vars: [[contractId, varName]] },
|
|
667
|
+
options,
|
|
668
|
+
);
|
|
669
|
+
const v = r.vars?.[0];
|
|
670
|
+
if (!v || !('Ok' in v)) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
`var-get ${contractId} ${varName} failed: ${JSON.stringify(v)}`,
|
|
673
|
+
);
|
|
476
674
|
}
|
|
675
|
+
return deserializeCV(v.Ok);
|
|
477
676
|
}
|
|
478
677
|
|
|
479
678
|
// Re-export simulation types
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction-building helpers for the simulator.
|
|
3
|
+
*
|
|
4
|
+
* The simulator does NOT verify signatures — it derives `tx-sender`
|
|
5
|
+
* from the `signer` field (= hash160 of the public key) plus the
|
|
6
|
+
* `hashMode` byte. By overriding those two fields on an *unsigned*
|
|
7
|
+
* transaction, you can simulate any sender without a private key.
|
|
8
|
+
*
|
|
9
|
+
* {@link setSender} performs the override; {@link buildUnsignedContractCallHex}
|
|
10
|
+
* combines the unsigned-tx builder from `@stacks/transactions` with
|
|
11
|
+
* `setSender` and returns a hex string ready to drop into a
|
|
12
|
+
* `{ Transaction: <hex> }` simulation step.
|
|
13
|
+
*/
|
|
14
|
+
import { AddressVersion, type StacksNetworkName } from '@stacks/network';
|
|
15
|
+
import {
|
|
16
|
+
AddressHashMode,
|
|
17
|
+
Cl,
|
|
18
|
+
type ClarityValue,
|
|
19
|
+
type MultiSigSpendingCondition,
|
|
20
|
+
makeUnsignedContractCall,
|
|
21
|
+
PostConditionMode,
|
|
22
|
+
type StacksTransactionWire,
|
|
23
|
+
serializeTransaction,
|
|
24
|
+
} from '@stacks/transactions';
|
|
25
|
+
import { c32addressDecode } from 'c32check';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Override the spending condition's `signer` (and `hashMode`) so the
|
|
29
|
+
* simulator treats `tx-sender` as `sender`.
|
|
30
|
+
*
|
|
31
|
+
* Single-sig and multi-sig flavours are both handled — the multi-sig
|
|
32
|
+
* spending condition is rebuilt with empty signature fields because
|
|
33
|
+
* the simulator never validates signatures.
|
|
34
|
+
*/
|
|
35
|
+
export function setSender(
|
|
36
|
+
tx: StacksTransactionWire,
|
|
37
|
+
sender: string,
|
|
38
|
+
): StacksTransactionWire {
|
|
39
|
+
const [addressVersion, signer] = c32addressDecode(sender);
|
|
40
|
+
switch (addressVersion) {
|
|
41
|
+
case AddressVersion.MainnetSingleSig:
|
|
42
|
+
case AddressVersion.TestnetSingleSig:
|
|
43
|
+
tx.auth.spendingCondition.hashMode = AddressHashMode.P2PKH;
|
|
44
|
+
tx.auth.spendingCondition.signer = signer;
|
|
45
|
+
break;
|
|
46
|
+
case AddressVersion.MainnetMultiSig:
|
|
47
|
+
case AddressVersion.TestnetMultiSig: {
|
|
48
|
+
const sc = tx.auth.spendingCondition;
|
|
49
|
+
tx.auth.spendingCondition = {
|
|
50
|
+
hashMode: AddressHashMode.P2SH,
|
|
51
|
+
signer,
|
|
52
|
+
fields: [],
|
|
53
|
+
signaturesRequired: 0,
|
|
54
|
+
nonce: sc.nonce,
|
|
55
|
+
fee: sc.fee,
|
|
56
|
+
} as MultiSigSpendingCondition;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
default:
|
|
60
|
+
throw new Error(`unsupported address version: ${addressVersion}`);
|
|
61
|
+
}
|
|
62
|
+
return tx;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ContractCallTxArgs {
|
|
66
|
+
/** Principal to set as `tx-sender`. */
|
|
67
|
+
sender: string;
|
|
68
|
+
/** Fully-qualified contract id, e.g. `"SP1G48….dia-oracle"`. */
|
|
69
|
+
contract: string;
|
|
70
|
+
functionName: string;
|
|
71
|
+
functionArgs: ClarityValue[];
|
|
72
|
+
/** microSTX. Defaults to 1_000. */
|
|
73
|
+
fee?: number;
|
|
74
|
+
/** Nonce to declare. Caller is responsible for sequencing. */
|
|
75
|
+
nonce: number;
|
|
76
|
+
network?: StacksNetworkName;
|
|
77
|
+
/**
|
|
78
|
+
* Post-condition mode. Defaults to `Allow` so the simulator runs
|
|
79
|
+
* the call regardless of asset movements; switch to `Deny` + an
|
|
80
|
+
* explicit list when a test wants to assert post-condition
|
|
81
|
+
* enforcement.
|
|
82
|
+
*/
|
|
83
|
+
postConditionMode?: PostConditionMode;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build an unsigned contract-call transaction with `sender` patched
|
|
88
|
+
* in via {@link setSender}, then return the serialized hex (no `0x`
|
|
89
|
+
* prefix). Drop the result directly into `{ Transaction: <hex> }`.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* import { Cl } from '@stacks/transactions';
|
|
94
|
+
* import { buildUnsignedContractCallHex } from 'stxer';
|
|
95
|
+
*
|
|
96
|
+
* const txHex = await buildUnsignedContractCallHex({
|
|
97
|
+
* sender: 'SP3573...',
|
|
98
|
+
* contract: 'SM3VDXK...sbtc-deposit',
|
|
99
|
+
* functionName: 'complete-deposit-wrapper',
|
|
100
|
+
* functionArgs: [Cl.bufferFromHex(txid), Cl.uint(0), Cl.uint(amount), ...],
|
|
101
|
+
* nonce: 5,
|
|
102
|
+
* });
|
|
103
|
+
* await submitSimulationSteps(sim, { steps: [{ Transaction: txHex }] });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export async function buildUnsignedContractCallHex(
|
|
107
|
+
args: ContractCallTxArgs,
|
|
108
|
+
): Promise<string> {
|
|
109
|
+
const [contractAddress, contractName] = args.contract.split('.');
|
|
110
|
+
if (!contractName) {
|
|
111
|
+
throw new Error(`bad contract id "${args.contract}" — expected SP….name`);
|
|
112
|
+
}
|
|
113
|
+
const tx = await makeUnsignedContractCall({
|
|
114
|
+
contractAddress,
|
|
115
|
+
contractName,
|
|
116
|
+
functionName: args.functionName,
|
|
117
|
+
functionArgs: args.functionArgs,
|
|
118
|
+
fee: args.fee ?? 1_000,
|
|
119
|
+
nonce: args.nonce,
|
|
120
|
+
// Dummy 33-byte compressed pubkey. The simulator never validates
|
|
121
|
+
// the signature; setSender overwrites the derived signer hash.
|
|
122
|
+
publicKey: '0'.repeat(66),
|
|
123
|
+
network: args.network ?? 'mainnet',
|
|
124
|
+
postConditionMode: args.postConditionMode ?? PostConditionMode.Allow,
|
|
125
|
+
});
|
|
126
|
+
setSender(tx, args.sender);
|
|
127
|
+
return serializeTransaction(tx);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Build a `Cl.contractPrincipal` from a `"SP….name"` id string.
|
|
132
|
+
* Trips on standard principals or malformed ids — those should use
|
|
133
|
+
* `Cl.principal` directly.
|
|
134
|
+
*/
|
|
135
|
+
export function ftPrincipal(contractId: string): ClarityValue {
|
|
136
|
+
const [addr, name] = contractId.split('.');
|
|
137
|
+
if (!name) {
|
|
138
|
+
throw new Error(`bad contract id "${contractId}" — expected SP….name`);
|
|
139
|
+
}
|
|
140
|
+
return Cl.contractPrincipal(addr, name);
|
|
141
|
+
}
|