@hazbase/simplicity 0.0.1
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/LICENSE +182 -0
- package/README.md +778 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +628 -0
- package/dist/client/ContractFactory.d.ts +13 -0
- package/dist/client/ContractFactory.js +50 -0
- package/dist/client/DeployedContract.d.ts +12 -0
- package/dist/client/DeployedContract.js +43 -0
- package/dist/client/SimplicityClient.d.ts +21 -0
- package/dist/client/SimplicityClient.js +65 -0
- package/dist/core/artifact.d.ts +6 -0
- package/dist/core/artifact.js +100 -0
- package/dist/core/compiler.d.ts +3 -0
- package/dist/core/compiler.js +117 -0
- package/dist/core/errors.d.ts +33 -0
- package/dist/core/errors.js +70 -0
- package/dist/core/executor.d.ts +5 -0
- package/dist/core/executor.js +664 -0
- package/dist/core/presets.d.ts +16 -0
- package/dist/core/presets.js +251 -0
- package/dist/core/rpc.d.ts +7 -0
- package/dist/core/rpc.js +37 -0
- package/dist/core/summary.d.ts +6 -0
- package/dist/core/summary.js +27 -0
- package/dist/core/templating.d.ts +2 -0
- package/dist/core/templating.js +17 -0
- package/dist/core/toolchain.d.ts +14 -0
- package/dist/core/toolchain.js +82 -0
- package/dist/core/types.d.ts +258 -0
- package/dist/core/types.js +2 -0
- package/dist/gasless/RelayerClient.d.ts +13 -0
- package/dist/gasless/RelayerClient.js +76 -0
- package/dist/gasless/types.d.ts +144 -0
- package/dist/gasless/types.js +2 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +35 -0
- package/dist/presets/htlc.simf.tmpl +35 -0
- package/dist/presets/manifest.d.ts +1 -0
- package/dist/presets/manifest.js +5 -0
- package/dist/presets/p2pk.simf.tmpl +4 -0
- package/dist/presets/p2pkLockHeight.simf.tmpl +5 -0
- package/dist/presets/transferWithTimeout.simf.tmpl +26 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
# @hazbase/simplicity
|
|
2
|
+
|
|
3
|
+
`@hazbase/simplicity` is a Node.js / TypeScript SDK for working with Simplicity contracts on Liquid with an EVM-like developer workflow. It lets you compile SimplicityHL (`.simf`) contracts, derive the contract address, fund that address, inspect the spend you are about to make, execute the contract, and optionally run fee-sponsored flows through a sponsor wallet or relayer. It also ships with built-in presets so you can start from known-good contract templates before moving to custom `.simf` code.
|
|
4
|
+
|
|
5
|
+
This SDK is designed to help Node developers get productive quickly, but it is still opinionated and early-stage:
|
|
6
|
+
- SimplicityHL is still upstream work-in-progress and is not production-ready.
|
|
7
|
+
- This SDK currently optimizes for explicit / unblinded success paths first.
|
|
8
|
+
- Gasless support exists, but it comes in multiple modes with different tradeoffs.
|
|
9
|
+
|
|
10
|
+
## Who This Is For
|
|
11
|
+
|
|
12
|
+
This README is for you if:
|
|
13
|
+
- you are comfortable with Node.js / TypeScript,
|
|
14
|
+
- you want to experiment with Simplicity on Liquid without building everything from scratch,
|
|
15
|
+
- you want a clear path from `compile` to `fund` to `inspect` to `execute`,
|
|
16
|
+
- you want to understand how presets, custom contracts, witnesses, and gasless execution fit together.
|
|
17
|
+
|
|
18
|
+
This README is not assuming you already know Simplicity well. It will explain the model first, then show the happy path, then move into advanced topics.
|
|
19
|
+
|
|
20
|
+
## Mental Model First
|
|
21
|
+
|
|
22
|
+
Before touching the API, it helps to anchor on how Simplicity on Liquid differs from an EVM contract.
|
|
23
|
+
|
|
24
|
+
- A **Simplicity contract** is a spend condition that can be tied to a contract address.
|
|
25
|
+
- **Deploying** a contract is not a separate bytecode deployment transaction. In practice, you compile the contract, get its derived address, and fund that address with a UTXO.
|
|
26
|
+
- An **artifact** is the compile output plus the metadata you need later to inspect and execute that contract again.
|
|
27
|
+
- **Executing** a contract means consuming the contract UTXO and building a new transaction that satisfies the contract's witness rules.
|
|
28
|
+
- **Inspecting** a contract call means building the spend first and reviewing what will happen before broadcasting.
|
|
29
|
+
- **Gasless** means the fee is paid by a sponsor wallet or relayer instead of by the contract caller directly.
|
|
30
|
+
|
|
31
|
+
If you come from Ethereum, a helpful translation is:
|
|
32
|
+
- EVM `deploy contract` -> Simplicity `compile contract and fund its address`
|
|
33
|
+
- EVM `call contract` -> Simplicity `spend a contract UTXO with the correct witness`
|
|
34
|
+
- EVM `transaction preview / wallet confirmation` -> Simplicity `inspectCall()` / summary hash review
|
|
35
|
+
|
|
36
|
+
## What You Can Build With This SDK
|
|
37
|
+
|
|
38
|
+
With the current SDK you can build and test flows such as:
|
|
39
|
+
- single-sig contract spends,
|
|
40
|
+
- single-sig spends gated by block height,
|
|
41
|
+
- HTLC-style contracts,
|
|
42
|
+
- cooperative transfer with unilateral timeout recovery,
|
|
43
|
+
- relayer-backed fee-sponsored contract execution,
|
|
44
|
+
- custom `.simf` contract workflows driven from TypeScript or CLI.
|
|
45
|
+
|
|
46
|
+
You can also design more advanced systems such as ERC20-like token behavior, but the model is different from Ethereum. On Liquid/Simplicity, you usually represent state transitions as UTXO transitions instead of account storage updates. So the SDK can support that kind of application, but it does not mean you port Solidity account logic 1:1.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
You need three things:
|
|
51
|
+
1. the npm package,
|
|
52
|
+
2. a local Simplicity toolchain,
|
|
53
|
+
3. a reachable Elements / Liquid RPC endpoint.
|
|
54
|
+
|
|
55
|
+
### Package
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install @hazbase/simplicity
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Runtime assumptions
|
|
62
|
+
|
|
63
|
+
The current SDK assumes you have access to:
|
|
64
|
+
- `simc`
|
|
65
|
+
- `hal-simplicity`
|
|
66
|
+
- an Elements-compatible RPC endpoint
|
|
67
|
+
- a wallet-enabled RPC when you want to inspect or execute spends
|
|
68
|
+
|
|
69
|
+
### Node version
|
|
70
|
+
|
|
71
|
+
- Node.js `>= 20`
|
|
72
|
+
|
|
73
|
+
## Quickstart: First Working Contract
|
|
74
|
+
|
|
75
|
+
This section is the shortest path to a real success case. We will:
|
|
76
|
+
1. create a client,
|
|
77
|
+
2. compile the built-in `p2pkLockHeight` preset,
|
|
78
|
+
3. get the contract address,
|
|
79
|
+
4. fund it,
|
|
80
|
+
5. confirm the contract UTXO exists,
|
|
81
|
+
6. inspect the call,
|
|
82
|
+
7. execute it.
|
|
83
|
+
|
|
84
|
+
We use `p2pkLockHeight` first because it has a simple witness model and is the easiest way to understand how the SDK works end to end.
|
|
85
|
+
|
|
86
|
+
### Step 1: Create a client
|
|
87
|
+
|
|
88
|
+
This is the main entrypoint for the SDK.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { createSimplicityClient } from "@hazbase/simplicity";
|
|
92
|
+
|
|
93
|
+
const sdk = createSimplicityClient({
|
|
94
|
+
network: "liquidtestnet",
|
|
95
|
+
rpc: {
|
|
96
|
+
url: process.env.ELEMENTS_RPC_URL || "http://127.0.0.1:18884",
|
|
97
|
+
username: process.env.ELEMENTS_RPC_USER || "<rpc-user>",
|
|
98
|
+
password: process.env.ELEMENTS_RPC_PASSWORD || "<rpc-password>",
|
|
99
|
+
wallet: process.env.ELEMENTS_RPC_WALLET || "simplicity-test",
|
|
100
|
+
},
|
|
101
|
+
toolchain: {
|
|
102
|
+
simcPath: process.env.SIMC_PATH || "simc",
|
|
103
|
+
halSimplicityPath: process.env.HAL_SIMPLICITY_PATH || "hal-simplicity",
|
|
104
|
+
elementsCliPath: process.env.ELEMENTS_CLI_PATH || "eltc",
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If you are wondering whether a public RPC endpoint is enough: usually not for full execution flows. Public RPC endpoints often do not expose wallet methods such as `walletprocesspsbt`, so for real `inspect` / `execute` flows you should assume a trusted, authenticated RPC.
|
|
110
|
+
|
|
111
|
+
CLI equivalent for discovery starts here:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
simplicity-cli presets list
|
|
115
|
+
simplicity-cli presets show --preset p2pkLockHeight
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Step 2: Compile a preset
|
|
119
|
+
|
|
120
|
+
Now compile the built-in preset and save an artifact.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const compiled = await sdk.compileFromPreset({
|
|
124
|
+
preset: "p2pkLockHeight",
|
|
125
|
+
params: {
|
|
126
|
+
MIN_HEIGHT: 2344430,
|
|
127
|
+
SIGNER_XONLY: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
|
128
|
+
},
|
|
129
|
+
artifactPath: "./artifact.json",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
console.log(compiled.deployment());
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
What you get back:
|
|
136
|
+
- a `CompiledContract`,
|
|
137
|
+
- a deployable contract address,
|
|
138
|
+
- the CMR,
|
|
139
|
+
- the internal key,
|
|
140
|
+
- an artifact you can reload later.
|
|
141
|
+
|
|
142
|
+
CLI equivalent:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
simplicity-cli preset compile \
|
|
146
|
+
--preset p2pkLockHeight \
|
|
147
|
+
--param MIN_HEIGHT=2344430 \
|
|
148
|
+
--param SIGNER_XONLY=79be... \
|
|
149
|
+
--artifact ./artifact.json
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Step 3: Understand the deployment output
|
|
153
|
+
|
|
154
|
+
`compiled.deployment()` tells you where to send funds and what contract you just created.
|
|
155
|
+
|
|
156
|
+
The most important fields are:
|
|
157
|
+
- `contractAddress`: where you send L-BTC to make the contract live,
|
|
158
|
+
- `cmr`: the commitment merkle root for the compiled contract,
|
|
159
|
+
- `internalKey`: the internal taproot key used in the address derivation.
|
|
160
|
+
|
|
161
|
+
This is the point where Simplicity differs from EVM most clearly: **you are not broadcasting a separate deployment transaction here**. You are preparing a spend condition and then making it live by funding the resulting address.
|
|
162
|
+
|
|
163
|
+
### Step 4: Fund the contract
|
|
164
|
+
|
|
165
|
+
Send L-BTC to the `contractAddress` from your Liquid wallet.
|
|
166
|
+
|
|
167
|
+
Example with `eltc`:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
eltc -rpcwallet=simplicity-test sendtoaddress "<contract-address>" 0.00002
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
You can then wait for the UTXO from TypeScript:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const contract = compiled.at();
|
|
177
|
+
|
|
178
|
+
await contract.waitForFunding({
|
|
179
|
+
minAmountSat: 1000,
|
|
180
|
+
pollIntervalMs: 5000,
|
|
181
|
+
timeoutMs: 120000,
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Or inspect the artifact status from the CLI:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
simplicity-cli artifact show --artifact ./artifact.json
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
What `artifact show` tells you:
|
|
192
|
+
- whether the contract is unfunded,
|
|
193
|
+
- whether only unconfirmed UTXOs are visible,
|
|
194
|
+
- whether it is executable,
|
|
195
|
+
- which contract UTXOs are currently visible.
|
|
196
|
+
|
|
197
|
+
The state you usually want before calling `execute` is:
|
|
198
|
+
- `status: executable`
|
|
199
|
+
- `ready: yes`
|
|
200
|
+
|
|
201
|
+
### Step 5: Inspect before broadcast
|
|
202
|
+
|
|
203
|
+
Before broadcasting a contract spend, build it and inspect it.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const inspectResult = await contract.inspectCall({
|
|
207
|
+
wallet: "simplicity-test",
|
|
208
|
+
toAddress: "tex1...",
|
|
209
|
+
signer: {
|
|
210
|
+
type: "schnorrPrivkeyHex",
|
|
211
|
+
privkeyHex: process.env.SIMPLICITY_PRIMARY_PRIVKEY || "<primary-privkey-hex>",
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
console.log(inspectResult.summaryHash);
|
|
216
|
+
console.log(inspectResult.summary);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Why this matters:
|
|
220
|
+
- you see the candidate transaction before broadcasting,
|
|
221
|
+
- you can inspect inputs, outputs, and fee behavior,
|
|
222
|
+
- you can log or verify the `summaryHash` in higher-level applications.
|
|
223
|
+
|
|
224
|
+
CLI equivalent:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
simplicity-cli contract inspect \
|
|
228
|
+
--artifact ./artifact.json \
|
|
229
|
+
--wallet simplicity-test \
|
|
230
|
+
--privkey <primary-privkey-hex> \
|
|
231
|
+
--to-address tex1...
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
A practical rule: use `inspect` first, especially when you are still learning the contract or changing witness logic.
|
|
235
|
+
|
|
236
|
+
### Step 6: Execute
|
|
237
|
+
|
|
238
|
+
Once you are satisfied with the preview, execute the spend.
|
|
239
|
+
|
|
240
|
+
A safe first step is to build the final raw transaction without broadcasting:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
const executeResult = await contract.execute({
|
|
244
|
+
wallet: "simplicity-test",
|
|
245
|
+
toAddress: "tex1...",
|
|
246
|
+
signer: {
|
|
247
|
+
type: "schnorrPrivkeyHex",
|
|
248
|
+
privkeyHex: process.env.SIMPLICITY_PRIMARY_PRIVKEY || "<primary-privkey-hex>",
|
|
249
|
+
},
|
|
250
|
+
broadcast: false,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
console.log(executeResult.rawTxHex);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Then switch to broadcast mode when you are ready:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
const broadcastResult = await contract.execute({
|
|
260
|
+
wallet: "simplicity-test",
|
|
261
|
+
toAddress: "tex1...",
|
|
262
|
+
signer: {
|
|
263
|
+
type: "schnorrPrivkeyHex",
|
|
264
|
+
privkeyHex: process.env.SIMPLICITY_PRIMARY_PRIVKEY || "<primary-privkey-hex>",
|
|
265
|
+
},
|
|
266
|
+
broadcast: true,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
console.log(broadcastResult.txId);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
CLI equivalent:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
simplicity-cli contract execute \
|
|
276
|
+
--artifact ./artifact.json \
|
|
277
|
+
--wallet simplicity-test \
|
|
278
|
+
--privkey <primary-privkey-hex> \
|
|
279
|
+
--to-address tex1... \
|
|
280
|
+
--broadcast
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Recommended habit:
|
|
284
|
+
1. compile
|
|
285
|
+
2. fund
|
|
286
|
+
3. artifact check
|
|
287
|
+
4. inspect
|
|
288
|
+
5. execute with `broadcast: false`
|
|
289
|
+
6. execute with `broadcast: true`
|
|
290
|
+
|
|
291
|
+
## Step-by-Step Walkthrough
|
|
292
|
+
|
|
293
|
+
This section explains the same flow in terms of the SDK types and responsibilities.
|
|
294
|
+
|
|
295
|
+
### Create a Client
|
|
296
|
+
|
|
297
|
+
Use `createSimplicityClient(config)` to define three pieces of infrastructure:
|
|
298
|
+
- which Liquid network you are targeting,
|
|
299
|
+
- which RPC endpoint and wallet you will use,
|
|
300
|
+
- where the local toolchain binaries live.
|
|
301
|
+
|
|
302
|
+
This client is the root object for both JS/TS flows and relayer integrations.
|
|
303
|
+
|
|
304
|
+
### Compile a Built-in Preset
|
|
305
|
+
|
|
306
|
+
Use `sdk.compileFromPreset(...)` when you want the fastest route to a working contract.
|
|
307
|
+
|
|
308
|
+
Use it when:
|
|
309
|
+
- you are learning Simplicity with the SDK,
|
|
310
|
+
- your contract matches a built-in pattern,
|
|
311
|
+
- you want a known witness schema and a stable example path.
|
|
312
|
+
|
|
313
|
+
The return value is a `CompiledContract`, which gives you:
|
|
314
|
+
- `deployment()`
|
|
315
|
+
- `saveArtifact(path)`
|
|
316
|
+
- `at()` to turn it into a deployed contract handle.
|
|
317
|
+
|
|
318
|
+
### Understand Deployment
|
|
319
|
+
|
|
320
|
+
`deployment()` gives you the metadata you need to make the contract live:
|
|
321
|
+
- `contractAddress`
|
|
322
|
+
- `cmr`
|
|
323
|
+
- `internalKey`
|
|
324
|
+
- `instructions`
|
|
325
|
+
|
|
326
|
+
This is the point where the README should change your mental model: **compiling does not put anything on chain yet**. Funding the derived address is what makes the contract usable.
|
|
327
|
+
|
|
328
|
+
### Fund the Contract
|
|
329
|
+
|
|
330
|
+
A compiled contract becomes executable only after a UTXO exists at its address.
|
|
331
|
+
|
|
332
|
+
Helpful SDK / CLI tools here:
|
|
333
|
+
- `contract.waitForFunding(...)`
|
|
334
|
+
- `contract.findUtxos()`
|
|
335
|
+
- `simplicity-cli artifact show --artifact ...`
|
|
336
|
+
|
|
337
|
+
Use `artifact show` when you want a human-readable view of:
|
|
338
|
+
- address,
|
|
339
|
+
- compile source,
|
|
340
|
+
- linked preset,
|
|
341
|
+
- live UTXO status.
|
|
342
|
+
|
|
343
|
+
### Inspect Before Broadcast
|
|
344
|
+
|
|
345
|
+
`inspectCall()` is the safe preview path.
|
|
346
|
+
|
|
347
|
+
It answers:
|
|
348
|
+
- which UTXO is being spent,
|
|
349
|
+
- where the outputs go,
|
|
350
|
+
- what fee output exists,
|
|
351
|
+
- what summary hash represents the proposed spend.
|
|
352
|
+
|
|
353
|
+
This is especially important if you intend to build signing UX or higher-level approval logic later.
|
|
354
|
+
|
|
355
|
+
### Execute
|
|
356
|
+
|
|
357
|
+
`execute()` is the direct contract spend path.
|
|
358
|
+
|
|
359
|
+
Use:
|
|
360
|
+
- `broadcast: false` when you want to generate and inspect the final raw transaction,
|
|
361
|
+
- `broadcast: true` when you actually want to submit the spend.
|
|
362
|
+
|
|
363
|
+
For beginners, the safest practice is:
|
|
364
|
+
- inspect first,
|
|
365
|
+
- dry-run execute second,
|
|
366
|
+
- broadcast last.
|
|
367
|
+
|
|
368
|
+
## Common Workflow Patterns
|
|
369
|
+
|
|
370
|
+
This section helps you choose the right path for real work.
|
|
371
|
+
|
|
372
|
+
### Pattern A: Start from a preset
|
|
373
|
+
|
|
374
|
+
Best for:
|
|
375
|
+
- first experiments,
|
|
376
|
+
- demos,
|
|
377
|
+
- validating a toolchain setup,
|
|
378
|
+
- learning witness behavior.
|
|
379
|
+
|
|
380
|
+
Use:
|
|
381
|
+
- `p2pkLockHeight` first,
|
|
382
|
+
- then `p2pk` if you do not need a timelock.
|
|
383
|
+
|
|
384
|
+
### Pattern B: Move to custom `.simf`
|
|
385
|
+
|
|
386
|
+
Best for:
|
|
387
|
+
- your own contract logic,
|
|
388
|
+
- app-specific spend rules,
|
|
389
|
+
- moving from prototype to product-specific behavior.
|
|
390
|
+
|
|
391
|
+
Use:
|
|
392
|
+
- `sdk.compileFromFile(...)`
|
|
393
|
+
- `templateVars`
|
|
394
|
+
- `artifactPath`
|
|
395
|
+
|
|
396
|
+
A common progression is:
|
|
397
|
+
1. start from a preset,
|
|
398
|
+
2. inspect the preset's witness model,
|
|
399
|
+
3. write your own `.simf`,
|
|
400
|
+
4. keep the same artifact / inspect / execute lifecycle.
|
|
401
|
+
|
|
402
|
+
### Pattern C: Multi-witness contracts
|
|
403
|
+
|
|
404
|
+
Best for:
|
|
405
|
+
- HTLC-style logic,
|
|
406
|
+
- cooperative spends,
|
|
407
|
+
- timeout recovery,
|
|
408
|
+
- flows where multiple branches of witness data are possible.
|
|
409
|
+
|
|
410
|
+
Relevant SDK features:
|
|
411
|
+
- `witness.values`
|
|
412
|
+
- `witness.signers`
|
|
413
|
+
- witness schema validation
|
|
414
|
+
|
|
415
|
+
Relevant presets:
|
|
416
|
+
- `htlc`
|
|
417
|
+
- `transferWithTimeout`
|
|
418
|
+
|
|
419
|
+
### Pattern D: Gasless execution
|
|
420
|
+
|
|
421
|
+
Best for:
|
|
422
|
+
- developer experience where the caller should not manage fees directly,
|
|
423
|
+
- fee-sponsored app flows,
|
|
424
|
+
- relayer-backed applications.
|
|
425
|
+
|
|
426
|
+
There are three different gasless-style paths in this SDK, and they are not interchangeable:
|
|
427
|
+
- standard L-BTC transfer through a relayer,
|
|
428
|
+
- local sponsor wallet mode for Simplicity contract execution,
|
|
429
|
+
- relayer-backed Simplicity execution.
|
|
430
|
+
|
|
431
|
+
## Presets Overview
|
|
432
|
+
|
|
433
|
+
These presets are built into the SDK and are the best place to start.
|
|
434
|
+
|
|
435
|
+
| Preset | What it does | When to use it | Custom witness? | Relayer execute? | Best first use |
|
|
436
|
+
| --- | --- | --- | --- | --- | --- |
|
|
437
|
+
| `p2pkLockHeight` | Single signer spend gated by block height | First end-to-end tutorial, timelocked tests | No | Yes | Yes |
|
|
438
|
+
| `p2pk` | Basic single key spend | Minimal happy path | No | Yes | Yes |
|
|
439
|
+
| `htlc` | Hash/time based branch contract | Preimage or timeout experiments | Yes | Yes | After presets without custom witness |
|
|
440
|
+
| `transferWithTimeout` | Cooperative transfer with unilateral timeout fallback | Multi-witness and branch logic | Yes | Yes | After HTLC basics |
|
|
441
|
+
|
|
442
|
+
Use the CLI to inspect presets interactively:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
simplicity-cli presets list
|
|
446
|
+
simplicity-cli presets show --preset transferWithTimeout
|
|
447
|
+
simplicity-cli presets scaffold --preset transferWithTimeout --write-dir ./transfer-timeout-scaffold
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Custom `.simf` Contracts
|
|
451
|
+
|
|
452
|
+
When built-in presets are no longer enough, move to your own `.simf` file.
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
const compiled = await sdk.compileFromFile({
|
|
456
|
+
simfPath: "./contracts/my-contract.simf",
|
|
457
|
+
templateVars: {
|
|
458
|
+
ADMIN_XONLY: "79be...",
|
|
459
|
+
MIN_HEIGHT: 2344430,
|
|
460
|
+
},
|
|
461
|
+
artifactPath: "./artifacts/my-contract.artifact.json",
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Use custom `.simf` when:
|
|
466
|
+
- your business logic is not represented by a preset,
|
|
467
|
+
- you need your own parameterization,
|
|
468
|
+
- you want to build app-specific wrappers on top of the generic SDK.
|
|
469
|
+
|
|
470
|
+
Recommended path:
|
|
471
|
+
- learn the lifecycle with a preset first,
|
|
472
|
+
- then move to `compileFromFile(...)` once the model is clear.
|
|
473
|
+
|
|
474
|
+
## Witnesses Explained
|
|
475
|
+
|
|
476
|
+
A **witness** is the runtime data needed to satisfy a Simplicity contract spend.
|
|
477
|
+
|
|
478
|
+
### Auto-generated witness
|
|
479
|
+
|
|
480
|
+
For the simplest presets, you usually do not need to build witness data manually.
|
|
481
|
+
|
|
482
|
+
Examples:
|
|
483
|
+
- `p2pkLockHeight`
|
|
484
|
+
- `p2pk`
|
|
485
|
+
|
|
486
|
+
These rely on the default signature path and use the primary signer you pass to `inspectCall()` or `execute()`.
|
|
487
|
+
|
|
488
|
+
### `witness.values`
|
|
489
|
+
|
|
490
|
+
Use `witness.values` when the contract needs structured runtime data in addition to the primary signer.
|
|
491
|
+
|
|
492
|
+
Example:
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
witness: {
|
|
496
|
+
values: {
|
|
497
|
+
COMPLETE_OR_CANCEL: {
|
|
498
|
+
type: "Either<(u256, Signature), Signature>",
|
|
499
|
+
value: "Left((0x0000000000000000000000000000000000000000000000000000000000000000, ${SIGNATURE}))",
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### `${SIGNATURE}`
|
|
506
|
+
|
|
507
|
+
`${SIGNATURE}` is replaced by the SDK with the actual Simplicity signature for the current contract input.
|
|
508
|
+
|
|
509
|
+
This means you can express witness templates declaratively while letting the SDK calculate the actual signature material.
|
|
510
|
+
|
|
511
|
+
### `${SIGNATURE:NAME}` and `witness.signers`
|
|
512
|
+
|
|
513
|
+
Use named signer placeholders when a contract requires more than one signature source.
|
|
514
|
+
|
|
515
|
+
Example:
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
witness: {
|
|
519
|
+
signers: {
|
|
520
|
+
RECIPIENT: {
|
|
521
|
+
type: "schnorrPrivkeyHex",
|
|
522
|
+
privkeyHex: "<recipient-privkey-hex>",
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
values: {
|
|
526
|
+
SENDER_SIG: {
|
|
527
|
+
type: "Signature",
|
|
528
|
+
value: "${SIGNATURE}",
|
|
529
|
+
},
|
|
530
|
+
TRANSFER_OR_TIMEOUT: {
|
|
531
|
+
type: "Option<Signature>",
|
|
532
|
+
value: "Some(${SIGNATURE:RECIPIENT})",
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Validation rules
|
|
539
|
+
|
|
540
|
+
The SDK validates preset witness usage before calling `simc`.
|
|
541
|
+
|
|
542
|
+
That means it can reject mistakes such as:
|
|
543
|
+
- missing required witness fields,
|
|
544
|
+
- mismatched witness type strings,
|
|
545
|
+
- a named signature placeholder without a matching signer entry.
|
|
546
|
+
|
|
547
|
+
### Which presets need custom witness?
|
|
548
|
+
|
|
549
|
+
- `p2pkLockHeight`: no
|
|
550
|
+
- `p2pk`: no
|
|
551
|
+
- `htlc`: yes
|
|
552
|
+
- `transferWithTimeout`: yes
|
|
553
|
+
|
|
554
|
+
## Gasless Modes Explained
|
|
555
|
+
|
|
556
|
+
Gasless support exists in three forms, and it is important to understand the difference.
|
|
557
|
+
|
|
558
|
+
### 1. Standard gasless transfer
|
|
559
|
+
|
|
560
|
+
Use this when you want a relayer to sponsor a normal L-BTC payment flow.
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
const result = await sdk.payments.gaslessTransfer({
|
|
564
|
+
relayer: sdk.relayer({
|
|
565
|
+
baseUrl: process.env.SIMPLICITY_RELAYER_URL || "http://127.0.0.1:3000",
|
|
566
|
+
apiKey: process.env.SIMPLICITY_RELAYER_API_KEY || "<relayer-api-key>",
|
|
567
|
+
}),
|
|
568
|
+
amount: 0.0001,
|
|
569
|
+
toAddress: "tex1...",
|
|
570
|
+
fromLabel: "user-1",
|
|
571
|
+
userWallet: "userwallet",
|
|
572
|
+
});
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Use it when:
|
|
576
|
+
- you want fee sponsorship,
|
|
577
|
+
- you are sending L-BTC,
|
|
578
|
+
- you are not executing a Simplicity contract input.
|
|
579
|
+
|
|
580
|
+
### 2. Local sponsor wallet mode
|
|
581
|
+
|
|
582
|
+
Use this when the contract spend is local, but another wallet on the same system should pay the fee.
|
|
583
|
+
|
|
584
|
+
```ts
|
|
585
|
+
const result = await compiled.at().executeGasless({
|
|
586
|
+
wallet: "simplicity-test",
|
|
587
|
+
sponsorWallet: "sponsorwallet",
|
|
588
|
+
toAddress: "tex1...",
|
|
589
|
+
signer: {
|
|
590
|
+
type: "schnorrPrivkeyHex",
|
|
591
|
+
privkeyHex: process.env.SIMPLICITY_PRIMARY_PRIVKEY || "<primary-privkey-hex>",
|
|
592
|
+
},
|
|
593
|
+
broadcast: true,
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Use it when:
|
|
598
|
+
- you control both wallets,
|
|
599
|
+
- you do not need a separate external relayer service,
|
|
600
|
+
- you want a local fee-sponsored contract execution path.
|
|
601
|
+
|
|
602
|
+
### 3. Relayer-backed Simplicity execution
|
|
603
|
+
|
|
604
|
+
Use this when a separate relayer service should sponsor and submit the Simplicity execution.
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
const relayer = sdk.relayer({
|
|
608
|
+
baseUrl: process.env.SIMPLICITY_RELAYER_URL || "http://127.0.0.1:3000",
|
|
609
|
+
apiKey: process.env.SIMPLICITY_RELAYER_API_KEY || "<relayer-api-key>",
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
const result = await compiled.at().executeGasless({
|
|
613
|
+
relayer,
|
|
614
|
+
fromLabel: "demo-user",
|
|
615
|
+
wallet: "simplicity-test",
|
|
616
|
+
toAddress: "tex1...",
|
|
617
|
+
signer: {
|
|
618
|
+
type: "schnorrPrivkeyHex",
|
|
619
|
+
privkeyHex: process.env.SIMPLICITY_PRIMARY_PRIVKEY || "<primary-privkey-hex>",
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Use it when:
|
|
625
|
+
- your app has a relayer backend,
|
|
626
|
+
- users should not manage fees directly,
|
|
627
|
+
- you want contract execution plus fee sponsorship.
|
|
628
|
+
|
|
629
|
+
## CLI Guide
|
|
630
|
+
|
|
631
|
+
If you prefer the CLI, the same lifecycle is available there.
|
|
632
|
+
|
|
633
|
+
### Discover
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
simplicity-cli presets list
|
|
637
|
+
simplicity-cli presets show --preset p2pkLockHeight
|
|
638
|
+
simplicity-cli presets show --preset htlc
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Scaffold
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
simplicity-cli presets scaffold --preset transferWithTimeout
|
|
645
|
+
simplicity-cli presets scaffold --preset transferWithTimeout --write-dir ./transfer-timeout-scaffold
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
Use scaffold when you want a starting bundle with:
|
|
649
|
+
- params JSON,
|
|
650
|
+
- witness JSON,
|
|
651
|
+
- compile / execute command examples,
|
|
652
|
+
- `.env.example`,
|
|
653
|
+
- a small TypeScript example.
|
|
654
|
+
|
|
655
|
+
### Compile and deploy
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
simplicity-cli preset compile \
|
|
659
|
+
--preset p2pkLockHeight \
|
|
660
|
+
--param MIN_HEIGHT=2344430 \
|
|
661
|
+
--param SIGNER_XONLY=79be... \
|
|
662
|
+
--artifact ./artifact.json
|
|
663
|
+
|
|
664
|
+
simplicity-cli artifact show --artifact ./artifact.json
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Execute
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
simplicity-cli contract inspect \
|
|
671
|
+
--artifact ./artifact.json \
|
|
672
|
+
--wallet simplicity-test \
|
|
673
|
+
--privkey <primary-privkey-hex> \
|
|
674
|
+
--to-address tex1...
|
|
675
|
+
|
|
676
|
+
simplicity-cli contract execute \
|
|
677
|
+
--artifact ./artifact.json \
|
|
678
|
+
--wallet simplicity-test \
|
|
679
|
+
--privkey <primary-privkey-hex> \
|
|
680
|
+
--to-address tex1... \
|
|
681
|
+
--broadcast
|
|
682
|
+
|
|
683
|
+
simplicity-cli contract execute-gasless \
|
|
684
|
+
--artifact ./artifact.json \
|
|
685
|
+
--wallet simplicity-test \
|
|
686
|
+
--relayer http://127.0.0.1:3000 \
|
|
687
|
+
--api-key <relayer-api-key> \
|
|
688
|
+
--from-label demo-user \
|
|
689
|
+
--privkey <primary-privkey-hex> \
|
|
690
|
+
--to-address tex1...
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Gasless transfer
|
|
694
|
+
|
|
695
|
+
```bash
|
|
696
|
+
simplicity-cli gasless request \
|
|
697
|
+
--relayer http://127.0.0.1:3000 \
|
|
698
|
+
--api-key <relayer-api-key> \
|
|
699
|
+
--from-label user-1 \
|
|
700
|
+
--to-address tex1... \
|
|
701
|
+
--amount 0.0001
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
## Examples Map
|
|
705
|
+
|
|
706
|
+
These examples are included to help you jump to the right workflow quickly.
|
|
707
|
+
|
|
708
|
+
- [compile-custom.ts](./examples/compile-custom.ts): compile a custom `.simf` file.
|
|
709
|
+
- [compile-preset.ts](./examples/compile-preset.ts): compile a built-in preset.
|
|
710
|
+
- [inspect-contract.ts](./examples/inspect-contract.ts): inspect a contract spend before broadcast.
|
|
711
|
+
- [execute-contract.ts](./examples/execute-contract.ts): execute a contract directly.
|
|
712
|
+
- [execute-contract-gasless.ts](./examples/execute-contract-gasless.ts): execute with a local sponsor wallet paying fees.
|
|
713
|
+
- [execute-contract-gasless-relayer.ts](./examples/execute-contract-gasless-relayer.ts): execute through a relayer-backed gasless flow.
|
|
714
|
+
- [execute-htlc.ts](./examples/execute-htlc.ts): HTLC preset with custom witness values.
|
|
715
|
+
- [execute-transfer-with-timeout-cooperative.ts](./examples/execute-transfer-with-timeout-cooperative.ts): cooperative multi-witness timeout flow.
|
|
716
|
+
- [gasless-transfer.ts](./examples/gasless-transfer.ts): standard relayer-backed gasless L-BTC transfer.
|
|
717
|
+
|
|
718
|
+
## FAQ / Practical Notes
|
|
719
|
+
|
|
720
|
+
### Can I use a public RPC endpoint?
|
|
721
|
+
|
|
722
|
+
Sometimes for read-only or light inspection flows, but usually not for full execution. Many public endpoints do not expose wallet RPC methods, and this SDK relies on wallet-aware flows for inspect / execute in practical setups.
|
|
723
|
+
|
|
724
|
+
### What does “deploy” mean here?
|
|
725
|
+
|
|
726
|
+
It means: compile the contract, derive its address, then fund that address with a UTXO. There is no separate EVM-style bytecode deployment transaction.
|
|
727
|
+
|
|
728
|
+
### What is an artifact?
|
|
729
|
+
|
|
730
|
+
An artifact is the contract's compile output plus the metadata needed to reload, inspect, and execute it later. Think of it as the bridge between compilation time and on-chain execution time.
|
|
731
|
+
|
|
732
|
+
### When should I use a preset instead of a custom `.simf` file?
|
|
733
|
+
|
|
734
|
+
Use a preset first when you are learning the lifecycle or your use case already matches a built-in contract. Move to custom `.simf` when your business rules are app-specific.
|
|
735
|
+
|
|
736
|
+
### Does gasless mean the contract itself is free?
|
|
737
|
+
|
|
738
|
+
No. It means someone else pays the transaction fee. The transaction still has a fee; the caller just does not provide it directly.
|
|
739
|
+
|
|
740
|
+
### Can I build an ERC20-like token with this SDK?
|
|
741
|
+
|
|
742
|
+
Yes, but not by copying the EVM account model directly. On Liquid/Simplicity, you usually model token logic as UTXO state transitions rather than storage mappings. The SDK can support that workflow, but the contract design is different from Solidity.
|
|
743
|
+
|
|
744
|
+
## Practical Limitations
|
|
745
|
+
|
|
746
|
+
Be aware of these current constraints:
|
|
747
|
+
- SimplicityHL is still upstream work-in-progress.
|
|
748
|
+
- The SDK currently prioritizes explicit / unblinded paths.
|
|
749
|
+
- Public RPC endpoints are usually not enough for full wallet-based execution.
|
|
750
|
+
- Gasless support exists in multiple modes and should be chosen deliberately.
|
|
751
|
+
- This is not a browser SDK.
|
|
752
|
+
- Full confidential / blinded support is not the current success path.
|
|
753
|
+
|
|
754
|
+
## E2E Note
|
|
755
|
+
|
|
756
|
+
The repository also includes an E2E script for relayer-backed Simplicity execution:
|
|
757
|
+
|
|
758
|
+
```bash
|
|
759
|
+
PATH="/tmp:$PATH" npm run e2e:simplicity-relayer
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
Helpful env vars include:
|
|
763
|
+
- `SIMPLICITY_ARTIFACT`
|
|
764
|
+
- `SIMPLICITY_RELAYER_PORT`
|
|
765
|
+
- `SIMPLICITY_RELAYER_API_KEY`
|
|
766
|
+
- `SIMPLICITY_RELAYER_DIR`
|
|
767
|
+
- `SIMPLICITY_FROM_LABEL`
|
|
768
|
+
- `SIMPLICITY_PRIVKEY`
|
|
769
|
+
- `ELEMENTS_RPC_URL`
|
|
770
|
+
- `ELEMENTS_RPC_USER`
|
|
771
|
+
- `ELEMENTS_RPC_PASSWORD`
|
|
772
|
+
- `ELEMENTS_RPC_WALLET`
|
|
773
|
+
|
|
774
|
+
You do not need this script to understand the SDK, but it is useful once you want to validate relayer-backed flows end to end.
|
|
775
|
+
|
|
776
|
+
## License
|
|
777
|
+
|
|
778
|
+
Apache-2.0
|