@appliedblockchain/silentdatarollup-ethers-provider 1.0.8 → 1.0.10
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 +21 -0
- package/README.md +66 -3
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +168 -35
- package/dist/index.mjs +170 -35
- package/package.json +11 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Applied Blockchain Ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
- [Overview](#overview)
|
|
16
16
|
- [Using the SDInterface](#using-the-sdinterface)
|
|
17
17
|
- [Private Events Example](#private-events-example)
|
|
18
|
+
- [Smart Account Support](#smart-account-support)
|
|
19
|
+
- [Smart Account Overview](#smart-account-overview)
|
|
20
|
+
- [Smart Account Example](#smart-account-example)
|
|
18
21
|
- [Troubleshooting](#troubleshooting)
|
|
19
22
|
- [License](#license)
|
|
20
23
|
- [Additional Resources](#additional-resources)
|
|
@@ -26,7 +29,7 @@ Custom providers for Silent Data, compatible with ethers.js.
|
|
|
26
29
|
## Prerequisites
|
|
27
30
|
|
|
28
31
|
- Node.js (version 18 or higher)
|
|
29
|
-
-
|
|
32
|
+
- pnpm
|
|
30
33
|
- Basic knowledge of Ethereum and smart contracts
|
|
31
34
|
- Ethers.js v6
|
|
32
35
|
|
|
@@ -37,7 +40,7 @@ Custom providers for Silent Data, compatible with ethers.js.
|
|
|
37
40
|
#### Installing Basic Usage Dependencies
|
|
38
41
|
|
|
39
42
|
```bash
|
|
40
|
-
|
|
43
|
+
pnpm add @appliedblockchain/silentdatarollup-core @appliedblockchain/silentdatarollup-ethers-provider ethers@6
|
|
41
44
|
```
|
|
42
45
|
|
|
43
46
|
#### Basic Usage Example
|
|
@@ -67,7 +70,7 @@ console.log(balance)
|
|
|
67
70
|
#### Installing Usage with a Contract Dependencies
|
|
68
71
|
|
|
69
72
|
```bash
|
|
70
|
-
|
|
73
|
+
pnpm add @appliedblockchain/silentdatarollup-core @appliedblockchain/silentdatarollup-ethers-provider ethers@6
|
|
71
74
|
```
|
|
72
75
|
|
|
73
76
|
#### Usage with a Contract Example
|
|
@@ -216,6 +219,66 @@ for (const log of privateEvents) {
|
|
|
216
219
|
}
|
|
217
220
|
```
|
|
218
221
|
|
|
222
|
+
### Smart Account Support
|
|
223
|
+
|
|
224
|
+
#### Smart Account Overview
|
|
225
|
+
|
|
226
|
+
Silent Data Rollup supports smart accounts (smart wallet contracts) that implement [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) for signature verification. When using a smart account, you provide the `smartWalletAddress` in the provider configuration, and the provider automatically handles signature verification through the smart contract.
|
|
227
|
+
|
|
228
|
+
**Key features:**
|
|
229
|
+
|
|
230
|
+
- **EIP-1271 Compatibility**: Your smart account contract must implement the `isValidSignature(bytes32 hash, bytes signature)` function
|
|
231
|
+
- **Automatic Hash Signing**: When `smartWalletAddress` is provided, the provider signs the hash of messages for proper EIP-1271 verification
|
|
232
|
+
- **Flexible Signer Support**: Works with any signer implementation, including passkey signers, browser wallets, and more
|
|
233
|
+
|
|
234
|
+
#### Smart Account Example
|
|
235
|
+
|
|
236
|
+
This example shows how to use the provider with a passkey signer and smart account. For a complete passkey implementation, see the [Giano repository](https://github.com/appliedblockchain/giano).
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { SilentDataRollupProvider } from '@appliedblockchain/silentdatarollup-ethers-provider'
|
|
240
|
+
import { NetworkName } from '@appliedblockchain/silentdatarollup-core'
|
|
241
|
+
// Example: Using Giano passkey signer
|
|
242
|
+
// See https://github.com/appliedblockchain/giano for full implementation
|
|
243
|
+
import { GianoSigner } from '@appliedblockchain/giano-client'
|
|
244
|
+
|
|
245
|
+
// Initialize your Giano passkey signer
|
|
246
|
+
const gianoSigner = await GianoSigner.create({
|
|
247
|
+
// Passkey configuration
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// Configure provider with smart account
|
|
251
|
+
const providerConfig = {
|
|
252
|
+
rpcUrl: 'SILENT_DATA_ROLLUP_RPC_URL',
|
|
253
|
+
network: NetworkName.TESTNET,
|
|
254
|
+
signer: gianoSigner,
|
|
255
|
+
// Provide your EIP-1271 compatible smart account address
|
|
256
|
+
smartWalletAddress: '0xYOUR_SMART_ACCOUNT_ADDRESS',
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const provider = new SilentDataRollupProvider(providerConfig)
|
|
260
|
+
|
|
261
|
+
// Use the provider as normal
|
|
262
|
+
// All signatures will be verified via your smart account's EIP-1271 implementation
|
|
263
|
+
const balance = await provider.getBalance('YOUR_ADDRESS')
|
|
264
|
+
console.log(balance)
|
|
265
|
+
|
|
266
|
+
// Works with contracts too
|
|
267
|
+
const contractConfig = {
|
|
268
|
+
contractAddress: 'YOUR_CONTRACT_ADDRESS',
|
|
269
|
+
abi: [
|
|
270
|
+
/* Your contract ABI */
|
|
271
|
+
],
|
|
272
|
+
runner: provider,
|
|
273
|
+
methodsToSign: ['privateMethod'],
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const contract = new SilentDataRollupContract(contractConfig)
|
|
277
|
+
const result = await contract.privateMethod('param')
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Note**: Your smart account contract must implement EIP-1271's `isValidSignature` function to verify signatures. The provider will automatically sign message hashes when `smartWalletAddress` is configured, ensuring compatibility with the EIP-1271 standard.
|
|
281
|
+
|
|
219
282
|
## License
|
|
220
283
|
|
|
221
284
|
This project is licensed under the [MIT License](LICENSE).
|
package/dist/index.d.mts
CHANGED
|
@@ -5,12 +5,19 @@ declare const DEBUG_NAMESPACE = "silentdata:ethers-provider";
|
|
|
5
5
|
|
|
6
6
|
interface SilentDataRollupProviderConfig extends BaseConfig {
|
|
7
7
|
rpcUrl: string;
|
|
8
|
+
l1RpcUrl?: string;
|
|
9
|
+
registryContract?: string;
|
|
8
10
|
network?: NetworkName;
|
|
9
11
|
chainId?: number;
|
|
10
12
|
privateKey?: string;
|
|
11
13
|
signer?: Signer;
|
|
12
14
|
options?: JsonRpcApiProviderOptions;
|
|
13
15
|
smartWalletAddress?: string;
|
|
16
|
+
/**
|
|
17
|
+
* When set to true, all eth_call requests will be signed
|
|
18
|
+
* with authentication headers, regardless of whether they match signable contracts
|
|
19
|
+
*/
|
|
20
|
+
alwaysSignEthCalls?: boolean;
|
|
14
21
|
}
|
|
15
22
|
/**
|
|
16
23
|
* Extended filter type that includes a special flag for private events
|
|
@@ -37,6 +44,15 @@ declare class SilentDataRollupProvider extends JsonRpcProvider {
|
|
|
37
44
|
private baseProvider;
|
|
38
45
|
constructor(config: SilentDataRollupProviderConfig);
|
|
39
46
|
_send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult>>;
|
|
47
|
+
private _isRegistryContractSet;
|
|
48
|
+
private _isNodeRuntime;
|
|
49
|
+
/**
|
|
50
|
+
* Get authentication headers if required for the given payload.
|
|
51
|
+
* Returns null if no auth headers are needed.
|
|
52
|
+
*/
|
|
53
|
+
private _getAuthHeaders;
|
|
54
|
+
private _sendWithoutAttestation;
|
|
55
|
+
private _sendWithPinnedTlsAndAttest;
|
|
40
56
|
static getRequest({ rpcUrl }: {
|
|
41
57
|
rpcUrl: string;
|
|
42
58
|
}): FetchRequest;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,19 @@ declare const DEBUG_NAMESPACE = "silentdata:ethers-provider";
|
|
|
5
5
|
|
|
6
6
|
interface SilentDataRollupProviderConfig extends BaseConfig {
|
|
7
7
|
rpcUrl: string;
|
|
8
|
+
l1RpcUrl?: string;
|
|
9
|
+
registryContract?: string;
|
|
8
10
|
network?: NetworkName;
|
|
9
11
|
chainId?: number;
|
|
10
12
|
privateKey?: string;
|
|
11
13
|
signer?: Signer;
|
|
12
14
|
options?: JsonRpcApiProviderOptions;
|
|
13
15
|
smartWalletAddress?: string;
|
|
16
|
+
/**
|
|
17
|
+
* When set to true, all eth_call requests will be signed
|
|
18
|
+
* with authentication headers, regardless of whether they match signable contracts
|
|
19
|
+
*/
|
|
20
|
+
alwaysSignEthCalls?: boolean;
|
|
14
21
|
}
|
|
15
22
|
/**
|
|
16
23
|
* Extended filter type that includes a special flag for private events
|
|
@@ -37,6 +44,15 @@ declare class SilentDataRollupProvider extends JsonRpcProvider {
|
|
|
37
44
|
private baseProvider;
|
|
38
45
|
constructor(config: SilentDataRollupProviderConfig);
|
|
39
46
|
_send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult>>;
|
|
47
|
+
private _isRegistryContractSet;
|
|
48
|
+
private _isNodeRuntime;
|
|
49
|
+
/**
|
|
50
|
+
* Get authentication headers if required for the given payload.
|
|
51
|
+
* Returns null if no auth headers are needed.
|
|
52
|
+
*/
|
|
53
|
+
private _getAuthHeaders;
|
|
54
|
+
private _sendWithoutAttestation;
|
|
55
|
+
private _sendWithPinnedTlsAndAttest;
|
|
40
56
|
static getRequest({ rpcUrl }: {
|
|
41
57
|
rpcUrl: string;
|
|
42
58
|
}): FetchRequest;
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,7 @@ var SilentDataRollupSigner = class extends import_ethers.AbstractSigner {
|
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
// src/provider.ts
|
|
93
|
+
var import_silentdatarollup_core2 = require("@appliedblockchain/silentdatarollup-core");
|
|
93
94
|
var log = (0, import_debug.default)(import_silentdatarollup_core.DEBUG_NAMESPACE);
|
|
94
95
|
function isPromise(value) {
|
|
95
96
|
return value && typeof value.then === "function";
|
|
@@ -126,7 +127,10 @@ var SilentDataRollupProvider = class _SilentDataRollupProvider extends import_et
|
|
|
126
127
|
"config",
|
|
127
128
|
config
|
|
128
129
|
);
|
|
129
|
-
this.baseProvider = new import_silentdatarollup_core.SilentDataRollupBase(
|
|
130
|
+
this.baseProvider = new import_silentdatarollup_core.SilentDataRollupBase({
|
|
131
|
+
...config,
|
|
132
|
+
smartWalletAddress: config.smartWalletAddress
|
|
133
|
+
});
|
|
130
134
|
this.config = config;
|
|
131
135
|
this.config.authSignatureType = config.authSignatureType || import_silentdatarollup_core.SignatureType.Raw;
|
|
132
136
|
if (config.signer) {
|
|
@@ -165,43 +169,78 @@ var SilentDataRollupProvider = class _SilentDataRollupProvider extends import_et
|
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
}
|
|
172
|
+
if (!this._isNodeRuntime() || !this._isRegistryContractSet()) {
|
|
173
|
+
return await this._sendWithoutAttestation(payload, isPrivateLogsRequest);
|
|
174
|
+
}
|
|
175
|
+
return await this._sendWithPinnedTlsAndAttest(payload, isPrivateLogsRequest);
|
|
176
|
+
}
|
|
177
|
+
_isRegistryContractSet() {
|
|
178
|
+
return Boolean(this.config.l1RpcUrl) && Boolean(this.config.registryContract);
|
|
179
|
+
}
|
|
180
|
+
_isNodeRuntime() {
|
|
181
|
+
return typeof process !== "undefined" && typeof process.versions === "object" && typeof process.versions.node === "string";
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get authentication headers if required for the given payload.
|
|
185
|
+
* Returns null if no auth headers are needed.
|
|
186
|
+
*/
|
|
187
|
+
async _getAuthHeaders(payload, isPrivateLogsRequest) {
|
|
188
|
+
const requiresAuthHeaders = isPrivateLogsRequest || import_silentdatarollup_core.SIGN_RPC_METHODS.includes(payload.method) || (0, import_silentdatarollup_core.isSignableContractCall)(payload, this.baseProvider.contracts) || this.config.alwaysSignEthCalls && payload.method === "eth_call";
|
|
189
|
+
if (!requiresAuthHeaders) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const headers = {};
|
|
193
|
+
if (this.config.delegate) {
|
|
194
|
+
const {
|
|
195
|
+
[import_silentdatarollup_core.HEADER_DELEGATE]: xDelegate,
|
|
196
|
+
[import_silentdatarollup_core.HEADER_DELEGATE_SIGNATURE]: xDelegateSignature,
|
|
197
|
+
[import_silentdatarollup_core.HEADER_EIP712_DELEGATE_SIGNATURE]: xEip712DelegateSignature
|
|
198
|
+
} = await this.baseProvider.getDelegateHeaders(this);
|
|
199
|
+
headers[import_silentdatarollup_core.HEADER_DELEGATE] = xDelegate;
|
|
200
|
+
if (xDelegateSignature) {
|
|
201
|
+
headers[import_silentdatarollup_core.HEADER_DELEGATE_SIGNATURE] = xDelegateSignature;
|
|
202
|
+
}
|
|
203
|
+
if (xEip712DelegateSignature) {
|
|
204
|
+
headers[import_silentdatarollup_core.HEADER_EIP712_DELEGATE_SIGNATURE] = xEip712DelegateSignature;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (this.config.smartWalletAddress) {
|
|
208
|
+
log("Setting smart wallet header:", this.config.smartWalletAddress);
|
|
209
|
+
headers[import_silentdatarollup_core.HEADER_SIGNER_SWC] = this.config.smartWalletAddress;
|
|
210
|
+
}
|
|
211
|
+
const {
|
|
212
|
+
[import_silentdatarollup_core.HEADER_TIMESTAMP]: xTimestamp,
|
|
213
|
+
[import_silentdatarollup_core.HEADER_SIGNATURE]: xSignature,
|
|
214
|
+
[import_silentdatarollup_core.HEADER_EIP712_SIGNATURE]: xEip712Signature,
|
|
215
|
+
[import_silentdatarollup_core.HEADER_FROM_BLOCK]: xFromBlock
|
|
216
|
+
} = await this.baseProvider.getAuthHeaders(this, payload);
|
|
217
|
+
headers[import_silentdatarollup_core.HEADER_TIMESTAMP] = xTimestamp;
|
|
218
|
+
if (xSignature) {
|
|
219
|
+
headers[import_silentdatarollup_core.HEADER_SIGNATURE] = xSignature;
|
|
220
|
+
}
|
|
221
|
+
if (xEip712Signature) {
|
|
222
|
+
headers[import_silentdatarollup_core.HEADER_EIP712_SIGNATURE] = xEip712Signature;
|
|
223
|
+
}
|
|
224
|
+
if (xFromBlock) {
|
|
225
|
+
headers[import_silentdatarollup_core.HEADER_FROM_BLOCK] = xFromBlock;
|
|
226
|
+
}
|
|
227
|
+
const signatureType = this.config.authSignatureType ?? import_silentdatarollup_core.SignatureType.EIP191;
|
|
228
|
+
if (signatureType) {
|
|
229
|
+
headers[import_silentdatarollup_core.HEADER_SIGNATURE_TYPE] = signatureType;
|
|
230
|
+
}
|
|
231
|
+
return headers;
|
|
232
|
+
}
|
|
233
|
+
async _sendWithoutAttestation(payload, isPrivateLogsRequest) {
|
|
168
234
|
const request = this._getConnection();
|
|
169
235
|
request.body = JSON.stringify(payload);
|
|
170
236
|
request.setHeader("content-type", "application/json");
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
} = await this.baseProvider.getDelegateHeaders(this);
|
|
179
|
-
request.setHeader(import_silentdatarollup_core.HEADER_DELEGATE, xDelegate);
|
|
180
|
-
if (xDelegateSignature) {
|
|
181
|
-
request.setHeader(import_silentdatarollup_core.HEADER_DELEGATE_SIGNATURE, xDelegateSignature);
|
|
182
|
-
}
|
|
183
|
-
if (xEip712DelegateSignature) {
|
|
184
|
-
request.setHeader(
|
|
185
|
-
import_silentdatarollup_core.HEADER_EIP712_DELEGATE_SIGNATURE,
|
|
186
|
-
xEip712DelegateSignature
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (this.config.smartWalletAddress) {
|
|
191
|
-
log("Setting smart wallet header:", this.config.smartWalletAddress);
|
|
192
|
-
request.setHeader(import_silentdatarollup_core.HEADER_SIGNER_SWC, this.config.smartWalletAddress);
|
|
193
|
-
}
|
|
194
|
-
const {
|
|
195
|
-
[import_silentdatarollup_core.HEADER_TIMESTAMP]: xTimestamp,
|
|
196
|
-
[import_silentdatarollup_core.HEADER_SIGNATURE]: xSignature,
|
|
197
|
-
[import_silentdatarollup_core.HEADER_EIP712_SIGNATURE]: xEip712Signature
|
|
198
|
-
} = await this.baseProvider.getAuthHeaders(this, payload);
|
|
199
|
-
request.setHeader(import_silentdatarollup_core.HEADER_TIMESTAMP, xTimestamp);
|
|
200
|
-
if (xSignature) {
|
|
201
|
-
request.setHeader(import_silentdatarollup_core.HEADER_SIGNATURE, xSignature);
|
|
202
|
-
}
|
|
203
|
-
if (xEip712Signature) {
|
|
204
|
-
request.setHeader(import_silentdatarollup_core.HEADER_EIP712_SIGNATURE, xEip712Signature);
|
|
237
|
+
const authHeaders = await this._getAuthHeaders(
|
|
238
|
+
payload,
|
|
239
|
+
isPrivateLogsRequest
|
|
240
|
+
);
|
|
241
|
+
if (authHeaders) {
|
|
242
|
+
for (const [key, value] of Object.entries(authHeaders)) {
|
|
243
|
+
request.setHeader(key, value);
|
|
205
244
|
}
|
|
206
245
|
}
|
|
207
246
|
const response = await request.send();
|
|
@@ -212,6 +251,100 @@ var SilentDataRollupProvider = class _SilentDataRollupProvider extends import_et
|
|
|
212
251
|
}
|
|
213
252
|
return resp;
|
|
214
253
|
}
|
|
254
|
+
async _sendWithPinnedTlsAndAttest(payload, isPrivateLogsRequest) {
|
|
255
|
+
const https = await import(
|
|
256
|
+
/* webpackIgnore: true */
|
|
257
|
+
"https"
|
|
258
|
+
);
|
|
259
|
+
const { URL } = await import(
|
|
260
|
+
/* webpackIgnore: true */
|
|
261
|
+
"url"
|
|
262
|
+
);
|
|
263
|
+
const conn = this._getConnection();
|
|
264
|
+
const rpcUrlStr = conn.url;
|
|
265
|
+
if (!rpcUrlStr) {
|
|
266
|
+
throw new Error("Unable to determine rpcUrl for pinned TLS transport");
|
|
267
|
+
}
|
|
268
|
+
const rpcUrl = new URL(rpcUrlStr);
|
|
269
|
+
let exporterKey = null;
|
|
270
|
+
const doRequest = (method, path, body, headers2 = {}, forcedSocket) => new Promise((resolve, reject) => {
|
|
271
|
+
const req = https.request(
|
|
272
|
+
{
|
|
273
|
+
protocol: rpcUrl.protocol,
|
|
274
|
+
hostname: rpcUrl.hostname,
|
|
275
|
+
port: rpcUrl.port ? Number(rpcUrl.port) : 443,
|
|
276
|
+
path,
|
|
277
|
+
method,
|
|
278
|
+
headers: headers2,
|
|
279
|
+
agent: false,
|
|
280
|
+
createConnection: forcedSocket ? () => forcedSocket : void 0
|
|
281
|
+
},
|
|
282
|
+
(res) => {
|
|
283
|
+
const socket = res.socket;
|
|
284
|
+
const DomainSeparator = "CUSTOM-RPC ATTEST:";
|
|
285
|
+
const DOMAIN_SEPARATOR = Buffer.from(DomainSeparator, "utf8");
|
|
286
|
+
exporterKey = socket.exportKeyingMaterial(
|
|
287
|
+
32,
|
|
288
|
+
"tdx-attestation",
|
|
289
|
+
DOMAIN_SEPARATOR
|
|
290
|
+
);
|
|
291
|
+
const chunks = [];
|
|
292
|
+
res.on(
|
|
293
|
+
"data",
|
|
294
|
+
(d) => chunks.push(Buffer.isBuffer(d) ? d : Buffer.from(d))
|
|
295
|
+
);
|
|
296
|
+
res.on(
|
|
297
|
+
"end",
|
|
298
|
+
() => resolve({
|
|
299
|
+
statusCode: res.statusCode ?? 0,
|
|
300
|
+
body: Buffer.concat(chunks),
|
|
301
|
+
socket
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
req.on("error", reject);
|
|
307
|
+
if (body) {
|
|
308
|
+
req.write(body);
|
|
309
|
+
}
|
|
310
|
+
req.end();
|
|
311
|
+
});
|
|
312
|
+
const attest = await doRequest("GET", "/tdx/attest", void 0, {
|
|
313
|
+
connection: "keep-alive"
|
|
314
|
+
});
|
|
315
|
+
if (attest.statusCode < 200 || attest.statusCode >= 300) {
|
|
316
|
+
throw new Error(`/tdx/attest failed: HTTP ${attest.statusCode}`);
|
|
317
|
+
}
|
|
318
|
+
const isValid = await (0, import_silentdatarollup_core2.validateTdxAttestation)(
|
|
319
|
+
attest.body,
|
|
320
|
+
exporterKey,
|
|
321
|
+
this.config.l1RpcUrl,
|
|
322
|
+
this.config.registryContract
|
|
323
|
+
);
|
|
324
|
+
if (!isValid) {
|
|
325
|
+
throw new Error("TDX attestation validation failed");
|
|
326
|
+
}
|
|
327
|
+
const headers = {
|
|
328
|
+
"content-type": "application/json"
|
|
329
|
+
};
|
|
330
|
+
const authHeaders = await this._getAuthHeaders(
|
|
331
|
+
payload,
|
|
332
|
+
isPrivateLogsRequest
|
|
333
|
+
);
|
|
334
|
+
if (authHeaders) {
|
|
335
|
+
Object.assign(headers, authHeaders);
|
|
336
|
+
}
|
|
337
|
+
const rpcBody = Buffer.from(JSON.stringify(payload));
|
|
338
|
+
const rpc = await doRequest("POST", "/", rpcBody, headers, attest.socket);
|
|
339
|
+
if (rpc.statusCode < 200 || rpc.statusCode >= 300) {
|
|
340
|
+
throw new Error(`RPC POST / failed: HTTP ${rpc.statusCode}`);
|
|
341
|
+
}
|
|
342
|
+
let parsed = JSON.parse(rpc.body.toString("utf8"));
|
|
343
|
+
if (!Array.isArray(parsed)) {
|
|
344
|
+
parsed = [parsed];
|
|
345
|
+
}
|
|
346
|
+
return parsed;
|
|
347
|
+
}
|
|
215
348
|
static getRequest({ rpcUrl }) {
|
|
216
349
|
const request = new import_ethers2.FetchRequest(rpcUrl);
|
|
217
350
|
request.allowGzip = true;
|
package/dist/index.mjs
CHANGED
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
HEADER_DELEGATE_SIGNATURE,
|
|
12
12
|
HEADER_EIP712_DELEGATE_SIGNATURE,
|
|
13
13
|
HEADER_EIP712_SIGNATURE,
|
|
14
|
+
HEADER_FROM_BLOCK,
|
|
14
15
|
HEADER_SIGNATURE,
|
|
16
|
+
HEADER_SIGNATURE_TYPE,
|
|
15
17
|
HEADER_SIGNER_SWC,
|
|
16
18
|
HEADER_TIMESTAMP,
|
|
17
19
|
isSignableContractCall,
|
|
@@ -79,6 +81,7 @@ var SilentDataRollupSigner = class extends AbstractSigner {
|
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
// src/provider.ts
|
|
84
|
+
import { validateTdxAttestation } from "@appliedblockchain/silentdatarollup-core";
|
|
82
85
|
var log = debug(DEBUG_NAMESPACE2);
|
|
83
86
|
function isPromise(value) {
|
|
84
87
|
return value && typeof value.then === "function";
|
|
@@ -115,7 +118,10 @@ var SilentDataRollupProvider = class _SilentDataRollupProvider extends JsonRpcPr
|
|
|
115
118
|
"config",
|
|
116
119
|
config
|
|
117
120
|
);
|
|
118
|
-
this.baseProvider = new SilentDataRollupBase(
|
|
121
|
+
this.baseProvider = new SilentDataRollupBase({
|
|
122
|
+
...config,
|
|
123
|
+
smartWalletAddress: config.smartWalletAddress
|
|
124
|
+
});
|
|
119
125
|
this.config = config;
|
|
120
126
|
this.config.authSignatureType = config.authSignatureType || SignatureType.Raw;
|
|
121
127
|
if (config.signer) {
|
|
@@ -154,43 +160,78 @@ var SilentDataRollupProvider = class _SilentDataRollupProvider extends JsonRpcPr
|
|
|
154
160
|
}
|
|
155
161
|
}
|
|
156
162
|
}
|
|
163
|
+
if (!this._isNodeRuntime() || !this._isRegistryContractSet()) {
|
|
164
|
+
return await this._sendWithoutAttestation(payload, isPrivateLogsRequest);
|
|
165
|
+
}
|
|
166
|
+
return await this._sendWithPinnedTlsAndAttest(payload, isPrivateLogsRequest);
|
|
167
|
+
}
|
|
168
|
+
_isRegistryContractSet() {
|
|
169
|
+
return Boolean(this.config.l1RpcUrl) && Boolean(this.config.registryContract);
|
|
170
|
+
}
|
|
171
|
+
_isNodeRuntime() {
|
|
172
|
+
return typeof process !== "undefined" && typeof process.versions === "object" && typeof process.versions.node === "string";
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get authentication headers if required for the given payload.
|
|
176
|
+
* Returns null if no auth headers are needed.
|
|
177
|
+
*/
|
|
178
|
+
async _getAuthHeaders(payload, isPrivateLogsRequest) {
|
|
179
|
+
const requiresAuthHeaders = isPrivateLogsRequest || SIGN_RPC_METHODS.includes(payload.method) || isSignableContractCall(payload, this.baseProvider.contracts) || this.config.alwaysSignEthCalls && payload.method === "eth_call";
|
|
180
|
+
if (!requiresAuthHeaders) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const headers = {};
|
|
184
|
+
if (this.config.delegate) {
|
|
185
|
+
const {
|
|
186
|
+
[HEADER_DELEGATE]: xDelegate,
|
|
187
|
+
[HEADER_DELEGATE_SIGNATURE]: xDelegateSignature,
|
|
188
|
+
[HEADER_EIP712_DELEGATE_SIGNATURE]: xEip712DelegateSignature
|
|
189
|
+
} = await this.baseProvider.getDelegateHeaders(this);
|
|
190
|
+
headers[HEADER_DELEGATE] = xDelegate;
|
|
191
|
+
if (xDelegateSignature) {
|
|
192
|
+
headers[HEADER_DELEGATE_SIGNATURE] = xDelegateSignature;
|
|
193
|
+
}
|
|
194
|
+
if (xEip712DelegateSignature) {
|
|
195
|
+
headers[HEADER_EIP712_DELEGATE_SIGNATURE] = xEip712DelegateSignature;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (this.config.smartWalletAddress) {
|
|
199
|
+
log("Setting smart wallet header:", this.config.smartWalletAddress);
|
|
200
|
+
headers[HEADER_SIGNER_SWC] = this.config.smartWalletAddress;
|
|
201
|
+
}
|
|
202
|
+
const {
|
|
203
|
+
[HEADER_TIMESTAMP]: xTimestamp,
|
|
204
|
+
[HEADER_SIGNATURE]: xSignature,
|
|
205
|
+
[HEADER_EIP712_SIGNATURE]: xEip712Signature,
|
|
206
|
+
[HEADER_FROM_BLOCK]: xFromBlock
|
|
207
|
+
} = await this.baseProvider.getAuthHeaders(this, payload);
|
|
208
|
+
headers[HEADER_TIMESTAMP] = xTimestamp;
|
|
209
|
+
if (xSignature) {
|
|
210
|
+
headers[HEADER_SIGNATURE] = xSignature;
|
|
211
|
+
}
|
|
212
|
+
if (xEip712Signature) {
|
|
213
|
+
headers[HEADER_EIP712_SIGNATURE] = xEip712Signature;
|
|
214
|
+
}
|
|
215
|
+
if (xFromBlock) {
|
|
216
|
+
headers[HEADER_FROM_BLOCK] = xFromBlock;
|
|
217
|
+
}
|
|
218
|
+
const signatureType = this.config.authSignatureType ?? SignatureType.EIP191;
|
|
219
|
+
if (signatureType) {
|
|
220
|
+
headers[HEADER_SIGNATURE_TYPE] = signatureType;
|
|
221
|
+
}
|
|
222
|
+
return headers;
|
|
223
|
+
}
|
|
224
|
+
async _sendWithoutAttestation(payload, isPrivateLogsRequest) {
|
|
157
225
|
const request = this._getConnection();
|
|
158
226
|
request.body = JSON.stringify(payload);
|
|
159
227
|
request.setHeader("content-type", "application/json");
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
} = await this.baseProvider.getDelegateHeaders(this);
|
|
168
|
-
request.setHeader(HEADER_DELEGATE, xDelegate);
|
|
169
|
-
if (xDelegateSignature) {
|
|
170
|
-
request.setHeader(HEADER_DELEGATE_SIGNATURE, xDelegateSignature);
|
|
171
|
-
}
|
|
172
|
-
if (xEip712DelegateSignature) {
|
|
173
|
-
request.setHeader(
|
|
174
|
-
HEADER_EIP712_DELEGATE_SIGNATURE,
|
|
175
|
-
xEip712DelegateSignature
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (this.config.smartWalletAddress) {
|
|
180
|
-
log("Setting smart wallet header:", this.config.smartWalletAddress);
|
|
181
|
-
request.setHeader(HEADER_SIGNER_SWC, this.config.smartWalletAddress);
|
|
182
|
-
}
|
|
183
|
-
const {
|
|
184
|
-
[HEADER_TIMESTAMP]: xTimestamp,
|
|
185
|
-
[HEADER_SIGNATURE]: xSignature,
|
|
186
|
-
[HEADER_EIP712_SIGNATURE]: xEip712Signature
|
|
187
|
-
} = await this.baseProvider.getAuthHeaders(this, payload);
|
|
188
|
-
request.setHeader(HEADER_TIMESTAMP, xTimestamp);
|
|
189
|
-
if (xSignature) {
|
|
190
|
-
request.setHeader(HEADER_SIGNATURE, xSignature);
|
|
191
|
-
}
|
|
192
|
-
if (xEip712Signature) {
|
|
193
|
-
request.setHeader(HEADER_EIP712_SIGNATURE, xEip712Signature);
|
|
228
|
+
const authHeaders = await this._getAuthHeaders(
|
|
229
|
+
payload,
|
|
230
|
+
isPrivateLogsRequest
|
|
231
|
+
);
|
|
232
|
+
if (authHeaders) {
|
|
233
|
+
for (const [key, value] of Object.entries(authHeaders)) {
|
|
234
|
+
request.setHeader(key, value);
|
|
194
235
|
}
|
|
195
236
|
}
|
|
196
237
|
const response = await request.send();
|
|
@@ -201,6 +242,100 @@ var SilentDataRollupProvider = class _SilentDataRollupProvider extends JsonRpcPr
|
|
|
201
242
|
}
|
|
202
243
|
return resp;
|
|
203
244
|
}
|
|
245
|
+
async _sendWithPinnedTlsAndAttest(payload, isPrivateLogsRequest) {
|
|
246
|
+
const https = await import(
|
|
247
|
+
/* webpackIgnore: true */
|
|
248
|
+
"node:https"
|
|
249
|
+
);
|
|
250
|
+
const { URL } = await import(
|
|
251
|
+
/* webpackIgnore: true */
|
|
252
|
+
"node:url"
|
|
253
|
+
);
|
|
254
|
+
const conn = this._getConnection();
|
|
255
|
+
const rpcUrlStr = conn.url;
|
|
256
|
+
if (!rpcUrlStr) {
|
|
257
|
+
throw new Error("Unable to determine rpcUrl for pinned TLS transport");
|
|
258
|
+
}
|
|
259
|
+
const rpcUrl = new URL(rpcUrlStr);
|
|
260
|
+
let exporterKey = null;
|
|
261
|
+
const doRequest = (method, path, body, headers2 = {}, forcedSocket) => new Promise((resolve, reject) => {
|
|
262
|
+
const req = https.request(
|
|
263
|
+
{
|
|
264
|
+
protocol: rpcUrl.protocol,
|
|
265
|
+
hostname: rpcUrl.hostname,
|
|
266
|
+
port: rpcUrl.port ? Number(rpcUrl.port) : 443,
|
|
267
|
+
path,
|
|
268
|
+
method,
|
|
269
|
+
headers: headers2,
|
|
270
|
+
agent: false,
|
|
271
|
+
createConnection: forcedSocket ? () => forcedSocket : void 0
|
|
272
|
+
},
|
|
273
|
+
(res) => {
|
|
274
|
+
const socket = res.socket;
|
|
275
|
+
const DomainSeparator = "CUSTOM-RPC ATTEST:";
|
|
276
|
+
const DOMAIN_SEPARATOR = Buffer.from(DomainSeparator, "utf8");
|
|
277
|
+
exporterKey = socket.exportKeyingMaterial(
|
|
278
|
+
32,
|
|
279
|
+
"tdx-attestation",
|
|
280
|
+
DOMAIN_SEPARATOR
|
|
281
|
+
);
|
|
282
|
+
const chunks = [];
|
|
283
|
+
res.on(
|
|
284
|
+
"data",
|
|
285
|
+
(d) => chunks.push(Buffer.isBuffer(d) ? d : Buffer.from(d))
|
|
286
|
+
);
|
|
287
|
+
res.on(
|
|
288
|
+
"end",
|
|
289
|
+
() => resolve({
|
|
290
|
+
statusCode: res.statusCode ?? 0,
|
|
291
|
+
body: Buffer.concat(chunks),
|
|
292
|
+
socket
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
req.on("error", reject);
|
|
298
|
+
if (body) {
|
|
299
|
+
req.write(body);
|
|
300
|
+
}
|
|
301
|
+
req.end();
|
|
302
|
+
});
|
|
303
|
+
const attest = await doRequest("GET", "/tdx/attest", void 0, {
|
|
304
|
+
connection: "keep-alive"
|
|
305
|
+
});
|
|
306
|
+
if (attest.statusCode < 200 || attest.statusCode >= 300) {
|
|
307
|
+
throw new Error(`/tdx/attest failed: HTTP ${attest.statusCode}`);
|
|
308
|
+
}
|
|
309
|
+
const isValid = await validateTdxAttestation(
|
|
310
|
+
attest.body,
|
|
311
|
+
exporterKey,
|
|
312
|
+
this.config.l1RpcUrl,
|
|
313
|
+
this.config.registryContract
|
|
314
|
+
);
|
|
315
|
+
if (!isValid) {
|
|
316
|
+
throw new Error("TDX attestation validation failed");
|
|
317
|
+
}
|
|
318
|
+
const headers = {
|
|
319
|
+
"content-type": "application/json"
|
|
320
|
+
};
|
|
321
|
+
const authHeaders = await this._getAuthHeaders(
|
|
322
|
+
payload,
|
|
323
|
+
isPrivateLogsRequest
|
|
324
|
+
);
|
|
325
|
+
if (authHeaders) {
|
|
326
|
+
Object.assign(headers, authHeaders);
|
|
327
|
+
}
|
|
328
|
+
const rpcBody = Buffer.from(JSON.stringify(payload));
|
|
329
|
+
const rpc = await doRequest("POST", "/", rpcBody, headers, attest.socket);
|
|
330
|
+
if (rpc.statusCode < 200 || rpc.statusCode >= 300) {
|
|
331
|
+
throw new Error(`RPC POST / failed: HTTP ${rpc.statusCode}`);
|
|
332
|
+
}
|
|
333
|
+
let parsed = JSON.parse(rpc.body.toString("utf8"));
|
|
334
|
+
if (!Array.isArray(parsed)) {
|
|
335
|
+
parsed = [parsed];
|
|
336
|
+
}
|
|
337
|
+
return parsed;
|
|
338
|
+
}
|
|
204
339
|
static getRequest({ rpcUrl }) {
|
|
205
340
|
const request = new FetchRequest(rpcUrl);
|
|
206
341
|
request.allowGzip = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appliedblockchain/silentdatarollup-ethers-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Ethers.js provider for Silent Data",
|
|
5
5
|
"author": "Applied Blockchain",
|
|
6
6
|
"homepage": "https://github.com/appliedblockchain/silent-data-rollup-providers#readme",
|
|
@@ -25,23 +25,23 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
|
-
"scripts": {
|
|
29
|
-
"build": "tsc --noEmit && tsup src/index.ts --format cjs,esm --dts --clean",
|
|
30
|
-
"check-exports": "attw --pack . --profile node16",
|
|
31
|
-
"prepack": "npm run build",
|
|
32
|
-
"test": "jest"
|
|
33
|
-
},
|
|
34
28
|
"dependencies": {
|
|
35
|
-
"@appliedblockchain/silentdatarollup-core": "1.0.8",
|
|
36
29
|
"debug": "4.3.7",
|
|
37
|
-
"ethers": "6.13.2"
|
|
30
|
+
"ethers": "6.13.2",
|
|
31
|
+
"@appliedblockchain/silentdatarollup-core": "1.0.10"
|
|
38
32
|
},
|
|
39
33
|
"devDependencies": {
|
|
40
|
-
"@types/
|
|
34
|
+
"@types/debug": "4.1.12",
|
|
35
|
+
"@types/node": "24.10.1",
|
|
41
36
|
"ts-node": "10.9.2",
|
|
42
37
|
"typescript": "5.6.2"
|
|
43
38
|
},
|
|
44
39
|
"engines": {
|
|
45
40
|
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc --noEmit && tsup src/index.ts --format cjs,esm --dts --clean",
|
|
44
|
+
"check-exports": "attw --pack . --profile node16",
|
|
45
|
+
"test": "jest"
|
|
46
46
|
}
|
|
47
|
-
}
|
|
47
|
+
}
|