-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(common): move
createContract
's internal write logic to `wr…
…iteContract` (#1693)
- Loading branch information
Showing
21 changed files
with
328 additions
and
220 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
--- | ||
"@latticexyz/common": minor | ||
--- | ||
|
||
- Moves contract write logic out of `createContract` into its own `writeContract` method so that it can be used outside of the contract instance, and for consistency with viem. | ||
- Deprecates `createContract` in favor of `getContract` for consistency with viem. | ||
- Reworks `createNonceManager`'s `BroadcastChannel` setup and moves out the notion of a "nonce manager ID" to `getNonceManagerId` so we can create an internal cache with `getNonceManager` for use in `writeContract`. | ||
|
||
If you were using the `createNonceManager` before, you'll just need to rename `publicClient` argument to `client`: | ||
|
||
```diff | ||
const publicClient = createPublicClient({ ... }); | ||
- const nonceManager = createNonceManager({ publicClient, ... }); | ||
+ const nonceManager = createNonceManager({ client: publicClient, ... }); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,173 +1,4 @@ | ||
import { | ||
Abi, | ||
Account, | ||
Address, | ||
Chain, | ||
GetContractParameters, | ||
GetContractReturnType, | ||
Hex, | ||
PublicClient, | ||
SimulateContractParameters, | ||
Transport, | ||
WalletClient, | ||
WriteContractParameters, | ||
getContract, | ||
} from "viem"; | ||
import pRetry from "p-retry"; | ||
import { createNonceManager } from "./createNonceManager"; | ||
import { debug as parentDebug } from "./debug"; | ||
import { UnionOmit } from "./type-utils/common"; | ||
import { getContract } from "./getContract"; | ||
|
||
const debug = parentDebug.extend("createContract"); | ||
|
||
// copied from viem because this isn't exported | ||
// TODO: import from viem? | ||
function getFunctionParameters(values: [args?: readonly unknown[], options?: object]): { | ||
args: readonly unknown[]; | ||
options: object; | ||
} { | ||
const hasArgs = values.length && Array.isArray(values[0]); | ||
const args = hasArgs ? values[0]! : []; | ||
const options = (hasArgs ? values[1] : values[0]) ?? {}; | ||
return { args, options }; | ||
} | ||
|
||
export type ContractWrite = { | ||
id: string; | ||
request: WriteContractParameters; | ||
result: Promise<Hex>; | ||
}; | ||
|
||
export type CreateContractOptions< | ||
TTransport extends Transport, | ||
TAddress extends Address, | ||
TAbi extends Abi, | ||
TChain extends Chain, | ||
TAccount extends Account, | ||
TPublicClient extends PublicClient<TTransport, TChain>, | ||
TWalletClient extends WalletClient<TTransport, TChain, TAccount> | ||
> = Required<GetContractParameters<TTransport, TChain, TAccount, TAbi, TPublicClient, TWalletClient, TAddress>> & { | ||
onWrite?: (write: ContractWrite) => void; | ||
}; | ||
|
||
export function createContract< | ||
TTransport extends Transport, | ||
TAddress extends Address, | ||
TAbi extends Abi, | ||
TChain extends Chain, | ||
TAccount extends Account, | ||
TPublicClient extends PublicClient<TTransport, TChain>, | ||
TWalletClient extends WalletClient<TTransport, TChain, TAccount> | ||
>({ | ||
abi, | ||
address, | ||
publicClient, | ||
walletClient, | ||
onWrite, | ||
}: CreateContractOptions< | ||
TTransport, | ||
TAddress, | ||
TAbi, | ||
TChain, | ||
TAccount, | ||
TPublicClient, | ||
TWalletClient | ||
>): GetContractReturnType<TAbi, TPublicClient, TWalletClient, TAddress> { | ||
const contract = getContract<TTransport, TAddress, TAbi, TChain, TAccount, TPublicClient, TWalletClient>({ | ||
abi, | ||
address, | ||
publicClient, | ||
walletClient, | ||
}) as unknown as GetContractReturnType<Abi, PublicClient, WalletClient>; | ||
|
||
if (contract.write) { | ||
let nextWriteId = 0; | ||
const nonceManager = createNonceManager({ | ||
publicClient: publicClient as PublicClient, | ||
address: walletClient.account.address, | ||
}); | ||
|
||
// Replace write calls with our own proxy. Implemented ~the same as viem, but adds better handling of nonces (via queue + retries). | ||
contract.write = new Proxy( | ||
{}, | ||
{ | ||
get(_, functionName: string): GetContractReturnType<Abi, PublicClient, WalletClient>["write"][string] { | ||
async function prepareWrite( | ||
options: WriteContractParameters | ||
): Promise<WriteContractParameters<TAbi, typeof functionName, TChain, TAccount>> { | ||
if (options.gas) { | ||
debug("gas provided, skipping simulate", functionName, options); | ||
return options as unknown as WriteContractParameters<TAbi, typeof functionName, TChain, TAccount>; | ||
} | ||
|
||
debug("simulating write", functionName, options); | ||
const { request } = await publicClient.simulateContract({ | ||
...options, | ||
account: options.account ?? walletClient.account, | ||
} as unknown as SimulateContractParameters<TAbi, typeof functionName, TChain>); | ||
|
||
return request as unknown as WriteContractParameters<TAbi, typeof functionName, TChain, TAccount>; | ||
} | ||
|
||
async function write(options: WriteContractParameters): Promise<Hex> { | ||
const preparedWrite = await prepareWrite(options); | ||
|
||
return await pRetry( | ||
async () => { | ||
if (!nonceManager.hasNonce()) { | ||
await nonceManager.resetNonce(); | ||
} | ||
|
||
const nonce = nonceManager.nextNonce(); | ||
debug("calling write function with nonce", nonce, preparedWrite); | ||
return await walletClient.writeContract({ | ||
nonce, | ||
...preparedWrite, | ||
}); | ||
}, | ||
{ | ||
retries: 3, | ||
onFailedAttempt: async (error) => { | ||
// On nonce errors, reset the nonce and retry | ||
if (nonceManager.shouldResetNonce(error)) { | ||
debug("got nonce error, retrying", error); | ||
await nonceManager.resetNonce(); | ||
return; | ||
} | ||
// TODO: prepareWrite again if there are gas errors? | ||
throw error; | ||
}, | ||
} | ||
); | ||
} | ||
|
||
return (...parameters) => { | ||
const id = `${walletClient.chain.id}:${walletClient.account.address}:${nextWriteId++}`; | ||
const { args, options } = < | ||
{ | ||
args: unknown[]; | ||
options: UnionOmit<WriteContractParameters, "address" | "abi" | "functionName" | "args">; | ||
} | ||
>getFunctionParameters(parameters as any); | ||
|
||
const request = { | ||
address, | ||
abi, | ||
functionName, | ||
args, | ||
...options, | ||
}; | ||
|
||
const result = write(request); | ||
|
||
onWrite?.({ id, request, result }); | ||
|
||
return result; | ||
}; | ||
}, | ||
} | ||
); | ||
} | ||
|
||
return contract as unknown as GetContractReturnType<TAbi, TPublicClient, TWalletClient, TAddress>; | ||
} | ||
/** @deprecated use `getContract` instead */ | ||
export const createContract = getContract; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
d075f82
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
mud-docs – ./
mud-docs-latticexyz.vercel.app
mud-docs.vercel.app
mud-docs-git-main-latticexyz.vercel.app
mud.dev
v2.mud.dev
www.mud.dev