feat: add clarity-api with type-safe contract interactions

- Introduced `clarity-api.ts` with type-safe methods for reading Clarity contract data
- Added `callReadonly`, `readMap`, and `readVariable` functions with strong typing
- Integrated with `BatchProcessor` for efficient blockchain reads
- Updated dependencies to use latest `clarity-abi` and `ts-clarity`
- Added sample usage demonstrating SIP-010 token interactions
- Exported new methods in `index.ts`
This commit is contained in:
Kyle Fang
2025-02-23 14:22:15 +00:00
parent 8a84661463
commit cc57ebcb92
6 changed files with 391 additions and 120 deletions

View File

@@ -143,6 +143,40 @@ The BatchProcessor automatically:
This is particularly useful when you need to make multiple blockchain reads and want to optimize network calls.
### 3. Clarity API Utilities
The SDK provides convenient utilities for reading data from Clarity contracts:
```typescript
import { callReadonly, readVariable, readMap } from 'stxer';
import { SIP010TraitABI } from 'clarity-abi/abis';
import { unwrapResponse } from 'ts-clarity';
// Read from a contract function
const supply = await callReadonly({
abi: SIP010TraitABI.functions,
functionName: 'get-total-supply',
contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
}).then(unwrapResponse);
// Read a contract variable
const paused = await readVariable({
abi: [{ name: 'paused', type: 'bool', access: 'variable' }],
variableName: 'paused',
contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
});
// Read from a contract map
const approved = await readMap({
abi: [{ key: 'principal', name: 'approved-tokens', value: 'bool' }],
mapName: 'approved-tokens',
key: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
});
```
These utilities provide type-safe ways to interact with Clarity contracts, with built-in ABI support and response unwrapping.
## Configuration
You can customize the API endpoints:

View File

@@ -55,18 +55,20 @@
"devDependencies": {
"@size-limit/preset-small-lib": "^11.1.5",
"@tsconfig/recommended": "^1.0.7",
"@types/node": "^22.13.5",
"dts-cli": "^2.0.5",
"husky": "^9.1.6",
"size-limit": "^11.1.5",
"tslib": "^2.7.0",
"typescript": "^5.6.2",
"tsx": "^4.19.2"
"tsx": "^4.19.2",
"typescript": "^5.6.2"
},
"dependencies": {
"@stacks/network": "^7.0.0",
"@stacks/stacks-blockchain-api-types": "^7.14.1",
"@stacks/transactions": "^7.0.0",
"c32check": "^2.0.0",
"ts-clarity": "^0.0.26"
"clarity-abi": "^0.1.0",
"ts-clarity": "^0.1.0-pre.2"
}
}

216
pnpm-lock.yaml generated
View File

