soltag 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -2
- package/README.md +115 -69
- package/dist/chunk-YTNI7QSL.js +351 -0
- package/dist/chunk-YTNI7QSL.js.map +1 -0
- package/dist/esbuild.js +2 -2
- package/dist/esbuild.js.map +1 -1
- package/dist/index.d.ts +47 -26
- package/dist/index.js +73 -89
- package/dist/index.js.map +1 -1
- package/dist/plugin.cjs +316 -260
- package/dist/plugin.cjs.map +1 -1
- package/dist/rollup.js +2 -2
- package/dist/rollup.js.map +1 -1
- package/dist/unplugin.d.ts +22 -1
- package/dist/unplugin.js +1 -1
- package/dist/vite.js +2 -2
- package/dist/vite.js.map +1 -1
- package/dist/webpack.js +2 -2
- package/dist/webpack.js.map +1 -1
- package/package.json +8 -7
- package/plugin/package.json +1 -0
- package/dist/chunk-LNEBK5MP.js +0 -192
- package/dist/chunk-LNEBK5MP.js.map +0 -1
- package/plugin.js +0 -4
package/LICENSE
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 morpho-org
|
|
4
|
-
|
|
5
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
4
|
of this software and associated documentation files (the "Software"), to deal
|
|
7
5
|
in the Software without restriction, including without limitation the rights
|
package/README.md
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
# soltag
|
|
2
2
|
|
|
3
|
-
Inline Solidity in TypeScript. Write Solidity inside a tagged template literal, get
|
|
3
|
+
Inline Solidity in TypeScript. Write Solidity inside a tagged template literal, get a data object with typed `abi`, `bytecode()`, `deployedBytecode`, and a deterministic `address`.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import { sol } from 'soltag';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const lens = sol("Lens")`
|
|
9
9
|
pragma solidity ^0.8.24;
|
|
10
10
|
interface IERC20 { function balanceOf(address) external view returns (uint256); }
|
|
11
|
-
|
|
12
|
-
function
|
|
13
|
-
external view returns (uint256
|
|
11
|
+
contract Lens {
|
|
12
|
+
function getBalance(address token, address user)
|
|
13
|
+
external view returns (uint256)
|
|
14
14
|
{
|
|
15
|
-
|
|
16
|
-
for (uint256 i = 0; i < tokens.length; i++) {
|
|
17
|
-
out[i] = IERC20(tokens[i]).balanceOf(user);
|
|
18
|
-
}
|
|
15
|
+
return IERC20(token).balanceOf(user);
|
|
19
16
|
}
|
|
20
17
|
}
|
|
21
18
|
`;
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
//
|
|
20
|
+
lens.name; // "Lens" (typed as literal)
|
|
21
|
+
lens.abi; // precise, as-const ABI (via generated .d.ts)
|
|
22
|
+
lens.address; // `0x${string}` (deterministic, derived from deployedBytecode)
|
|
23
|
+
lens.deployedBytecode; // `0x${string}` (runtime bytecode)
|
|
24
|
+
lens.bytecode(); // `0x${string}` (creation bytecode)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Features
|
|
28
28
|
|
|
29
|
-
- **`sol` tagged template** — write Solidity inline, get a `
|
|
30
|
-
- **Real-time IDE support** —
|
|
29
|
+
- **`sol("Name")` tagged template** — write Solidity inline, get a `InlineContract` with typed ABI, bytecode, and a deterministic address. Supports string interpolation for composing reusable fragments
|
|
30
|
+
- **Real-time IDE support** — inline Solidity diagnostics and contract-name validation via a TypeScript Language Service Plugin. ABI and `bytecode()` types are provided through generated `.d.ts` augmentation
|
|
31
31
|
- **Build-time compilation** — bundler plugin (Vite, Rollup, esbuild, webpack) compiles Solidity at build time so `solc` (8MB WASM) never ships to production
|
|
32
|
-
- **
|
|
32
|
+
- **Data-oriented** — `InlineContract` is a plain data container. Use `abi` and `bytecode` with whatever execution library you prefer (viem, ethers, etc.)
|
|
33
33
|
|
|
34
34
|
## Install
|
|
35
35
|
|
|
@@ -55,14 +55,20 @@ pnpm add -D solc unplugin magic-string
|
|
|
55
55
|
|
|
56
56
|
### Bundler Plugin (recommended for apps)
|
|
57
57
|
|
|
58
|
-
Compiles `sol` templates at build time — `solc` is only needed during the build, not at runtime.
|
|
58
|
+
Compiles `sol("Name")` templates at build time — `solc` is only needed during the build, not at runtime.
|
|
59
59
|
|
|
60
60
|
```ts
|
|
61
61
|
// vite.config.ts
|
|
62
62
|
import soltag from 'soltag/vite';
|
|
63
63
|
|
|
64
64
|
export default defineConfig({
|
|
65
|
-
plugins: [
|
|
65
|
+
plugins: [
|
|
66
|
+
soltag({
|
|
67
|
+
solc: {
|
|
68
|
+
optimizer: { enabled: true, runs: 200 }
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
],
|
|
66
72
|
});
|
|
67
73
|
```
|
|
68
74
|
|
|
@@ -76,7 +82,7 @@ import soltag from 'soltag/webpack';
|
|
|
76
82
|
|
|
77
83
|
### TypeScript Plugin (IDE support)
|
|
78
84
|
|
|
79
|
-
Add to your `tsconfig.json` for
|
|
85
|
+
Add to your `tsconfig.json` for inline Solidity diagnostics and contract-name validation:
|
|
80
86
|
|
|
81
87
|
```json
|
|
82
88
|
{
|
|
@@ -94,12 +100,8 @@ Add to your `tsconfig.json` for autocomplete, hover types, and inline Solidity d
|
|
|
94
100
|
|
|
95
101
|
```ts
|
|
96
102
|
import { sol } from 'soltag';
|
|
97
|
-
import { createPublicClient, http } from 'viem';
|
|
98
|
-
import { mainnet } from 'viem/chains';
|
|
99
|
-
|
|
100
|
-
const client = createPublicClient({ chain: mainnet, transport: http() });
|
|
101
103
|
|
|
102
|
-
const
|
|
104
|
+
const math = sol("Math")`
|
|
103
105
|
pragma solidity ^0.8.24;
|
|
104
106
|
contract Math {
|
|
105
107
|
function add(uint256 a, uint256 b) external pure returns (uint256) {
|
|
@@ -108,30 +110,68 @@ const contract = sol`
|
|
|
108
110
|
}
|
|
109
111
|
`;
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
+
math.name; // "Math"
|
|
114
|
+
math.abi; // readonly [{ type: "function", name: "add", ... }]
|
|
115
|
+
math.bytecode(); // creation bytecode
|
|
116
|
+
math.deployedBytecode; // runtime bytecode
|
|
117
|
+
math.address; // deterministic address derived from deployedBytecode
|
|
113
118
|
```
|
|
114
119
|
|
|
115
|
-
###
|
|
120
|
+
### Constructor arguments
|
|
121
|
+
|
|
122
|
+
For contracts with constructors, `bytecode()` accepts typed arguments:
|
|
116
123
|
|
|
117
124
|
```ts
|
|
118
|
-
const
|
|
125
|
+
const token = sol("MyToken")`
|
|
119
126
|
pragma solidity ^0.8.24;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
contract MyToken {
|
|
128
|
+
uint256 public supply;
|
|
129
|
+
constructor(uint256 _supply) {
|
|
130
|
+
supply = _supply;
|
|
131
|
+
}
|
|
123
132
|
}
|
|
124
|
-
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
token.bytecode(1000n); // creation bytecode + ABI-encoded constructor args
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Using with viem
|
|
139
|
+
|
|
140
|
+
`InlineContract` is a data container — use its properties with any execution library. Here's an example with viem's `eth_call` + `stateOverride` for deployless reads:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { createPublicClient, http, decodeFunctionResult, encodeFunctionData } from 'viem';
|
|
144
|
+
import { mainnet } from 'viem/chains';
|
|
145
|
+
import { sol } from 'soltag';
|
|
146
|
+
|
|
147
|
+
const client = createPublicClient({ chain: mainnet, transport: http() });
|
|
148
|
+
|
|
149
|
+
const lens = sol("Lens")`
|
|
150
|
+
pragma solidity ^0.8.24;
|
|
151
|
+
interface IERC20 { function balanceOf(address) external view returns (uint256); }
|
|
152
|
+
contract Lens {
|
|
125
153
|
function getBalance(address token, address user)
|
|
126
|
-
external view returns (uint256
|
|
154
|
+
external view returns (uint256)
|
|
127
155
|
{
|
|
128
|
-
|
|
129
|
-
decimals = IERC20(token).decimals();
|
|
156
|
+
return IERC20(token).balanceOf(user);
|
|
130
157
|
}
|
|
131
158
|
}
|
|
132
159
|
`;
|
|
133
160
|
|
|
134
|
-
const
|
|
161
|
+
const data = encodeFunctionData({
|
|
162
|
+
abi: lens.abi,
|
|
163
|
+
functionName: 'getBalance',
|
|
164
|
+
args: [USDC, user],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const result = await client.call({
|
|
168
|
+
to: lens.address,
|
|
169
|
+
data,
|
|
170
|
+
stateOverrides: [{
|
|
171
|
+
address: lens.address,
|
|
172
|
+
code: lens.deployedBytecode,
|
|
173
|
+
}],
|
|
174
|
+
});
|
|
135
175
|
```
|
|
136
176
|
|
|
137
177
|
### Composing with fragments
|
|
@@ -146,7 +186,7 @@ const IERC20 = `
|
|
|
146
186
|
}
|
|
147
187
|
`;
|
|
148
188
|
|
|
149
|
-
const balanceLens = sol`
|
|
189
|
+
const balanceLens = sol("BalanceLens")`
|
|
150
190
|
pragma solidity ^0.8.24;
|
|
151
191
|
${IERC20}
|
|
152
192
|
contract BalanceLens {
|
|
@@ -158,18 +198,6 @@ const balanceLens = sol`
|
|
|
158
198
|
}
|
|
159
199
|
}
|
|
160
200
|
`;
|
|
161
|
-
|
|
162
|
-
const allowanceLens = sol`
|
|
163
|
-
pragma solidity ^0.8.24;
|
|
164
|
-
${IERC20}
|
|
165
|
-
contract AllowanceLens {
|
|
166
|
-
function getAllowance(address token, address owner, address spender)
|
|
167
|
-
external view returns (uint256)
|
|
168
|
-
{
|
|
169
|
-
return IERC20(token).allowance(owner, spender);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
`;
|
|
173
201
|
```
|
|
174
202
|
|
|
175
203
|
The bundler plugin resolves `const` string interpolations at build time, so these templates are still compiled ahead of time. Interpolations that can't be statically resolved (e.g. variables from function calls) are left for runtime compilation.
|
|
@@ -179,13 +207,13 @@ The bundler plugin resolves `const` string interpolations at build time, so thes
|
|
|
179
207
|
For environments where you want full control over the compilation step:
|
|
180
208
|
|
|
181
209
|
```ts
|
|
182
|
-
import {
|
|
210
|
+
import { InlineContract } from 'soltag';
|
|
183
211
|
|
|
184
|
-
const contract =
|
|
212
|
+
const contract = InlineContract.fromArtifacts("MyContract", {
|
|
185
213
|
MyContract: {
|
|
186
214
|
abi: [/* ... */],
|
|
187
215
|
deployedBytecode: '0x...',
|
|
188
|
-
|
|
216
|
+
bytecode: '0x...',
|
|
189
217
|
},
|
|
190
218
|
});
|
|
191
219
|
```
|
|
@@ -194,48 +222,66 @@ const contract = SolContract.fromArtifacts({
|
|
|
194
222
|
|
|
195
223
|
### Runtime path (scripts, testing)
|
|
196
224
|
|
|
197
|
-
1. `sol`
|
|
198
|
-
2. On first
|
|
199
|
-
3.
|
|
200
|
-
4.
|
|
201
|
-
5. Decodes the return value via viem's ABI decoder
|
|
225
|
+
1. `sol("Name")` captures the Solidity source string and contract name
|
|
226
|
+
2. On first property access, compiles with `solc-js` (lazy-loaded) and caches the result
|
|
227
|
+
3. Exposes `abi`, `bytecode()`, and `deployedBytecode` for the named contract
|
|
228
|
+
4. Derives a deterministic `address` from `keccak256(deployedBytecode)`
|
|
202
229
|
|
|
203
230
|
### Build-time path (apps with bundler plugin)
|
|
204
231
|
|
|
205
|
-
1. The bundler plugin parses each file's AST to find `sol` tagged templates
|
|
232
|
+
1. The bundler plugin parses each file's AST to find `sol("Name")` tagged templates
|
|
206
233
|
2. For templates with interpolations, resolves `const` string values statically
|
|
207
234
|
3. Compiles the resolved Solidity with `solc-js` during the build
|
|
208
|
-
4. Replaces `` sol`...` `` with `
|
|
209
|
-
5. At runtime, no compilation happens —
|
|
235
|
+
4. Replaces `` sol("Name")`...` `` with `InlineContract.fromArtifacts("Name", {...})` containing the pre-compiled ABI and bytecode
|
|
236
|
+
5. At runtime, no compilation happens — property access returns pre-compiled data directly
|
|
210
237
|
|
|
211
238
|
### IDE path (TypeScript Language Service Plugin)
|
|
212
239
|
|
|
213
240
|
1. The TS plugin runs in `tsserver` (your editor's TypeScript process)
|
|
214
|
-
2. It finds `sol` tagged templates, compiles them with solc-js, and extracts the ABI
|
|
215
|
-
3.
|
|
241
|
+
2. It finds `sol("Name")` tagged templates, compiles them with solc-js, and extracts the ABI
|
|
242
|
+
3. Generates `.soltag/types.d.ts` with module augmentation that narrows `abi` to precise "as const" types and adds typed `bytecode()` overloads per contract
|
|
243
|
+
4. Reports inline Solidity compilation errors and warns if the contract name doesn't match any contract in the source
|
|
244
|
+
|
|
245
|
+
## Immutables Caveat
|
|
246
|
+
|
|
247
|
+
Solidity `immutable` variables are assigned in the constructor and baked into the runtime bytecode during deployment. The `deployedBytecode` returned by solc (and exposed by `InlineContract`) contains **placeholder zeros** in immutable slots — the real values are only filled in when the constructor actually runs on-chain.
|
|
248
|
+
|
|
249
|
+
This means **`deployedBytecode` is unsuitable for `stateOverride` injection** when the contract uses immutable variables. The zeroed slots will cause the contract to behave incorrectly.
|
|
250
|
+
|
|
251
|
+
If your contract uses immutables, deploy it normally using `bytecode(…constructorArgs)` instead of injecting `deployedBytecode` via `stateOverride`. Note that `bytecode()` returns _creation_ bytecode (the code that runs the constructor and returns the final runtime code), not runtime bytecode with constructor args spliced in — there is no way to produce correct runtime bytecode with immutables without actually executing the constructor.
|
|
216
252
|
|
|
217
253
|
## API
|
|
218
254
|
|
|
219
255
|
### `sol`
|
|
220
256
|
|
|
221
257
|
```ts
|
|
222
|
-
function sol
|
|
258
|
+
function sol<TName extends string>(name: TName):
|
|
259
|
+
(strings: TemplateStringsArray, ...values: string[]) => InlineContract<TName>;
|
|
223
260
|
```
|
|
224
261
|
|
|
225
|
-
|
|
262
|
+
Factory that returns a tagged template function. The `name` must match a contract in the Solidity source.
|
|
226
263
|
|
|
227
|
-
### `
|
|
264
|
+
### `InlineContract<TName>`
|
|
228
265
|
|
|
229
266
|
```ts
|
|
230
|
-
class
|
|
267
|
+
class InlineContract<TName extends string = string> {
|
|
231
268
|
// Create from pre-compiled artifacts (used by bundler plugin)
|
|
232
|
-
static fromArtifacts(
|
|
269
|
+
static fromArtifacts<T extends string>(name: T, artifacts: CompilationResult): InlineContract<T>;
|
|
270
|
+
|
|
271
|
+
// The contract name (typed as a string literal)
|
|
272
|
+
get name(): TName;
|
|
273
|
+
|
|
274
|
+
// The contract's ABI (narrowed to precise type via generated .d.ts)
|
|
275
|
+
get abi(): Abi;
|
|
276
|
+
|
|
277
|
+
// Runtime bytecode as emitted by solc (see Immutables Caveat)
|
|
278
|
+
get deployedBytecode(): Hex;
|
|
233
279
|
|
|
234
|
-
//
|
|
235
|
-
|
|
280
|
+
// Deterministic address derived from keccak256(deployedBytecode)
|
|
281
|
+
get address(): Hex;
|
|
236
282
|
|
|
237
|
-
//
|
|
238
|
-
|
|
283
|
+
// Creation bytecode, optionally with ABI-encoded constructor args appended
|
|
284
|
+
bytecode(...args: unknown[]): Hex;
|
|
239
285
|
}
|
|
240
286
|
```
|
|
241
287
|
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// src/bundler/unplugin.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import MagicString from "magic-string";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
import { createUnplugin } from "unplugin";
|
|
7
|
+
|
|
8
|
+
// src/ast-utils.ts
|
|
9
|
+
function isSolTag(ts2, tag) {
|
|
10
|
+
if (ts2.isCallExpression(tag) && ts2.isIdentifier(tag.expression) && tag.expression.text === "sol" && tag.arguments.length === 1 && ts2.isStringLiteral(tag.arguments[0])) {
|
|
11
|
+
return { contractName: tag.arguments[0].text };
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function resolveStringExpression(ts2, node, sourceFile) {
|
|
16
|
+
if (ts2.isStringLiteral(node)) return node.text;
|
|
17
|
+
if (ts2.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
18
|
+
if (ts2.isTemplateExpression(node)) {
|
|
19
|
+
let result = node.head.text;
|
|
20
|
+
for (const span of node.templateSpans) {
|
|
21
|
+
const resolved = resolveStringExpression(ts2, span.expression, sourceFile);
|
|
22
|
+
if (resolved === void 0) return void 0;
|
|
23
|
+
result += resolved + span.literal.text;
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
if (ts2.isIdentifier(node)) {
|
|
28
|
+
return resolveIdentifierToString(ts2, node, sourceFile);
|
|
29
|
+
}
|
|
30
|
+
if (ts2.isBinaryExpression(node) && node.operatorToken.kind === ts2.SyntaxKind.PlusToken) {
|
|
31
|
+
const left = resolveStringExpression(ts2, node.left, sourceFile);
|
|
32
|
+
const right = resolveStringExpression(ts2, node.right, sourceFile);
|
|
33
|
+
if (left !== void 0 && right !== void 0) return left + right;
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
return void 0;
|
|
37
|
+
}
|
|
38
|
+
function resolveIdentifierToString(ts2, identifier, sourceFile) {
|
|
39
|
+
const name = identifier.text;
|
|
40
|
+
for (const statement of sourceFile.statements) {
|
|
41
|
+
if (!ts2.isVariableStatement(statement)) continue;
|
|
42
|
+
if (!(statement.declarationList.flags & ts2.NodeFlags.Const)) continue;
|
|
43
|
+
for (const decl of statement.declarationList.declarations) {
|
|
44
|
+
if (ts2.isIdentifier(decl.name) && decl.name.text === name && decl.initializer) {
|
|
45
|
+
return resolveStringExpression(ts2, decl.initializer, sourceFile);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
function extractTemplateSource(ts2, template, sourceFile) {
|
|
52
|
+
if (ts2.isNoSubstitutionTemplateLiteral(template)) {
|
|
53
|
+
return template.text;
|
|
54
|
+
}
|
|
55
|
+
let result = template.head.text;
|
|
56
|
+
for (const span of template.templateSpans) {
|
|
57
|
+
const resolved = resolveStringExpression(ts2, span.expression, sourceFile);
|
|
58
|
+
if (resolved === void 0) return void 0;
|
|
59
|
+
result += resolved + span.literal.text;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/codegen.ts
|
|
65
|
+
var SOLTAG_DIR = ".soltag";
|
|
66
|
+
var SOLTAG_TYPES_FILE = "types.d.ts";
|
|
67
|
+
function solidityTypeToTs(param) {
|
|
68
|
+
const t = param.type;
|
|
69
|
+
if (t === "address") return "`0x${string}`";
|
|
70
|
+
if (t === "bool") return "boolean";
|
|
71
|
+
if (t === "string") return "string";
|
|
72
|
+
if (t === "bytes" || t.match(/^bytes\d+$/)) return "`0x${string}`";
|
|
73
|
+
if (t.match(/^u?int\d*$/)) return "bigint";
|
|
74
|
+
if (t.endsWith("[]")) {
|
|
75
|
+
const inner = { ...param, type: t.slice(0, -2) };
|
|
76
|
+
return `${solidityTypeToTs(inner)}[]`;
|
|
77
|
+
}
|
|
78
|
+
const fixedArray = t.match(/^(.+)\[(\d+)\]$/);
|
|
79
|
+
if (fixedArray) {
|
|
80
|
+
const inner = { ...param, type: fixedArray[1] };
|
|
81
|
+
return `${solidityTypeToTs(inner)}[]`;
|
|
82
|
+
}
|
|
83
|
+
if (t === "tuple" && param.components) {
|
|
84
|
+
const fields = param.components.map((c) => `${c.name}: ${solidityTypeToTs(c)}`).join("; ");
|
|
85
|
+
return `{ ${fields} }`;
|
|
86
|
+
}
|
|
87
|
+
return "unknown";
|
|
88
|
+
}
|
|
89
|
+
function jsonToConstType(value) {
|
|
90
|
+
if (value === null || value === void 0) return "null";
|
|
91
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
92
|
+
if (typeof value === "number") return String(value);
|
|
93
|
+
if (typeof value === "boolean") return String(value);
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
if (value.length === 0) return "readonly []";
|
|
96
|
+
return `readonly [${value.map(jsonToConstType).join(", ")}]`;
|
|
97
|
+
}
|
|
98
|
+
if (typeof value === "object") {
|
|
99
|
+
const entries = Object.entries(value);
|
|
100
|
+
if (entries.length === 0) return "{}";
|
|
101
|
+
const fields = entries.map(([k, v]) => `readonly ${k}: ${jsonToConstType(v)}`);
|
|
102
|
+
return `{ ${fields.join("; ")} }`;
|
|
103
|
+
}
|
|
104
|
+
return "unknown";
|
|
105
|
+
}
|
|
106
|
+
function generateDeclarationContent(entries) {
|
|
107
|
+
if (entries.length === 0) return { content: "", duplicates: [] };
|
|
108
|
+
const byName = /* @__PURE__ */ new Map();
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
const existing = byName.get(entry.contractName);
|
|
111
|
+
if (existing) {
|
|
112
|
+
existing.push(entry);
|
|
113
|
+
} else {
|
|
114
|
+
byName.set(entry.contractName, [entry]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const duplicates = [];
|
|
118
|
+
const unique = [];
|
|
119
|
+
for (const [name, group] of byName) {
|
|
120
|
+
unique.push(group[0]);
|
|
121
|
+
if (group.length > 1) {
|
|
122
|
+
const fingerprints = new Set(
|
|
123
|
+
group.map((e) => JSON.stringify({ constructorInputs: e.constructorInputs, abi: e.abi }))
|
|
124
|
+
);
|
|
125
|
+
if (fingerprints.size > 1) {
|
|
126
|
+
duplicates.push(name);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const abiMapEntries = [];
|
|
131
|
+
const overloads = [];
|
|
132
|
+
for (const entry of unique) {
|
|
133
|
+
abiMapEntries.push(` ${JSON.stringify(entry.contractName)}: ${jsonToConstType(entry.abi)};`);
|
|
134
|
+
const params = entry.constructorInputs.map((p, i) => {
|
|
135
|
+
const name = p.name || `arg${i}`;
|
|
136
|
+
return `${name}: ${solidityTypeToTs(p)}`;
|
|
137
|
+
});
|
|
138
|
+
overloads.push(
|
|
139
|
+
` bytecode(this: InlineContract<${JSON.stringify(entry.contractName)}>${params.length > 0 ? `, ${params.join(", ")}` : ""}): \`0x\${string}\`;`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (unique.length === 0) return { content: "", duplicates };
|
|
143
|
+
const content = `export {}
|
|
144
|
+
declare module "soltag" {
|
|
145
|
+
interface InlineContractAbiMap {
|
|
146
|
+
${abiMapEntries.join("\n")}
|
|
147
|
+
}
|
|
148
|
+
interface InlineContract<TName extends string> {
|
|
149
|
+
${overloads.join("\n")}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
return { content, duplicates };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/runtime/compiler.ts
|
|
157
|
+
import { createRequire } from "module";
|
|
158
|
+
import { keccak256, toHex } from "viem";
|
|
159
|
+
|
|
160
|
+
// src/solc.ts
|
|
161
|
+
function buildSolcInput(source, options) {
|
|
162
|
+
return {
|
|
163
|
+
language: "Solidity",
|
|
164
|
+
sources: {
|
|
165
|
+
"inline.sol": { content: source }
|
|
166
|
+
},
|
|
167
|
+
settings: {
|
|
168
|
+
optimizer: {
|
|
169
|
+
enabled: options?.optimizer?.enabled ?? true,
|
|
170
|
+
runs: options?.optimizer?.runs ?? 1
|
|
171
|
+
},
|
|
172
|
+
outputSelection: {
|
|
173
|
+
"*": {
|
|
174
|
+
"*": ["abi", "evm.bytecode.object", "evm.deployedBytecode.object"]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/runtime/compiler.ts
|
|
182
|
+
var require2 = createRequire(import.meta.url);
|
|
183
|
+
var solcInstance;
|
|
184
|
+
function getSolc() {
|
|
185
|
+
if (!solcInstance) {
|
|
186
|
+
try {
|
|
187
|
+
solcInstance = require2("solc");
|
|
188
|
+
} catch {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"solc is not installed. Install it (`pnpm add solc`) for runtime compilation, or use the soltag bundler plugin for build-time compilation."
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return solcInstance;
|
|
195
|
+
}
|
|
196
|
+
var SolCompilationError = class extends Error {
|
|
197
|
+
constructor(errors) {
|
|
198
|
+
const formatted = errors.map((e) => `${e.severity}: ${e.message}`).join("\n");
|
|
199
|
+
super(`Solidity compilation failed:
|
|
200
|
+
${formatted}`);
|
|
201
|
+
this.errors = errors;
|
|
202
|
+
this.name = "SolCompilationError";
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var compilationCache = /* @__PURE__ */ new Map();
|
|
206
|
+
function hashSource(source, options) {
|
|
207
|
+
return keccak256(toHex(source + JSON.stringify(options ?? {})));
|
|
208
|
+
}
|
|
209
|
+
function compile(source, options) {
|
|
210
|
+
const hash = hashSource(source, options);
|
|
211
|
+
const cached = compilationCache.get(hash);
|
|
212
|
+
if (cached) return cached;
|
|
213
|
+
const solc = getSolc();
|
|
214
|
+
const input = buildSolcInput(source, options);
|
|
215
|
+
const rawOutput = solc.compile(JSON.stringify(input));
|
|
216
|
+
const output = JSON.parse(rawOutput);
|
|
217
|
+
const diagnostics = toDiagnostics(output.errors);
|
|
218
|
+
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
219
|
+
if (errors.length > 0) {
|
|
220
|
+
throw new SolCompilationError(errors);
|
|
221
|
+
}
|
|
222
|
+
const result = {};
|
|
223
|
+
if (output.contracts) {
|
|
224
|
+
for (const [, fileContracts] of Object.entries(output.contracts)) {
|
|
225
|
+
for (const [contractName, contractOutput] of Object.entries(fileContracts)) {
|
|
226
|
+
result[contractName] = {
|
|
227
|
+
abi: contractOutput.abi,
|
|
228
|
+
deployedBytecode: `0x${contractOutput.evm.deployedBytecode.object}`,
|
|
229
|
+
bytecode: `0x${contractOutput.evm.bytecode.object}`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
compilationCache.set(hash, result);
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
function toDiagnostics(errors) {
|
|
238
|
+
return (errors ?? []).map((e) => ({
|
|
239
|
+
severity: e.severity,
|
|
240
|
+
message: e.message,
|
|
241
|
+
formattedMessage: e.formattedMessage,
|
|
242
|
+
sourceLocation: e.sourceLocation
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/bundler/unplugin.ts
|
|
247
|
+
function getConstructorInputs(abi) {
|
|
248
|
+
const ctor = abi.find((item) => item.type === "constructor");
|
|
249
|
+
if (!ctor) return [];
|
|
250
|
+
return ctor.inputs ?? [];
|
|
251
|
+
}
|
|
252
|
+
function transformSolTemplates(code, id, namedEntries, options) {
|
|
253
|
+
if (!/\bsol\s*\(\s*["'][^"']+["']\s*\)/.test(code)) return void 0;
|
|
254
|
+
const sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true);
|
|
255
|
+
const s = new MagicString(code);
|
|
256
|
+
let hasReplacements = false;
|
|
257
|
+
function visit(node) {
|
|
258
|
+
if (ts.isTaggedTemplateExpression(node)) {
|
|
259
|
+
const solTag = isSolTag(ts, node.tag);
|
|
260
|
+
if (solTag) {
|
|
261
|
+
const contractName = solTag.contractName;
|
|
262
|
+
const soliditySource = extractTemplateSource(ts, node.template, sourceFile);
|
|
263
|
+
if (soliditySource === void 0) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const artifacts = compile(soliditySource, options?.solc);
|
|
267
|
+
if (namedEntries != null) {
|
|
268
|
+
const contractArtifact = artifacts[contractName];
|
|
269
|
+
const constructorInputs = contractArtifact ? getConstructorInputs(contractArtifact.abi) : [];
|
|
270
|
+
const abi = contractArtifact ? contractArtifact.abi : [];
|
|
271
|
+
namedEntries.set(`${contractName}\0${soliditySource}`, {
|
|
272
|
+
contractName,
|
|
273
|
+
constructorInputs,
|
|
274
|
+
abi
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const replacement = `__InlineContract.fromArtifacts(${JSON.stringify(contractName)}, ${JSON.stringify(artifacts)})`;
|
|
278
|
+
s.overwrite(node.getStart(sourceFile), node.getEnd(), replacement);
|
|
279
|
+
hasReplacements = true;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
ts.forEachChild(node, visit);
|
|
284
|
+
}
|
|
285
|
+
visit(sourceFile);
|
|
286
|
+
if (!hasReplacements) return void 0;
|
|
287
|
+
s.prepend('import { InlineContract as __InlineContract } from "soltag";\n');
|
|
288
|
+
return {
|
|
289
|
+
code: s.toString(),
|
|
290
|
+
map: s.generateMap({ source: id, hires: true })
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
var unplugin = createUnplugin((options) => {
|
|
294
|
+
const include = options?.include ?? [".ts", ".tsx", ".mts", ".cts"];
|
|
295
|
+
const exclude = options?.exclude ?? [/node_modules/];
|
|
296
|
+
const namedEntries = /* @__PURE__ */ new Map();
|
|
297
|
+
let rootDir;
|
|
298
|
+
return {
|
|
299
|
+
name: "soltag",
|
|
300
|
+
enforce: "pre",
|
|
301
|
+
vite: {
|
|
302
|
+
configResolved(config) {
|
|
303
|
+
rootDir = config.root;
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
buildStart() {
|
|
307
|
+
namedEntries.clear();
|
|
308
|
+
},
|
|
309
|
+
transform: {
|
|
310
|
+
filter: {
|
|
311
|
+
id: {
|
|
312
|
+
include: include.map((ext) => new RegExp(`${ext.replace(".", "\\.")}$`)),
|
|
313
|
+
exclude
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
handler(code, id) {
|
|
317
|
+
if (!rootDir) {
|
|
318
|
+
rootDir = process.cwd();
|
|
319
|
+
}
|
|
320
|
+
return transformSolTemplates(code, id, namedEntries, options);
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
buildEnd() {
|
|
324
|
+
if (!rootDir || namedEntries.size === 0) return;
|
|
325
|
+
const entries = Array.from(namedEntries.values());
|
|
326
|
+
const { content } = generateDeclarationContent(entries);
|
|
327
|
+
if (content === "") return;
|
|
328
|
+
const typesDir = path.join(rootDir, SOLTAG_DIR);
|
|
329
|
+
const typesFile = path.join(typesDir, SOLTAG_TYPES_FILE);
|
|
330
|
+
if (!fs.existsSync(typesDir)) {
|
|
331
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
332
|
+
}
|
|
333
|
+
let existing;
|
|
334
|
+
try {
|
|
335
|
+
existing = fs.readFileSync(typesFile, "utf-8");
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
if (existing !== content) {
|
|
339
|
+
fs.writeFileSync(typesFile, content, "utf-8");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
});
|
|
344
|
+
var unplugin_default = unplugin;
|
|
345
|
+
|
|
346
|
+
export {
|
|
347
|
+
transformSolTemplates,
|
|
348
|
+
unplugin,
|
|
349
|
+
unplugin_default
|
|
350
|
+
};
|
|
351
|
+
//# sourceMappingURL=chunk-YTNI7QSL.js.map
|