Skip to content

Commit a757d4e

Browse files
authored
add provider to SilentDataRollupContract to fix nonce issue (#1)
1 parent be55b45 commit a757d4e

File tree

7 files changed

+132
-89
lines changed

7 files changed

+132
-89
lines changed

examples/custom-rpc/src/contract-interaction.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ const provider = new SilentDataRollupProvider({
2828
privateKey: PRIVATE_KEY,
2929
})
3030

31-
const tokenContract = new SilentDataRollupContract(
32-
TOKEN_ADDRESS,
33-
ERC20_ABI,
31+
const tokenContract = new SilentDataRollupContract({
32+
address: TOKEN_ADDRESS,
33+
abi: ERC20_ABI,
3434
// @ts-expect-error - signer is a Signer
35-
provider.signer,
36-
['balanceOf'],
37-
)
35+
runner: provider.signer,
36+
contractMethodsToSign: ['balanceOf'],
37+
})
3838

3939
const main = async () => {
4040
const [decimals, name, symbol] = await Promise.all([

examples/ethers-provider-metamask/src/App.tsx

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ function App() {
9090
await Promise.all(
9191
tokens.map(async (token) => {
9292
try {
93-
const tokenContract = new SilentDataRollupContract(
94-
token.address,
95-
ERC20_ABI,
93+
const tokenContract = new SilentDataRollupContract({
94+
address: token.address,
95+
abi: ERC20_ABI,
9696
// @ts-expect-error - provider is a SilentDataRollupProvider
97-
provider,
98-
['balanceOf'],
99-
)
97+
runner: provider,
98+
contractMethodsToSign: ['balanceOf'],
99+
})
100100

101101
const [decimals, balance] = await Promise.all([
102102
tokenContract.decimals(),
@@ -140,13 +140,13 @@ function App() {
140140

141141
try {
142142
const { provider } = await createProvider()
143-
const tokenContract = new SilentDataRollupContract(
144-
tokenAddress,
145-
ERC20_ABI,
143+
const tokenContract = new SilentDataRollupContract({
144+
address: tokenAddress,
145+
abi: ERC20_ABI,
146146
// @ts-expect-error - provider is a SilentDataRollupProvider
147-
provider,
148-
['balanceOf'],
149-
)
147+
runner: provider,
148+
contractMethodsToSign: ['balanceOf'],
149+
})
150150

151151
const [name, symbol, decimals, balance] = await Promise.all([
152152
tokenContract.name(),
@@ -180,14 +180,16 @@ function App() {
180180

181181
const transferToken = async (tokenAddress: string) => {
182182
try {
183-
const { signer } = await createProvider()
183+
const { signer, provider } = await createProvider()
184184

185-
const tokenContract = new SilentDataRollupContract(
186-
tokenAddress,
187-
ERC20_ABI,
188-
signer,
189-
['transfer'],
190-
)
185+
const tokenContract = new SilentDataRollupContract({
186+
address: tokenAddress,
187+
abi: ERC20_ABI,
188+
runner: signer,
189+
contractMethodsToSign: ['transfer'],
190+
// @ts-expect-error - provider is a SilentDataRollupProvider
191+
provider,
192+
})
191193

192194
const randomWallet = Wallet.createRandom()
193195

@@ -209,13 +211,13 @@ function App() {
209211
tokens.map(async (token) => {
210212
try {
211213
const { provider } = await createProvider()
212-
const tokenContract = new SilentDataRollupContract(
213-
token.address,
214-
ERC20_ABI,
214+
const tokenContract = new SilentDataRollupContract({
215+
address: token.address,
216+
abi: ERC20_ABI,
215217
// @ts-expect-error - provider is a SilentDataRollupProvider
216-
provider,
217-
['balanceOf'],
218-
)
218+
runner: provider,
219+
contractMethodsToSign: ['balanceOf'],
220+
})
219221

220222
const [decimals, balance] = await Promise.all([
221223
tokenContract.decimals(),

examples/ethers-provider-nodejs/src/contract-interaction.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ const provider = new SilentDataRollupProvider({
2828
privateKey: PRIVATE_KEY,
2929
})
3030

31-
const tokenContract = new SilentDataRollupContract(
32-
TOKEN_ADDRESS,
33-
ERC20_ABI,
31+
const tokenContract = new SilentDataRollupContract({
32+
address: TOKEN_ADDRESS,
33+
abi: ERC20_ABI,
3434
// @ts-expect-error - signer is a Signer
35-
provider.signer,
36-
['balanceOf'],
37-
)
35+
runner: provider.signer,
36+
contractMethodsToSign: ['balanceOf'],
37+
})
3838

3939
const main = async () => {
4040
const [decimals, name, symbol] = await Promise.all([

packages/core/src/contract.ts

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,75 @@ import {
22
Contract,
33
ContractRunner,
44
Interface,
5-
InterfaceAbi,
65
Provider,
76
Signer,
87
TransactionRequest,
98
TransactionResponse,
9+
assertArgument,
1010
} from 'ethers'
11+
import { SilentDataRollupContractConfig } from './types'
1112

1213
/**
1314
* Custom contract runner that handles transaction submission
1415
* with proper nonce management. This ensures transactions are sent with
1516
* the latest nonce value to prevent transaction ordering issues.
1617
*/
1718
class CustomContractRunner implements ContractRunner {
18-
provider: Provider | null
19+
provider: Provider
1920
private signer: Signer
2021

21-
constructor(signer: Signer) {
22-
this.provider = signer.provider
22+
constructor(provider: Provider, signer: Signer) {
23+
this.provider = provider
2324
this.signer = signer
2425
}
2526

2627
async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse> {
27-
tx.nonce = await this.signer.getNonce()
28+
const signerAddress = await this.signer.getAddress()
29+
const latestNonce = await this.provider.getTransactionCount(
30+
signerAddress,
31+
'latest',
32+
)
33+
tx.nonce = latestNonce
2834
return this.signer.sendTransaction(tx)
2935
}
3036
}
3137

38+
function getContractRunner(
39+
runner: SilentDataRollupContractConfig['runner'],
40+
provider: SilentDataRollupContractConfig['provider'],
41+
): SilentDataRollupContractConfig['runner'] | CustomContractRunner {
42+
const runnerIsSigner = typeof runner.sendTransaction === 'function'
43+
44+
// If the runner is not a Signer return the runner as is
45+
if (!runnerIsSigner) {
46+
return runner
47+
}
48+
49+
// Get the runner provider constructor name
50+
const runnerProviderConstructor = runner.provider?.constructor.name ?? ''
51+
52+
/**
53+
* If the runner provider is not part of the Silent Data Rollup providers
54+
* we check if the provider is provided and create a new CustomContractRunner
55+
* with the provider and signer
56+
*/
57+
if (!runnerProviderConstructor.includes('SilentDataRollupProvider')) {
58+
assertArgument(provider, 'provider is mandatory', 'provider', provider)
59+
60+
return new CustomContractRunner(provider, runner as Signer)
61+
}
62+
63+
/**
64+
* If the runner provider is part of the Silent Data Rollup providers
65+
* we create a new CustomContractRunner with the runner provider and signer
66+
*/
67+
return new CustomContractRunner(runner.provider as Provider, runner as Signer)
68+
}
69+
3270
export class SilentDataRollupContract extends Contract {
33-
constructor(
34-
address: string,
35-
abi: InterfaceAbi,
36-
runner: ContractRunner,
37-
contractMethodsToSign: string[],
38-
) {
71+
constructor(config: SilentDataRollupContractConfig) {
72+
const { address, abi, runner, contractMethodsToSign, provider } = config
73+
3974
/**
4075
* Validates that all methods specified for signing exist in the contract ABI.
4176
* This check ensures that only legitimate contract functions are marked for signing,
@@ -50,20 +85,12 @@ export class SilentDataRollupContract extends Contract {
5085
}
5186
})
5287

53-
const baseProvider =
54-
(runner as any).baseProvider || (runner as any).provider?.baseProvider
88+
const contractRunner = getContractRunner(runner, provider)
5589

56-
/**
57-
* If the runner is a Signer, create a CustomContractRunner to handle
58-
* transaction submission with proper nonce management.
59-
*/
60-
const runnerIsSigner = typeof (runner as any).sendTransaction === 'function'
61-
if (runnerIsSigner) {
62-
runner = new CustomContractRunner(runner as Signer)
63-
}
64-
65-
super(address, abi, runner)
90+
super(address, abi, contractRunner)
6691

92+
const baseProvider =
93+
(runner as any).baseProvider || (runner as any).provider?.baseProvider
6794
if (typeof baseProvider?.setContract === 'function') {
6895
baseProvider.setContract(this, contractMethodsToSign)
6996
}

packages/core/src/types.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { JsonRpcPayload, Signer } from 'ethers'
1+
import {
2+
ContractRunner,
3+
InterfaceAbi,
4+
JsonRpcPayload,
5+
Provider,
6+
Signer,
7+
} from 'ethers'
28
import {
39
HEADER_DELEGATE,
410
HEADER_DELEGATE_SIGNATURE,
@@ -87,3 +93,11 @@ export type DelegateHeaders = {
8793
[HEADER_DELEGATE_SIGNATURE]?: string
8894
[HEADER_EIP712_DELEGATE_SIGNATURE]?: string
8995
}
96+
97+
export type SilentDataRollupContractConfig = {
98+
address: string
99+
abi: InterfaceAbi
100+
runner: ContractRunner
101+
contractMethodsToSign: string[]
102+
provider?: Provider
103+
}

packages/ethers-provider-fireblocks/README.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,24 @@ const provider = new SilentDataRollupFireblocksProvider({
7171
const balance = await provider.getBalance('YOUR_ADDRESS')
7272
console.log(balance)
7373

74-
const signer = await provider.getSigner()
75-
const contractAddress = 'YOUR_CONTRACT_ADDRESS'
76-
const abi = [
77-
/* Your contract ABI */
78-
]
79-
const methodsToSign = ['balance'] // Contract read calls that require signing
80-
81-
const contract = new SilentDataRollupContract(
82-
contractAddress,
83-
abi,
84-
signer,
85-
methodsToSign,
86-
)
87-
88-
const tokenBalance = await contract.balance('YOUR_ADDRESS')
89-
console.log(tokenBalance)
74+
const contractConfig = {
75+
contractAddress: 'YOUR_CONTRACT_ADDRESS'
76+
abi: [ /* Your contract ABI */ ],
77+
runner: signer,
78+
methodsToSign: ['method1', 'method2'] // Contract read calls that require signing
79+
}
80+
81+
const contract = new SilentDataRollupContract(contractConfig)
82+
83+
// Now you can call "private" contract methods. These calls will be signed,
84+
// and msg.sender will be available in the contract, representing the signer's address.
85+
const privateMethodResult = await contract.method1('param1', 'param2')
86+
console.log('Private method result:', privateMethodResult)
87+
88+
// You can also call methods that don't require signing.
89+
// These calls won't include a signature, and msg.sender won't be available in the contract.
90+
const publicMethodResult = await contract.method3('param1', 'param2')
91+
console.log('Public method result:', publicMethodResult)
9092
```
9193

9294
**Note:** The SilentDataRollupFireblocksProvider adds an additional namespace on top of the Fireblocks provider for debugging purposes. This namespace is the same as the Fireblocks namespace with an additional `:silentdata-interceptor` suffix. For example, if the Fireblocks namespace is `fireblocks:web3-provider`, the SilentData provider's namespace would be `fireblocks:web3-provider:silentdata-interceptor`.

packages/ethers-provider/README.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,17 @@ const providerConfig = {
8383
}
8484

8585
const provider = new SilentDataRollupProvider(providerConfig)
86+
const balance = await provider.getBalance('YOUR_ADDRESS')
87+
console.log(balance)
88+
89+
const contractConfig = {
90+
contractAddress: 'YOUR_CONTRACT_ADDRESS'
91+
abi: [ /* Your contract ABI */ ],
92+
runner: provider,
93+
methodsToSign: ['method1', 'method2'] // Contract read calls that require signing
94+
}
8695

87-
const contractAddress = 'YOUR_CONTRACT_ADDRESS'
88-
const abi = [
89-
/* Your contract ABI */
90-
]
91-
const methodsToSign = ['method1', 'method2'] // Contract read calls that require signing
92-
93-
const contract = new SilentDataRollupContract(
94-
contractAddress,
95-
abi,
96-
provider,
97-
methodsToSign,
98-
)
96+
const contract = new SilentDataRollupContract(contractConfig)
9997

10098
// Now you can call "private" contract methods. These calls will be signed,
10199
// and msg.sender will be available in the contract, representing the signer's address.

0 commit comments

Comments
 (0)