@@ -20,9 +20,12 @@ importers:
c32check:
specifier: ^2.0.0
version: 2.0.0
clarity-abi:
specifier: ^0.1.0
version: 0.1.0(typescript@5.6.2)
ts-clarity:
specifier: ^0.0.26
version: 0.0.26(typescript@5.6.2)
specifier: ^0.1.0-pre.2
version: 0.1.0-pre.2(typescript@5.6.2)
devDependencies:
'@size-limit/preset-small-lib':
specifier: ^11.1.5
@@ -30,9 +33,12 @@ importers:
'@tsconfig/recommended':
specifier: ^1.0.7
version: 1.0.7
'@types/node':
specifier: ^22.13.5
version: 22.13.5
dts-cli:
specifier: ^2.0.5
version: 2.0.5(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/babel__core@7.20.5)(@types/node@22.6.1)
version: 2.0.5(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/babel__core@7.20.5)(@types/node@22.13.5)
husky:
specifier: ^9.1.6
version: 9.1.6
@@ -1065,27 +1071,27 @@ packages:
peerDependencies:
size-limit: 11.1.5
'@stacks/common@6.16.0':
resolution: {integrity: sha512-PnzvhrdGRMVZvxTulitlYafSK4l02gPCBBoI9QEoTqgSnv62oaOXhYAUUkTMFKxdHW1seVEwZsrahuXiZPIAwg==}
'@stacks/common@7.0.0':
resolution: {integrity: sha512-/BKBK9S9GEuGjbnc2fBAwsG21f8cfNekG/9mXLSMwBqnh4qaQY2hxK+6wRI2YXJgpkXrpZilpZy2sdPGlVUdQA==}
'@stacks/network@6.16.0':
resolution: {integrity: sha512-uqz9Nb6uf+SeyCKENJN+idt51HAfEeggQKrOMfGjpAeFgZV2CR66soB/ci9+OVQR/SURvasncAz2ScI1blfS8A==}
'@stacks/common@7.0.2':
resolution: {integrity: sha512-+RSecHdkxOtswmE4tDDoZlYEuULpnTQVeDIG5eZ32opK8cFxf4EugAcK9CsIsHx/Se1yTEaQ21WGATmJGK84lQ==}
'@stacks/network@7.0.0':
resolution: {integrity: sha512-4diddT0ii85BQ4PW6ww3l4cS7Oo0a5VIsJ7umBcCPAArIc4Sm/MIOEXIg9joKK8fVHLnWyh1p4D+febJQFfa+Q==}
'@stacks/network@7.0.2':
resolution: {integrity: sha512-XzHnoWqku/jRrTgMXhmh3c+I0O9vDH24KlhzGDZtBu+8CGGyHNPAZzGwvoUShonMXrXjEnfO9IYQwV5aJhfv6g==}
'@stacks/stacks-blockchain-api-types@7.14.1':
resolution: {integrity: sha512-65hvhXxC+EUqHJAQsqlBCqXB+zwfxZICSKYJugdg6BCp9I9qniyfz5XyQeC4RMVo0tgEoRdS/b5ZCFo5kLWmxA==}
'@stacks/transactions@6.16.1':
resolution: {integrity: sha512-yCtUM+8IN0QJbnnlFhY1wBW7Q30Cxje3Zmy8DgqdBoM/EPPWadez/8wNWFANVAMyUZeQ9V/FY+8MAw4E+pCReA==}
'@stacks/transactions@7.0.0':
resolution: {integrity: sha512-9kGTnJLwRQPugLzbdJ8MmFED+eRhlJKIXpz2mshyy238hvBc4T0jynsoJMi4qGqvJYzsiRYCLDPJVkkUde85vA==}
'@stacks/transactions@7.0.4':
resolution: {integrity: sha512-OTQSqb+xaq+QeHB1m83SONVjVs9DD1SMqX7UvvLmzQx3ppZNoRqbWx9UGjm1bVwpQ+mw44TVOe7w3Zm0HRYFfw==}
'@tootallnate/once@2.0.0':
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
engines: {node: '>= 10'}
@@ -1117,9 +1123,6 @@ packages:
'@types/babel__traverse@7.20.6':
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
'@types/bn.js@5.1.6':
resolution: {integrity: sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==}
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
@@ -1153,11 +1156,8 @@ packages:
'@types/minimatch@5.1.2':
resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
'@types/node@18.19.50':
resolution: {integrity: sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==}
'@types/node@22.6.1':
resolution: {integrity: sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==}
'@types/node@22.13.5':
resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -1548,8 +1548,8 @@ packages:
cjs-module-lexer@1.4.1:
resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==}
clarity-abi@0.0.20:
resolution: {integrity: sha512-uAkKEp84Z3LnM8qruLJyB1LP2NEGwns6Sl6zBOiKeIgTfFMm3GrQlTgkFUboVV/RQwk0dJYc72sV8pvSrtn+Sg==}
clarity-abi@0.1.0:
resolution: {integrity: sha512-+hf+M/MXsZY7DeGsVOXwc6p8Fbq3xNm1Gdp2R3ASdUtXMDmdnAZPEg2RSI5kQu7sFhAkBvgLdpXvsv8E05m29g==}
peerDependencies:
typescript: '>=5.0.4'
peerDependenciesMeta:
@@ -1641,8 +1641,8 @@ packages:
cross-fetch@3.1.8:
resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==}
cross-fetch@4.0.0:
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
cross-fetch@4.1.0:
resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
@@ -3453,8 +3453,8 @@ packages:
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
engines: {node: '>=12'}
ts-clarity@0.0.26:
resolution: {integrity: sha512-XRwLquuSTDkw6iYEpznMEEgehqaIR9t313chiiZTfvtRCq/kWYXPHQhgdTw6wJBAHsqdVPuN69enwlKtu2IQPg==}
ts-clarity@0.1.0-pre.2:
resolution: {integrity: sha512-VejdNZm4+8e07BPdCvQShkcRd6TCT6l0OkjTxdv93EqIXUAdDXdm4YcowV0KYSD/U3VqSeJbGAzgnFmO0vEnhQ==}
ts-jest@29.2.5:
resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==}
@@ -3558,11 +3558,8 @@ packages:
unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
undici-types@6.20.0:
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
unicode-canonical-property-names-ecmascript@2.0.1:
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
@@ -4660,27 +4657,27 @@ snapshots:
'@jest/console@29.7.0':
dependencies:
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
chalk: 4.1.2
jest-message-util: 29.7.0
jest-util: 29.7.0
slash: 3.0.0
'@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))':
'@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))':
dependencies:
'@jest/console': 29.7.0
'@jest/reporters': 29.7.0
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
jest-config: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@@ -4705,7 +4702,7 @@ snapshots:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
jest-mock: 29.7.0
'@jest/expect-utils@29.7.0':
@@ -4723,7 +4720,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
'@types/node': 22.6.1
'@types/node': 22.13.5
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -4745,7 +4742,7 @@ snapshots:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.25
'@types/node': 22.6.1
'@types/node': 22.13.5
chalk: 4.1.2
collect-v8-coverage: 1.0.2
exit: 0.1.2
@@ -4815,7 +4812,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 22.6.1
'@types/node': 22.13.5
'@types/yargs': 17.0.33
chalk: 4.1.2
@@ -4958,19 +4955,9 @@ snapshots:
'@size-limit/file': 11.1.5(size-limit@11.1.5)
size-limit: 11.1.5
'@stacks/common@6.16.0':
dependencies:
'@types/bn.js': 5.1.6
'@types/node': 18.19.50
'@stacks/common@7.0.0': {}
'@stacks/network@6.16.0':
dependencies:
'@stacks/common': 6.16.0
cross-fetch: 3.1.8
transitivePeerDependencies:
- encoding
'@stacks/common@7.0.2': {}
'@stacks/network@7.0.0':
dependencies:
@@ -4979,19 +4966,15 @@ snapshots:
transitivePeerDependencies:
- encoding
'@stacks/stacks-blockchain-api-types@7.14.1': {}
'@stacks/transactions@6.16.1':
'@stacks/network@7.0.2':
dependencies:
'@noble/hashes': 1.1.5
'@noble/secp256k1': 1.7.1
'@stacks/common': 6.16.0
'@stacks/network': 6.16.0
c32check: 2.0.0
lodash.clonedeep: 4.5.0
'@stacks/common': 7.0.2
cross-fetch: 3.1.8
transitivePeerDependencies:
- encoding
'@stacks/stacks-blockchain-api-types@7.14.1': {}
'@stacks/transactions@7.0.0':
dependencies:
'@noble/hashes': 1.1.5
@@ -5003,6 +4986,17 @@ snapshots:
transitivePeerDependencies:
- encoding
'@stacks/transactions@7.0.4':
dependencies:
'@noble/hashes': 1.1.5
'@noble/secp256k1': 1.7.1
'@stacks/common': 7.0.2
'@stacks/network': 7.0.2
c32check: 2.0.0
lodash.clonedeep: 4.5.0
transitivePeerDependencies:
- encoding
'@tootallnate/once@2.0.0': {}
'@tsconfig/node10@1.0.11': {}
@@ -5036,20 +5030,16 @@ snapshots:
dependencies:
'@babel/types': 7.25.6
'@types/bn.js@5.1.6':
dependencies:
'@types/node': 18.19.50
'@types/estree@1.0.6': {}
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 22.6.1
'@types/node': 22.13.5
'@types/graceful-fs@4.1.9':
dependencies:
'@types/node': 22.6.1
'@types/node': 22.13.5
'@types/istanbul-lib-coverage@2.0.6': {}
@@ -5068,7 +5058,7 @@ snapshots:
'@types/jsdom@20.0.1':
dependencies:
'@types/node': 22.6.1
'@types/node': 22.13.5
'@types/tough-cookie': 4.0.5
parse5: 7.1.2
@@ -5078,13 +5068,9 @@ snapshots:
'@types/minimatch@5.1.2': {}
'@types/node@18.19.50':
'@types/node@22.13.5':
dependencies:
undici-types: 5.26.5
'@types/node@22.6.1':
dependencies:
undici-types: 6.19.8
undici-types: 6.20.0
'@types/parse-json@4.0.2': {}
@@ -5549,7 +5535,7 @@ snapshots:
cjs-module-lexer@1.4.1: {}
clarity-abi@0.0.20(typescript@5.6.2):
clarity-abi@0.1.0(typescript@5.6.2):
optionalDependencies:
typescript: 5.6.2
@@ -5617,13 +5603,13 @@ snapshots:
path-type: 4.0.0
yaml: 1.10.2
create-jest@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)):
create-jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)):
dependencies:
'@jest/types': 29.6.3
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
jest-config: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@@ -5640,7 +5626,7 @@ snapshots:
transitivePeerDependencies:
- encoding
cross-fetch@4.0.0:
cross-fetch@4.1.0:
dependencies:
node-fetch: 2.7.0
transitivePeerDependencies:
@@ -5778,7 +5764,7 @@ snapshots:
dependencies:
webidl-conversions: 7.0.0
dts-cli@2.0.5(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/babel__core@7.20.5)(@types/node@22.6.1):
dts-cli@2.0.5(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(@jest/transform@29.7.0)(@jest/types@29.6.3)(@types/babel__core@7.20.5)(@types/node@22.13.5):
dependencies:
'@babel/core': 7.25.2
'@babel/helper-module-imports': 7.24.7
@@ -5811,7 +5797,7 @@ snapshots:
eslint-config-prettier: 8.10.0(eslint@8.57.1)
eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint@8.57.1)
eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)
eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)))(typescript@5.6.2)
eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)))(typescript@5.6.2)
eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1)
eslint-plugin-prettier: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@2.8.8)
eslint-plugin-react: 7.36.1(eslint@8.57.1)
@@ -5820,9 +5806,9 @@ snapshots:
execa: 4.1.0
figlet: 1.7.0
fs-extra: 10.1.0
jest: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
jest: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
jest-environment-jsdom: 29.7.0
jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)))
jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)))
jpjs: 1.2.1
lodash.merge: 4.6.2
ora: 5.4.1
@@ -5840,8 +5826,8 @@ snapshots:
shelljs: 0.8.5
sort-package-json: 1.57.0
tiny-glob: 0.2.9
ts-jest: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)))(typescript@5.6.2)
ts-node: 10.9.2(@types/node@22.6.1)(typescript@5.6.2)
ts-jest: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)))(typescript@5.6.2)
ts-node: 10.9.2(@types/node@22.13.5)(typescript@5.6.2)
tslib: 2.7.0
type-fest: 2.19.0
typescript: 5.6.2
@@ -6095,13 +6081,13 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)))(typescript@5.6.2):
eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)))(typescript@5.6.2):
dependencies:
'@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.6.2)
eslint: 8.57.1
optionalDependencies:
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2)
jest: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
jest: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
transitivePeerDependencies:
- supports-color
- typescript
@@ -6777,7 +6763,7 @@ snapshots:
'@jest/expect': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
chalk: 4.1.2
co: 4.6.0
dedent: 1.5.3(babel-plugin-macros@3.1.0)
@@ -6797,16 +6783,16 @@ snapshots:
- babel-plugin-macros
- supports-color
jest-cli@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)):
jest-cli@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)):
dependencies:
'@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
'@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
create-jest: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
create-jest: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
exit: 0.1.2
import-local: 3.2.0
jest-config: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
jest-config: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@@ -6816,7 +6802,7 @@ snapshots:
- supports-color
- ts-node
jest-config@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)):
jest-config@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)):
dependencies:
'@babel/core': 7.25.2
'@jest/test-sequencer': 29.7.0
@@ -6841,8 +6827,8 @@ snapshots:
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 22.6.1
ts-node: 10.9.2(@types/node@22.6.1)(typescript@5.6.2)
'@types/node': 22.13.5
ts-node: 10.9.2(@types/node@22.13.5)(typescript@5.6.2)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@@ -6872,7 +6858,7 @@ snapshots:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/jsdom': 20.0.1
'@types/node': 22.6.1
'@types/node': 22.13.5
jest-mock: 29.7.0
jest-util: 29.7.0
jsdom: 20.0.3
@@ -6886,7 +6872,7 @@ snapshots:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -6896,7 +6882,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
'@types/node': 22.6.1
'@types/node': 22.13.5
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@@ -6935,7 +6921,7 @@ snapshots:
jest-mock@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
jest-util: 29.7.0
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@@ -6970,7 +6956,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.11
@@ -6998,7 +6984,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
chalk: 4.1.2
cjs-module-lexer: 1.4.1
collect-v8-coverage: 1.0.2
@@ -7044,7 +7030,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@@ -7059,11 +7045,11 @@ snapshots:
leven: 3.1.0
pretty-format: 29.7.0
jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))):
jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))):
dependencies:
ansi-escapes: 6.2.1
chalk: 5.3.0
jest: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
jest: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
jest-regex-util: 29.6.3
jest-watcher: 29.7.0
slash: 5.1.0
@@ -7074,7 +7060,7 @@ snapshots:
dependencies:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 22.6.1
'@types/node': 22.13.5
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@@ -7083,17 +7069,17 @@ snapshots:
jest-worker@29.7.0:
dependencies:
'@types/node': 22.6.1
'@types/node': 22.13.5
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
jest@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)):
jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)):
dependencies:
'@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
'@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
'@jest/types': 29.6.3
import-local: 3.2.0
jest-cli: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
jest-cli: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@@ -7941,22 +7927,22 @@ snapshots:
dependencies:
punycode: 2.3.1
ts-clarity@0.0.26(typescript@5.6.2):
ts-clarity@0.1.0-pre.2(typescript@5.6.2):
dependencies:
'@stacks/stacks-blockchain-api-types': 7.14.1
'@stacks/transactions': 6.16.1
clarity-abi: 0.0.20(typescript@5.6.2)
cross-fetch: 4.0.0
'@stacks/transactions': 7.0.4
clarity-abi: 0.1.0(typescript@5.6.2)
cross-fetch: 4.1.0
transitivePeerDependencies:
- encoding
- typescript
ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2)))(typescript@5.6.2):
ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2)))(typescript@5.6.2):
dependencies:
bs-logger: 0.2.6
ejs: 3.1.10
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@22.6.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2))
jest: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2))
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
@@ -7970,14 +7956,14 @@ snapshots:
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.25.2)
ts-node@10.9.2(@types/node@22.6.1)(typescript@5.6.2):
ts-node@10.9.2(@types/node@22.13.5)(typescript@5.6.2):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 22.6.1
'@types/node': 22.13.5
acorn: 8.12.1
acorn-walk: 8.3.4
arg: 4.1.3
@@ -8064,9 +8050,7 @@ snapshots:
has-symbols: 1.0.3
which-boxed-primitive: 1.0.2
undici-types@5.26.5: {}
undici-types@6.19.8: {}
undici-types@6.20.0: {}
unicode-canonical-property-names-ecmascript@2.0.1: {}

