mirror of
https://github.com/stxer/stxer-sdk.git
synced 2026-01-12 07:23:57 +08:00
feat: support multisig address
This commit is contained in:
28
.vscode/settings.json
vendored
Normal file
28
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.formatOnSave": true,
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"typescript.preferences.importModuleSpecifier": "shortest",
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
}
|
||||
63
biome.json
Normal file
63
biome.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/coverage",
|
||||
"!**/dist",
|
||||
"!**/node_modules",
|
||||
"!**/cache"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 80
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "single",
|
||||
"trailingCommas": "all",
|
||||
"semicolons": "always"
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"complexity": {
|
||||
"noBannedTypes": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedVariables": "error",
|
||||
"noUnusedImports": "error"
|
||||
},
|
||||
"style": {
|
||||
"noParameterAssign": "error",
|
||||
"useAsConstAssertion": "error",
|
||||
"useDefaultParameterLast": "error",
|
||||
"useEnumInitializers": "error",
|
||||
"useSelfClosingElements": "error",
|
||||
"useSingleVarDeclarator": "error",
|
||||
"noUnusedTemplateLiteral": "error",
|
||||
"useNumberNamespace": "error",
|
||||
"noInferrableTypes": "error",
|
||||
"noUselessElse": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
}
|
||||
}
|
||||
29
package.json
29
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stxer",
|
||||
"version": "0.4.2",
|
||||
"version": "0.4.3",
|
||||
"license": "MIT",
|
||||
"author": "Kyle Fang",
|
||||
"repository": {
|
||||
@@ -17,7 +17,7 @@
|
||||
"scripts": {
|
||||
"analyze": "size-limit --why",
|
||||
"build": "dts build",
|
||||
"lint": "dts lint",
|
||||
"lint": "dts lint && biome check .",
|
||||
"prepare": "dts build",
|
||||
"size": "size-limit",
|
||||
"start": "dts watch",
|
||||
@@ -27,7 +27,7 @@
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "dts lint"
|
||||
"pre-commit": "dts lint && biome check ."
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
@@ -53,22 +53,23 @@
|
||||
}
|
||||
],
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "^11.1.5",
|
||||
"@tsconfig/recommended": "^1.0.7",
|
||||
"@types/node": "^22.13.5",
|
||||
"@biomejs/biome": "^2.2.4",
|
||||
"@size-limit/preset-small-lib": "^11.2.0",
|
||||
"@tsconfig/recommended": "^1.0.10",
|
||||
"@types/node": "^24.3.3",
|
||||
"dts-cli": "^2.0.5",
|
||||
"husky": "^9.1.6",
|
||||
"size-limit": "^11.1.5",
|
||||
"tslib": "^2.7.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.6.2"
|
||||
"husky": "^9.1.7",
|
||||
"size-limit": "^11.2.0",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stacks/network": "^7.0.0",
|
||||
"@stacks/network": "^7.2.0",
|
||||
"@stacks/stacks-blockchain-api-types": "^7.14.1",
|
||||
"@stacks/transactions": "^7.0.0",
|
||||
"@stacks/transactions": "^7.2.0",
|
||||
"c32check": "^2.0.0",
|
||||
"clarity-abi": "^0.1.0",
|
||||
"ts-clarity": "^0.1.0-pre.2"
|
||||
"ts-clarity": "^0.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
4617
pnpm-lock.yaml
generated
4617
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
@@ -59,7 +59,7 @@ function convertResults(
|
||||
|
||||
export async function batchRead(
|
||||
reads: BatchReads,
|
||||
options: BatchApiOptions = {}
|
||||
options: BatchApiOptions = {},
|
||||
): Promise<BatchReadsResult> {
|
||||
const ibh =
|
||||
reads.index_block_hash == null
|
||||
@@ -77,7 +77,10 @@ export async function batchRead(
|
||||
|
||||
if (reads.variables != null) {
|
||||
for (const variable of reads.variables) {
|
||||
payload.vars.push([serializeCV(variable.contract), variable.variableName]);
|
||||
payload.vars.push([
|
||||
serializeCV(variable.contract),
|
||||
variable.variableName,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +99,7 @@ export async function batchRead(
|
||||
payload.readonly.push([
|
||||
serializeCV(ro.contract),
|
||||
ro.functionName,
|
||||
...ro.functionArgs.map(v => serializeCV(v)),
|
||||
...ro.functionArgs.map((v) => serializeCV(v)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -132,4 +135,4 @@ export async function batchRead(
|
||||
maps: convertResults(rs.maps),
|
||||
readonly: convertResults(rs.readonly),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
import {
|
||||
type ClarityValue,
|
||||
type OptionalCV,
|
||||
contractPrincipalCV,
|
||||
type OptionalCV,
|
||||
} from '@stacks/transactions';
|
||||
import { type BatchReads, batchRead } from './BatchAPI';
|
||||
|
||||
@@ -175,4 +175,4 @@ export class BatchProcessor {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ import type {
|
||||
TContractPrincipal,
|
||||
TPrincipal,
|
||||
} from 'clarity-abi';
|
||||
import { decodeAbi, encodeAbi } from 'ts-clarity';
|
||||
import type {
|
||||
InferReadonlyCallParameterType,
|
||||
InferReadonlyCallResultType,
|
||||
InferMapValueType,
|
||||
InferReadMapParameterType,
|
||||
InferReadonlyCallParameterType,
|
||||
InferReadonlyCallResultType,
|
||||
InferReadVariableParameterType,
|
||||
InferVariableType,
|
||||
} from 'ts-clarity';
|
||||
import { decodeAbi, encodeAbi } from 'ts-clarity';
|
||||
import { BatchProcessor } from './BatchProcessor';
|
||||
|
||||
// Shared processor instance with default settings
|
||||
@@ -60,14 +60,16 @@ export async function callReadonly<
|
||||
const processor = params.batchProcessor ?? defaultProcessor;
|
||||
const [deployer, contractName] = params.contract.split('.', 2);
|
||||
const fn = String(params.functionName);
|
||||
|
||||
|
||||
const functionDef = (params.abi as readonly ClarityAbiFunction[]).find(
|
||||
(def) => def.name === params.functionName,
|
||||
);
|
||||
if (!functionDef) {
|
||||
throw new Error(`failed to find function definition for ${params.functionName}`);
|
||||
throw new Error(
|
||||
`failed to find function definition for ${params.functionName}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const argsKV = (params as unknown as { args: Record<string, unknown> }).args;
|
||||
const args: ClarityValue[] = [];
|
||||
for (const argDef of functionDef.args) {
|
||||
@@ -87,7 +89,9 @@ export async function callReadonly<
|
||||
resolve: (result: ClarityValue | OptionalCV) => {
|
||||
try {
|
||||
const decoded = decodeAbi(functionDef.outputs.type, result);
|
||||
resolve(decoded as InferReadonlyCallResultType<Functions, FunctionName>);
|
||||
resolve(
|
||||
decoded as InferReadonlyCallResultType<Functions, FunctionName>,
|
||||
);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
@@ -98,21 +102,23 @@ export async function callReadonly<
|
||||
}
|
||||
|
||||
export async function readMap<
|
||||
Maps extends readonly ClarityAbiMap[] | readonly unknown[] = readonly ClarityAbiMap[],
|
||||
Maps extends
|
||||
| readonly ClarityAbiMap[]
|
||||
| readonly unknown[] = readonly ClarityAbiMap[],
|
||||
MapName extends string = string,
|
||||
>(
|
||||
params: InferReadMapParameterType<Maps, MapName> & ReadMapRuntimeParameters,
|
||||
): Promise<InferMapValueType<Maps, MapName> | null> {
|
||||
const processor = params.batchProcessor ?? defaultProcessor;
|
||||
const [deployer, contractName] = params.contract.split('.', 2);
|
||||
|
||||
|
||||
const mapDef = (params.abi as readonly ClarityAbiMap[]).find(
|
||||
(m) => m.name === params.mapName,
|
||||
);
|
||||
if (!mapDef) {
|
||||
throw new Error(`failed to find map definition for ${params.mapName}`);
|
||||
}
|
||||
|
||||
|
||||
const key: ClarityValue = encodeAbi(mapDef.key, params.key);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -134,7 +140,10 @@ export async function readMap<
|
||||
if (result.type !== ClarityType.OptionalSome) {
|
||||
throw new Error(`unexpected map value: ${result}`);
|
||||
}
|
||||
const someCV = result as { type: ClarityType.OptionalSome; value: ClarityValue };
|
||||
const someCV = result as {
|
||||
type: ClarityType.OptionalSome;
|
||||
value: ClarityValue;
|
||||
};
|
||||
const decoded = decodeAbi(mapDef.value, someCV.value);
|
||||
resolve(decoded as InferMapValueType<Maps, MapName>);
|
||||
} catch (error) {
|
||||
@@ -147,7 +156,9 @@ export async function readMap<
|
||||
}
|
||||
|
||||
export async function readVariable<
|
||||
Variables extends readonly ClarityAbiVariable[] | readonly unknown[] = readonly ClarityAbiVariable[],
|
||||
Variables extends
|
||||
| readonly ClarityAbiVariable[]
|
||||
| readonly unknown[] = readonly ClarityAbiVariable[],
|
||||
VariableName extends string = string,
|
||||
>(
|
||||
params: InferReadVariableParameterType<Variables, VariableName> &
|
||||
@@ -155,12 +166,14 @@ export async function readVariable<
|
||||
): Promise<InferVariableType<Variables, VariableName>> {
|
||||
const processor = params.batchProcessor ?? defaultProcessor;
|
||||
const [deployer, contractName] = params.contract.split('.', 2);
|
||||
|
||||
|
||||
const varDef = (params.abi as readonly ClarityAbiVariable[]).find(
|
||||
(def) => def.name === params.variableName,
|
||||
);
|
||||
if (!varDef) {
|
||||
throw new Error(`failed to find variable definition for ${params.variableName}`);
|
||||
throw new Error(
|
||||
`failed to find variable definition for ${params.variableName}`,
|
||||
);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -183,4 +196,4 @@ export async function readVariable<
|
||||
reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './BatchAPI';
|
||||
export * from './clarity-api';
|
||||
export * from './simulation';
|
||||
export * from './clarity-api';
|
||||
@@ -1,12 +1,14 @@
|
||||
import { uintCV } from '@stacks/transactions';
|
||||
import { SimulationBuilder } from '..';
|
||||
|
||||
const test = () => SimulationBuilder.new()
|
||||
.withSender('SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER')
|
||||
.inlineSimulation('1ab04a8d13d72a301b77b6af9d4f612b')
|
||||
.addContractDeploy({
|
||||
contract_name: 'test-simulation',
|
||||
source_code: `
|
||||
const sender = 'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER';
|
||||
|
||||
const test = () =>
|
||||
SimulationBuilder.new()
|
||||
.withSender(sender)
|
||||
.addContractDeploy({
|
||||
contract_name: 'test-simulation',
|
||||
source_code: `
|
||||
;; counter example
|
||||
(define-data-var counter uint u0)
|
||||
|
||||
@@ -23,24 +25,18 @@ const test = () => SimulationBuilder.new()
|
||||
(define-read-only (get-counter)
|
||||
(ok (var-get counter)))
|
||||
`,
|
||||
})
|
||||
.addEvalCode(
|
||||
'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER.test-simulation',
|
||||
'(get-counter)'
|
||||
)
|
||||
.addContractCall({
|
||||
contract_id: 'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER.test-simulation',
|
||||
function_name: 'increment',
|
||||
function_args: [uintCV(10)],
|
||||
})
|
||||
.addEvalCode(
|
||||
'SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER.test-simulation',
|
||||
'(get-counter)'
|
||||
)
|
||||
.run()
|
||||
})
|
||||
.addEvalCode(`${sender}.test-simulation`, '(get-counter)')
|
||||
.addContractCall({
|
||||
contract_id: `${sender}.test-simulation`,
|
||||
function_name: 'increment',
|
||||
function_args: [uintCV(10)],
|
||||
})
|
||||
.addEvalCode(`${sender}.test-simulation`, '(get-counter)')
|
||||
.run();
|
||||
|
||||
if (require.main === module) {
|
||||
; (async () => {
|
||||
await test()
|
||||
(async () => {
|
||||
await test();
|
||||
})().catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
tupleCV,
|
||||
uintCV,
|
||||
} from '@stacks/transactions';
|
||||
import { SIP010TraitABI } from 'clarity-abi/abis'
|
||||
import { SIP010TraitABI } from 'clarity-abi/abis';
|
||||
import { unwrapResponse } from 'ts-clarity';
|
||||
import { batchRead } from '../BatchAPI';
|
||||
import { BatchProcessor } from '../BatchProcessor';
|
||||
import { callReadonly, readMap, readVariable } from '../clarity-api';
|
||||
import { unwrapResponse } from 'ts-clarity';
|
||||
|
||||
async function batchReadsExample() {
|
||||
const rs = await batchRead({
|
||||
@@ -18,21 +18,21 @@ async function batchReadsExample() {
|
||||
{
|
||||
contract: contractPrincipalCV(
|
||||
'SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275',
|
||||
'liquidity-token-v5kbe3oqvac'
|
||||
'liquidity-token-v5kbe3oqvac',
|
||||
),
|
||||
variableName: 'balance-x',
|
||||
},
|
||||
{
|
||||
contract: contractPrincipalCV(
|
||||
'SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275',
|
||||
'liquidity-token-v5kbe3oqvac'
|
||||
'liquidity-token-v5kbe3oqvac',
|
||||
),
|
||||
variableName: 'balance-y',
|
||||
},
|
||||
{
|
||||
contract: contractPrincipalCV(
|
||||
'SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275',
|
||||
'liquidity-token-v5kbe3oqvac'
|
||||
'liquidity-token-v5kbe3oqvac',
|
||||
),
|
||||
variableName: 'something-not-exists',
|
||||
},
|
||||
@@ -41,15 +41,15 @@ async function batchReadsExample() {
|
||||
{
|
||||
contract: contractPrincipalCV(
|
||||
'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM',
|
||||
'amm-registry-v2-01'
|
||||
'amm-registry-v2-01',
|
||||
),
|
||||
mapName: 'pools-data-map',
|
||||
mapKey: tupleCV({
|
||||
'token-x': principalCV(
|
||||
'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wstx-v2'
|
||||
'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wstx-v2',
|
||||
),
|
||||
'token-y': principalCV(
|
||||
'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex'
|
||||
'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
|
||||
),
|
||||
factor: uintCV(1e8),
|
||||
}),
|
||||
@@ -57,7 +57,7 @@ async function batchReadsExample() {
|
||||
{
|
||||
contract: contractPrincipalCV(
|
||||
'SP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1',
|
||||
'univ2-core'
|
||||
'univ2-core',
|
||||
),
|
||||
mapName: 'pools',
|
||||
mapKey: uintCV(1),
|
||||
@@ -65,7 +65,7 @@ async function batchReadsExample() {
|
||||
{
|
||||
contract: contractPrincipalCV(
|
||||
'SP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1',
|
||||
'contract-not-exists'
|
||||
'contract-not-exists',
|
||||
),
|
||||
mapName: 'pools',
|
||||
mapKey: uintCV(1),
|
||||
@@ -81,7 +81,6 @@ async function batchQueueProcessorExample() {
|
||||
batchDelayMs: 1000,
|
||||
});
|
||||
|
||||
|
||||
const promiseA = processor.read({
|
||||
mode: 'variable',
|
||||
contractAddress: 'SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275',
|
||||
@@ -105,7 +104,7 @@ async function batchSip010Example() {
|
||||
abi: SIP010TraitABI.functions,
|
||||
functionName: 'get-total-supply',
|
||||
contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
|
||||
}).then(unwrapResponse)
|
||||
}).then(unwrapResponse);
|
||||
const balance = callReadonly({
|
||||
abi: SIP010TraitABI.functions,
|
||||
functionName: 'get-balance',
|
||||
@@ -113,20 +112,18 @@ async function batchSip010Example() {
|
||||
args: {
|
||||
who: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
|
||||
},
|
||||
}).then(unwrapResponse)
|
||||
}).then(unwrapResponse);
|
||||
const paused = readVariable({
|
||||
abi: [{ name: 'paused', type: 'bool', access: 'variable' },],
|
||||
abi: [{ name: 'paused', type: 'bool', access: 'variable' }],
|
||||
variableName: 'paused',
|
||||
contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
|
||||
});
|
||||
const approved = readMap({
|
||||
abi: [
|
||||
{ key: 'principal', name: 'approved-tokens', value: 'bool' },
|
||||
],
|
||||
abi: [{ key: 'principal', name: 'approved-tokens', value: 'bool' }],
|
||||
mapName: 'approved-tokens',
|
||||
key: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
|
||||
contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
|
||||
})
|
||||
});
|
||||
const result = await Promise.all([supply, balance, paused, approved]);
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,55 @@
|
||||
import { type AccountDataResponse, getNodeInfo, richFetch } from 'ts-clarity';
|
||||
import type { Block } from '@stacks/stacks-blockchain-api-types';
|
||||
import {
|
||||
AddressVersion,
|
||||
STACKS_MAINNET,
|
||||
STACKS_TESTNET,
|
||||
type StacksNetworkName,
|
||||
} from '@stacks/network';
|
||||
import type { Block } from '@stacks/stacks-blockchain-api-types';
|
||||
import {
|
||||
AddressHashMode,
|
||||
bufferCV,
|
||||
type ClarityValue,
|
||||
ClarityVersion,
|
||||
PostConditionMode,
|
||||
type StacksTransactionWire,
|
||||
bufferCV,
|
||||
contractPrincipalCV,
|
||||
deserializeTransaction,
|
||||
type MultiSigSpendingCondition,
|
||||
makeUnsignedContractCall,
|
||||
makeUnsignedContractDeploy,
|
||||
makeUnsignedSTXTokenTransfer,
|
||||
PostConditionMode,
|
||||
type StacksTransactionWire,
|
||||
serializeCVBytes,
|
||||
stringAsciiCV,
|
||||
tupleCV,
|
||||
uintCV,
|
||||
} from '@stacks/transactions';
|
||||
import { c32addressDecode } from 'c32check';
|
||||
import { type AccountDataResponse, getNodeInfo, richFetch } from 'ts-clarity';
|
||||
|
||||
function setSender(tx: StacksTransactionWire, sender: string) {
|
||||
const [addressVersion, signer] = c32addressDecode(sender);
|
||||
switch (addressVersion) {
|
||||
case AddressVersion.MainnetSingleSig:
|
||||
case AddressVersion.TestnetSingleSig:
|
||||
tx.auth.spendingCondition.hashMode = AddressHashMode.P2PKH;
|
||||
tx.auth.spendingCondition.signer = signer;
|
||||
break;
|
||||
case AddressVersion.MainnetMultiSig:
|
||||
case AddressVersion.TestnetMultiSig: {
|
||||
const sc = tx.auth.spendingCondition;
|
||||
tx.auth.spendingCondition = {
|
||||
hashMode: AddressHashMode.P2SH,
|
||||
signer,
|
||||
fields: [],
|
||||
signaturesRequired: 0,
|
||||
nonce: sc.nonce,
|
||||
fee: sc.fee,
|
||||
} as MultiSigSpendingCondition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
|
||||
function runTx(tx: StacksTransactionWire) {
|
||||
// type 0: run transaction
|
||||
@@ -43,8 +71,8 @@ export function runEval({ contract_id, code }: SimulationEval) {
|
||||
tupleCV({
|
||||
contract: contractPrincipalCV(contract_address, contract_name),
|
||||
code: stringAsciiCV(code),
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -53,7 +81,7 @@ export async function runSimulation(
|
||||
apiEndpoint: string,
|
||||
block_hash: string,
|
||||
block_height: number,
|
||||
txs: (StacksTransactionWire | SimulationEval)[]
|
||||
txs: (StacksTransactionWire | SimulationEval)[],
|
||||
) {
|
||||
// Convert 'sim-v1' to Uint8Array
|
||||
const header = new TextEncoder().encode('sim-v1');
|
||||
@@ -73,7 +101,7 @@ export async function runSimulation(
|
||||
throw new Error('Invalid block hash format');
|
||||
}
|
||||
const hashBytes = new Uint8Array(
|
||||
matches.map((byte) => Number.parseInt(byte, 16))
|
||||
matches.map((byte) => Number.parseInt(byte, 16)),
|
||||
);
|
||||
|
||||
// Convert transactions to bytes
|
||||
@@ -128,10 +156,12 @@ export class SimulationBuilder {
|
||||
private constructor(options: SimulationBuilderOptions = {}) {
|
||||
this.network = options.network ?? 'mainnet';
|
||||
const isTestnet = this.network === 'testnet';
|
||||
|
||||
this.apiEndpoint = options.apiEndpoint ??
|
||||
|
||||
this.apiEndpoint =
|
||||
options.apiEndpoint ??
|
||||
(isTestnet ? 'https://testnet-api.stxer.xyz' : 'https://api.stxer.xyz');
|
||||
this.stacksNodeAPI = options.stacksNodeAPI ??
|
||||
this.stacksNodeAPI =
|
||||
options.stacksNodeAPI ??
|
||||
(isTestnet ? 'https://api.testnet.hiro.so' : 'https://api.hiro.so');
|
||||
}
|
||||
|
||||
@@ -139,37 +169,37 @@ export class SimulationBuilder {
|
||||
return new SimulationBuilder(options);
|
||||
}
|
||||
|
||||
// biome-ignore lint/style/useNumberNamespace: <explanation>
|
||||
// biome-ignore lint/style/useNumberNamespace: ignore this
|
||||
private block = NaN;
|
||||
private sender = '';
|
||||
private steps: (
|
||||
| {
|
||||
// inline simulation
|
||||
simulationId: string;
|
||||
}
|
||||
// inline simulation
|
||||
simulationId: string;
|
||||
}
|
||||
| {
|
||||
// contract call
|
||||
contract_id: string;
|
||||
function_name: string;
|
||||
function_args?: ClarityValue[];
|
||||
sender: string;
|
||||
fee: number;
|
||||
}
|
||||
// contract call
|
||||
contract_id: string;
|
||||
function_name: string;
|
||||
function_args?: ClarityValue[];
|
||||
sender: string;
|
||||
fee: number;
|
||||
}
|
||||
| {
|
||||
// contract deploy
|
||||
contract_name: string;
|
||||
source_code: string;
|
||||
deployer: string;
|
||||
fee: number;
|
||||
clarity_version: ClarityVersion;
|
||||
}
|
||||
// contract deploy
|
||||
contract_name: string;
|
||||
source_code: string;
|
||||
deployer: string;
|
||||
fee: number;
|
||||
clarity_version: ClarityVersion;
|
||||
}
|
||||
| {
|
||||
// STX transfer
|
||||
recipient: string;
|
||||
amount: number;
|
||||
sender: string;
|
||||
fee: number;
|
||||
}
|
||||
// STX transfer
|
||||
recipient: string;
|
||||
amount: number;
|
||||
sender: string;
|
||||
fee: number;
|
||||
}
|
||||
| SimulationEval
|
||||
)[] = [];
|
||||
|
||||
@@ -184,7 +214,7 @@ export class SimulationBuilder {
|
||||
public inlineSimulation(simulationId: string) {
|
||||
this.steps.push({
|
||||
simulationId,
|
||||
})
|
||||
});
|
||||
return this;
|
||||
}
|
||||
public addSTXTransfer(params: {
|
||||
@@ -195,7 +225,7 @@ export class SimulationBuilder {
|
||||
}) {
|
||||
if (params.sender == null && this.sender === '') {
|
||||
throw new Error(
|
||||
'Please specify a sender with useSender or adding a sender paramenter'
|
||||
'Please specify a sender with useSender or adding a sender paramenter',
|
||||
);
|
||||
}
|
||||
this.steps.push({
|
||||
@@ -214,7 +244,7 @@ export class SimulationBuilder {
|
||||
}) {
|
||||
if (params.sender == null && this.sender === '') {
|
||||
throw new Error(
|
||||
'Please specify a sender with useSender or adding a sender paramenter'
|
||||
'Please specify a sender with useSender or adding a sender paramenter',
|
||||
);
|
||||
}
|
||||
this.steps.push({
|
||||
@@ -233,7 +263,7 @@ export class SimulationBuilder {
|
||||
}) {
|
||||
if (params.deployer == null && this.sender === '') {
|
||||
throw new Error(
|
||||
'Please specify a deployer with useSender or adding a deployer paramenter'
|
||||
'Please specify a deployer with useSender or adding a deployer paramenter',
|
||||
);
|
||||
}
|
||||
this.steps.push({
|
||||
@@ -274,7 +304,7 @@ export class SimulationBuilder {
|
||||
this.block = stacks_tip_height;
|
||||
}
|
||||
const info: Block = await richFetch(
|
||||
`${this.stacksNodeAPI}/extended/v1/block/by_height/${this.block}?unanchored=true`
|
||||
`${this.stacksNodeAPI}/extended/v1/block/by_height/${this.block}?unanchored=true`,
|
||||
).then((r) => r.json());
|
||||
if (
|
||||
info.height !== this.block ||
|
||||
@@ -282,7 +312,7 @@ export class SimulationBuilder {
|
||||
!info.hash.startsWith('0x')
|
||||
) {
|
||||
throw new Error(
|
||||
`failed to get block info for block height ${this.block}`
|
||||
`failed to get block info for block height ${this.block}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
@@ -302,21 +332,22 @@ SP212Y5JKN59YP3GYG07K3S8W5SSGE4KH6B5STXER
|
||||
|
||||
Feedbacks and feature requests are welcome.
|
||||
To get in touch: contact@stxer.xyz
|
||||
--------------------------------`
|
||||
--------------------------------`,
|
||||
);
|
||||
const block = await this.getBlockInfo();
|
||||
console.log(
|
||||
`Using block height ${block.block_height} hash 0x${block.block_hash} to run simulation.`
|
||||
`Using block height ${block.block_height} hash 0x${block.block_hash} to run simulation.`,
|
||||
);
|
||||
const txs: (StacksTransactionWire | SimulationEval)[] = [];
|
||||
const nonce_by_address = new Map<string, number>();
|
||||
const nextNonce = async (sender: string) => {
|
||||
const nonce = nonce_by_address.get(sender);
|
||||
if (nonce == null) {
|
||||
const url = `${this.stacksNodeAPI
|
||||
}/v2/accounts/${sender}?proof=${false}&tip=${block.index_block_hash}`;
|
||||
const url = `${
|
||||
this.stacksNodeAPI
|
||||
}/v2/accounts/${sender}?proof=${false}&tip=${block.index_block_hash}`;
|
||||
const account: AccountDataResponse = await richFetch(url).then((r) =>
|
||||
r.json()
|
||||
r.json(),
|
||||
);
|
||||
nonce_by_address.set(sender, account.nonce + 1);
|
||||
return account.nonce;
|
||||
@@ -336,7 +367,21 @@ To get in touch: contact@stxer.xyz
|
||||
}
|
||||
for (const step of this.steps) {
|
||||
if ('simulationId' in step) {
|
||||
const previousSimulation: {steps: ({tx: string} | {code: string, contract: string})[]} = await fetch(`https://api.stxer.xyz/simulations/${step.simulationId}/request`).then(x => x.json())
|
||||
const previousSimulation: {
|
||||
steps: ({ tx: string } | { code: string; contract: string })[];
|
||||
} = await fetch(
|
||||
`https://api.stxer.xyz/simulations/${step.simulationId}/request`,
|
||||
).then(async (rs) => {
|
||||
const body = await rs.text();
|
||||
if (!body.startsWith('{')) {
|
||||
throw new Error(
|
||||
`failed to get simulation ${step.simulationId}: ${body}`,
|
||||
);
|
||||
}
|
||||
return JSON.parse(body) as {
|
||||
steps: ({ tx: string } | { code: string; contract: string })[];
|
||||
};
|
||||
});
|
||||
for (const step of previousSimulation.steps) {
|
||||
if ('tx' in step) {
|
||||
txs.push(deserializeTransaction(step.tx));
|
||||
@@ -361,7 +406,7 @@ To get in touch: contact@stxer.xyz
|
||||
postConditionMode: PostConditionMode.Allow,
|
||||
fee: step.fee,
|
||||
});
|
||||
tx.auth.spendingCondition.signer = c32addressDecode(step.sender)[1];
|
||||
setSender(tx, step.sender);
|
||||
txs.push(tx);
|
||||
} else if ('sender' in step && 'recipient' in step) {
|
||||
const nonce = await nextNonce(step.sender);
|
||||
@@ -373,7 +418,7 @@ To get in touch: contact@stxer.xyz
|
||||
publicKey: '',
|
||||
fee: step.fee,
|
||||
});
|
||||
tx.auth.spendingCondition.signer = c32addressDecode(step.sender)[1];
|
||||
setSender(tx, step.sender);
|
||||
txs.push(tx);
|
||||
} else if ('deployer' in step) {
|
||||
const nonce = await nextNonce(step.deployer);
|
||||
@@ -386,28 +431,29 @@ To get in touch: contact@stxer.xyz
|
||||
postConditionMode: PostConditionMode.Allow,
|
||||
fee: step.fee,
|
||||
});
|
||||
tx.auth.spendingCondition.signer = c32addressDecode(step.deployer)[1];
|
||||
setSender(tx, step.deployer);
|
||||
txs.push(tx);
|
||||
} else if ('code' in step) {
|
||||
txs.push(step);
|
||||
} else {
|
||||
// biome-ignore lint/style/noUnusedTemplateLiteral: <explanation>
|
||||
console.log(`Invalid simulation step:`, step);
|
||||
console.log(`Invalid simulation step: ${step}`);
|
||||
}
|
||||
}
|
||||
const id = await runSimulation(
|
||||
`${this.apiEndpoint}/simulations`,
|
||||
block.block_hash,
|
||||
block.block_height,
|
||||
txs
|
||||
txs,
|
||||
);
|
||||
console.log(
|
||||
`Simulation will be available at: https://stxer.xyz/simulations/${this.network}/${id}`
|
||||
`Simulation will be available at: https://stxer.xyz/simulations/${this.network}/${id}`,
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
public pipe(transform: (builder: SimulationBuilder) => SimulationBuilder): SimulationBuilder {
|
||||
public pipe(
|
||||
transform: (builder: SimulationBuilder) => SimulationBuilder,
|
||||
): SimulationBuilder {
|
||||
return transform(this);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user