186
src/clarity-api.ts Normal file
View File

@@ -0,0 +1,186 @@
import {
ClarityType,
type ClarityValue,
type OptionalCV,
} from '@stacks/transactions';
import type {
ClarityAbiFunction,
ClarityAbiMap,
ClarityAbiVariable,
TContractPrincipal,
TPrincipal,
} from 'clarity-abi';
import { decodeAbi, encodeAbi } from 'ts-clarity';
import type {
InferReadonlyCallParameterType,
InferReadonlyCallResultType,
InferMapValueType,
InferReadMapParameterType,
InferReadVariableParameterType,
InferVariableType,
} from 'ts-clarity';
import { BatchProcessor } from './BatchProcessor';
// Shared processor instance with default settings
const defaultProcessor = new BatchProcessor({
batchDelayMs: 100,
});
export type ReadonlyCallRuntimeOptions = {
sender?: TPrincipal;
contract: TContractPrincipal;
stacksEndpoint?: string;
indexBlockHash?: string;
batchProcessor?: BatchProcessor;
};
export type ReadMapRuntimeParameters = {
contract: TContractPrincipal;
stacksEndpoint?: string;
proof?: boolean;
indexBlockHash?: string;
batchProcessor?: BatchProcessor;
};
export type ReadVariableRuntimeParameterType = {
contract: TContractPrincipal;
stacksEndpoint?: string;
proof?: boolean;
indexBlockHash?: string;
batchProcessor?: BatchProcessor;
};
export async function callReadonly<
Functions extends readonly ClarityAbiFunction[] | readonly unknown[],
FunctionName extends string,
>(
params: InferReadonlyCallParameterType<Functions, FunctionName> &
ReadonlyCallRuntimeOptions,
): Promise<InferReadonlyCallResultType<Functions, FunctionName>> {
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}`);
}
const argsKV = (params as unknown as { args: Record<string, unknown> }).args;
const args: ClarityValue[] = [];
for (const argDef of functionDef.args) {
args.push(encodeAbi(argDef.type, argsKV[argDef.name]));
}
return new Promise((resolve, reject) => {
processor.enqueue({
request: {
mode: 'readonly',
contractAddress: deployer,
contractName: contractName,
functionName: fn,
functionArgs: args,
},
tip: params.indexBlockHash,
resolve: (result: ClarityValue | OptionalCV) => {
try {
const decoded = decodeAbi(functionDef.outputs.type, result);
resolve(decoded as InferReadonlyCallResultType<Functions, FunctionName>);
} catch (error) {
reject(error);
}
},
reject,
});
});
}
export async function readMap<
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) => {
processor.enqueue({
request: {
mode: 'mapEntry',
contractAddress: deployer,
contractName: contractName,
mapName: params.mapName,
mapKey: key,
},
tip: params.indexBlockHash,
resolve: (result: ClarityValue | OptionalCV) => {
try {
if (result.type === ClarityType.OptionalNone) {
resolve(null);
return;
}
if (result.type !== ClarityType.OptionalSome) {
throw new Error(`unexpected map value: ${result}`);
}
const someCV = result as { type: ClarityType.OptionalSome; value: ClarityValue };
const decoded = decodeAbi(mapDef.value, someCV.value);
resolve(decoded as InferMapValueType<Maps, MapName>);
} catch (error) {
reject(error);
}
},
reject,
});
});
}
export async function readVariable<
Variables extends readonly ClarityAbiVariable[] | readonly unknown[] = readonly ClarityAbiVariable[],
VariableName extends string = string,
>(
params: InferReadVariableParameterType<Variables, VariableName> &
ReadVariableRuntimeParameterType,
): 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}`);
}
return new Promise((resolve, reject) => {
processor.enqueue({
request: {
mode: 'variable',
contractAddress: deployer,
contractName: contractName,
variableName: params.variableName,
},
tip: params.indexBlockHash,
resolve: (result: ClarityValue | OptionalCV) => {
try {
const decoded = decodeAbi(varDef.type, result);
resolve(decoded as InferVariableType<Variables, VariableName>);
} catch (error) {
reject(error);
}
},
reject,
});
});
}

View File

@@ -1,2 +1,3 @@
export * from './BatchAPI';
export * from './simulation';
export * from './clarity-api';

View File

@@ -1,12 +1,14 @@
import {
contractPrincipalCV,
principalCV,
stringAsciiCV,
tupleCV,
uintCV,
} from '@stacks/transactions';
import { SIP010TraitABI } from 'clarity-abi/abis'
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({
@@ -110,9 +112,71 @@ async function batchQueueProcessorExample() {
console.log(result);
}
// https://explorer.hiro.so/txid/SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.trait-sip-010
const sip010 = {
functions: [
{
name: 'get-balance',
access: 'read_only',
args: [
{
name: 'who',
type: 'principal',
},
],
outputs: {
type: {
response: {
ok: 'uint128',
error: 'none',
},
},
},
},
],
variables: [],
maps: [],
fungible_tokens: [],
non_fungible_tokens: [],
epoch: 'Epoch2_05',
clarity_version: 'Clarity1',
} as const;
async function batchSip010Example() {
const supply = callReadonly({
abi: SIP010TraitABI.functions,
functionName: 'get-total-supply',
contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
}).then(unwrapResponse)
const balance = callReadonly({
abi: SIP010TraitABI.functions,
functionName: 'get-balance',
contract: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex',
args: {
who: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01',
},
}).then(unwrapResponse)
const paused = readVariable({
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' },
],
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);
}
async function main() {
await batchReadsExample();
await batchQueueProcessorExample();
await batchSip010Example();
}
if (require.main === module) {