viem 2.55.10 → 2.55.13
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/CHANGELOG.md +36 -0
- package/_cjs/actions/wallet/prepareTransactionRequest.js +12 -12
- package/_cjs/actions/wallet/prepareTransactionRequest.js.map +1 -1
- package/_cjs/actions/wallet/sendTransaction.js +24 -12
- package/_cjs/actions/wallet/sendTransaction.js.map +1 -1
- package/_cjs/actions/wallet/sendTransactionSync.js +24 -12
- package/_cjs/actions/wallet/sendTransactionSync.js.map +1 -1
- package/_cjs/chains/definitions/story.js +7 -7
- package/_cjs/chains/definitions/story.js.map +1 -1
- package/_cjs/chains/definitions/storyAeneid.js +8 -8
- package/_cjs/chains/definitions/storyAeneid.js.map +1 -1
- package/_cjs/clients/createClient.js +1 -0
- package/_cjs/clients/createClient.js.map +1 -1
- package/_cjs/errors/typedData.js +11 -1
- package/_cjs/errors/typedData.js.map +1 -1
- package/_cjs/errors/version.js +1 -1
- package/_cjs/index.js +5 -4
- package/_cjs/index.js.map +1 -1
- package/_cjs/tempo/Abis.js +642 -253
- package/_cjs/tempo/Abis.js.map +1 -1
- package/_cjs/tempo/Decorator.js +7 -0
- package/_cjs/tempo/Decorator.js.map +1 -1
- package/_cjs/tempo/actions/earn/deployment.js +654 -0
- package/_cjs/tempo/actions/earn/deployment.js.map +1 -0
- package/_cjs/tempo/actions/earn.js +60 -17
- package/_cjs/tempo/actions/earn.js.map +1 -1
- package/_cjs/tempo/actions/token.js +18 -6
- package/_cjs/tempo/actions/token.js.map +1 -1
- package/_cjs/tempo/actions/zone.js +11 -1
- package/_cjs/tempo/actions/zone.js.map +1 -1
- package/_cjs/tempo/chainConfig.js +24 -23
- package/_cjs/tempo/chainConfig.js.map +1 -1
- package/_cjs/tempo/internal/WithdrawalSenderTag.js +2 -2
- package/_cjs/tempo/internal/WithdrawalSenderTag.js.map +1 -1
- package/_cjs/tempo/zones/Abis.js +17 -0
- package/_cjs/tempo/zones/Abis.js.map +1 -1
- package/_cjs/tokens/definitions/usdc.js +7 -0
- package/_cjs/tokens/definitions/usdc.js.map +1 -1
- package/_cjs/utils/promise/withRetry.js +2 -2
- package/_cjs/utils/promise/withRetry.js.map +1 -1
- package/_cjs/utils/typedData.js +3 -0
- package/_cjs/utils/typedData.js.map +1 -1
- package/_esm/actions/wallet/prepareTransactionRequest.js +14 -13
- package/_esm/actions/wallet/prepareTransactionRequest.js.map +1 -1
- package/_esm/actions/wallet/sendTransaction.js +24 -12
- package/_esm/actions/wallet/sendTransaction.js.map +1 -1
- package/_esm/actions/wallet/sendTransactionSync.js +24 -12
- package/_esm/actions/wallet/sendTransactionSync.js.map +1 -1
- package/_esm/chains/definitions/story.js +7 -7
- package/_esm/chains/definitions/story.js.map +1 -1
- package/_esm/chains/definitions/storyAeneid.js +8 -8
- package/_esm/chains/definitions/storyAeneid.js.map +1 -1
- package/_esm/clients/createClient.js +4 -3
- package/_esm/clients/createClient.js.map +1 -1
- package/_esm/errors/typedData.js +9 -0
- package/_esm/errors/typedData.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_esm/index.js +1 -1
- package/_esm/index.js.map +1 -1
- package/_esm/tempo/Abis.js +643 -254
- package/_esm/tempo/Abis.js.map +1 -1
- package/_esm/tempo/Decorator.js +7 -0
- package/_esm/tempo/Decorator.js.map +1 -1
- package/_esm/tempo/actions/earn/deployment.js +967 -0
- package/_esm/tempo/actions/earn/deployment.js.map +1 -0
- package/_esm/tempo/actions/earn.js +47 -17
- package/_esm/tempo/actions/earn.js.map +1 -1
- package/_esm/tempo/actions/token.js +21 -8
- package/_esm/tempo/actions/token.js.map +1 -1
- package/_esm/tempo/actions/zone.js +11 -1
- package/_esm/tempo/actions/zone.js.map +1 -1
- package/_esm/tempo/chainConfig.js +26 -28
- package/_esm/tempo/chainConfig.js.map +1 -1
- package/_esm/tempo/internal/WithdrawalSenderTag.js +3 -3
- package/_esm/tempo/internal/WithdrawalSenderTag.js.map +1 -1
- package/_esm/tempo/zones/Abis.js +17 -0
- package/_esm/tempo/zones/Abis.js.map +1 -1
- package/_esm/tokens/definitions/usdc.js +7 -0
- package/_esm/tokens/definitions/usdc.js.map +1 -1
- package/_esm/utils/promise/withRetry.js +2 -2
- package/_esm/utils/promise/withRetry.js.map +1 -1
- package/_esm/utils/typedData.js +4 -1
- package/_esm/utils/typedData.js.map +1 -1
- package/_types/actions/wallet/prepareTransactionRequest.d.ts.map +1 -1
- package/_types/actions/wallet/sendTransaction.d.ts.map +1 -1
- package/_types/actions/wallet/sendTransactionSync.d.ts.map +1 -1
- package/_types/chains/definitions/story.d.ts +7 -7
- package/_types/chains/definitions/storyAeneid.d.ts +8 -8
- package/_types/clients/createClient.d.ts +3 -3
- package/_types/clients/createClient.d.ts.map +1 -1
- package/_types/errors/typedData.d.ts +8 -0
- package/_types/errors/typedData.d.ts.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/_types/index.d.ts +1 -1
- package/_types/index.d.ts.map +1 -1
- package/_types/tempo/Abis.d.ts +945 -388
- package/_types/tempo/Abis.d.ts.map +1 -1
- package/_types/tempo/Decorator.d.ts +18 -0
- package/_types/tempo/Decorator.d.ts.map +1 -1
- package/_types/tempo/actions/earn/deployment.d.ts +1710 -0
- package/_types/tempo/actions/earn/deployment.d.ts.map +1 -0
- package/_types/tempo/actions/earn.d.ts +1142 -535
- package/_types/tempo/actions/earn.d.ts.map +1 -1
- package/_types/tempo/actions/token.d.ts +1 -1
- package/_types/tempo/actions/token.d.ts.map +1 -1
- package/_types/tempo/actions/zone.d.ts.map +1 -1
- package/_types/tempo/chainConfig.d.ts.map +1 -1
- package/_types/tempo/internal/WithdrawalSenderTag.d.ts +2 -1
- package/_types/tempo/internal/WithdrawalSenderTag.d.ts.map +1 -1
- package/_types/tempo/zones/Abis.d.ts +48 -0
- package/_types/tempo/zones/Abis.d.ts.map +1 -1
- package/_types/tokens/definitions/usdc.d.ts +7 -0
- package/_types/tokens/definitions/usdc.d.ts.map +1 -1
- package/_types/tokens/sets.d.ts +14 -0
- package/_types/tokens/sets.d.ts.map +1 -1
- package/_types/utils/typedData.d.ts.map +1 -1
- package/actions/wallet/prepareTransactionRequest.ts +18 -17
- package/actions/wallet/sendTransaction.ts +25 -10
- package/actions/wallet/sendTransactionSync.ts +25 -10
- package/chains/definitions/story.ts +7 -7
- package/chains/definitions/storyAeneid.ts +8 -8
- package/clients/createClient.ts +4 -3
- package/errors/typedData.ts +13 -0
- package/errors/version.ts +1 -1
- package/index.ts +2 -0
- package/package.json +1 -1
- package/tempo/Abis.ts +644 -254
- package/tempo/Decorator.ts +41 -0
- package/tempo/actions/earn/deployment.ts +1427 -0
- package/tempo/actions/earn.ts +83 -63
- package/tempo/actions/token.ts +60 -9
- package/tempo/actions/zone.ts +10 -1
- package/tempo/chainConfig.ts +30 -27
- package/tempo/internal/WithdrawalSenderTag.ts +5 -4
- package/tempo/zones/Abis.ts +17 -0
- package/tokens/definitions/usdc.ts +7 -0
- package/utils/promise/withRetry.ts +4 -4
- package/utils/typedData.ts +5 -0
|
@@ -0,0 +1,1427 @@
|
|
|
1
|
+
import type { Address } from 'abitype'
|
|
2
|
+
import { Hex } from 'ox'
|
|
3
|
+
import type { Account } from '../../../accounts/types.js'
|
|
4
|
+
import { parseAccount } from '../../../accounts/utils/parseAccount.js'
|
|
5
|
+
import { getCode } from '../../../actions/public/getCode.js'
|
|
6
|
+
import { getLogs } from '../../../actions/public/getLogs.js'
|
|
7
|
+
import { readContract } from '../../../actions/public/readContract.js'
|
|
8
|
+
import { simulateContract } from '../../../actions/public/simulateContract.js'
|
|
9
|
+
import {
|
|
10
|
+
type WriteContractReturnType,
|
|
11
|
+
writeContract,
|
|
12
|
+
} from '../../../actions/wallet/writeContract.js'
|
|
13
|
+
import { writeContractSync } from '../../../actions/wallet/writeContractSync.js'
|
|
14
|
+
import type { Client } from '../../../clients/createClient.js'
|
|
15
|
+
import type { Transport } from '../../../clients/transports/createTransport.js'
|
|
16
|
+
import { zeroAddress } from '../../../constants/address.js'
|
|
17
|
+
import { maxUint64, maxUint256 } from '../../../constants/number.js'
|
|
18
|
+
import { AccountNotFoundError } from '../../../errors/account.js'
|
|
19
|
+
import { BaseError, type BaseErrorType } from '../../../errors/base.js'
|
|
20
|
+
import { TransactionReceiptRevertedError } from '../../../errors/transaction.js'
|
|
21
|
+
import type { Chain } from '../../../types/chain.js'
|
|
22
|
+
import type { GetEventArgs } from '../../../types/contract.js'
|
|
23
|
+
import type { Log } from '../../../types/log.js'
|
|
24
|
+
import type { Compute } from '../../../types/utils.js'
|
|
25
|
+
import { getAbiItem } from '../../../utils/abi/getAbiItem.js'
|
|
26
|
+
import { parseEventLogs } from '../../../utils/abi/parseEventLogs.js'
|
|
27
|
+
import { isAddressEqual } from '../../../utils/address/isAddressEqual.js'
|
|
28
|
+
import * as Abis from '../../Abis.js'
|
|
29
|
+
import * as Addresses from '../../Addresses.js'
|
|
30
|
+
import type {
|
|
31
|
+
WriteParameters,
|
|
32
|
+
WriteSyncParameters,
|
|
33
|
+
} from '../../internal/types.js'
|
|
34
|
+
import {
|
|
35
|
+
defineCall,
|
|
36
|
+
pickWriteParameters,
|
|
37
|
+
pickWriteSyncParameters,
|
|
38
|
+
} from '../../internal/utils.js'
|
|
39
|
+
import type { TransactionReceipt } from '../../Transaction.js'
|
|
40
|
+
|
|
41
|
+
/** @experimental Factory addresses for one reviewed Tempo Earn release. */
|
|
42
|
+
export type EarnFactoryAddresses = {
|
|
43
|
+
/** `ERC4626EngineFactory` address. */
|
|
44
|
+
erc4626Engine: Address
|
|
45
|
+
/** `EarnFactory` address from the same release. */
|
|
46
|
+
earn: Address
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @experimental Deployment-fixed engine migration policy. */
|
|
50
|
+
export type EngineMigrationMode = 'operatorEnabled' | 'userOnly'
|
|
51
|
+
|
|
52
|
+
/** @experimental Initial controls for an Earn vault. */
|
|
53
|
+
export type EarnVaultControls = {
|
|
54
|
+
/** Request-cancellation liveness seat. @default zero address */
|
|
55
|
+
asyncJanitor?: Address | undefined
|
|
56
|
+
/** Fast pause-only seat. @default zero address */
|
|
57
|
+
emergencyGuardian?: Address | undefined
|
|
58
|
+
/** Maximum actively managed assets. Zero means unlimited. @default 0 */
|
|
59
|
+
maxManagedAssets?: bigint | undefined
|
|
60
|
+
/** Whole-pool migration policy. @default 'userOnly' */
|
|
61
|
+
migrationMode?: EngineMigrationMode | undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @experimental Optional protected fee-distributor configuration. */
|
|
65
|
+
export type EarnDistributorConfiguration = {
|
|
66
|
+
/**
|
|
67
|
+
* Distributor address. The first entry in `fees.fixedFees` is its protected
|
|
68
|
+
* fee.
|
|
69
|
+
*/
|
|
70
|
+
distributor: Address
|
|
71
|
+
/** Delay before a distributor fee update can execute, in seconds. */
|
|
72
|
+
updateDelay: number
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @experimental Initial Earn fee configuration. Omit to deploy fee-free. */
|
|
76
|
+
export type EarnFeeConfiguration = {
|
|
77
|
+
/** Optional fee on returns above an annual target. */
|
|
78
|
+
excess?:
|
|
79
|
+
| {
|
|
80
|
+
/** Fee recipient. */
|
|
81
|
+
account: Address
|
|
82
|
+
/** Annual target rate in basis points. */
|
|
83
|
+
annualTargetRateBps: number
|
|
84
|
+
/** Rate charged above the target in basis points. */
|
|
85
|
+
rateBps: number
|
|
86
|
+
}
|
|
87
|
+
| undefined
|
|
88
|
+
/**
|
|
89
|
+
* Fixed fee recipients and rates, limited to four entries. When a
|
|
90
|
+
* distributor is enabled, the first entry is its protected fee and the
|
|
91
|
+
* remaining entries are operator-controlled.
|
|
92
|
+
*/
|
|
93
|
+
fixedFees?:
|
|
94
|
+
| readonly {
|
|
95
|
+
/** Fee recipient. */
|
|
96
|
+
account: Address
|
|
97
|
+
/** Fee rate in basis points. */
|
|
98
|
+
rateBps: number
|
|
99
|
+
}[]
|
|
100
|
+
| undefined
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Deploys a deterministic ERC-4626 Earn engine.
|
|
105
|
+
*
|
|
106
|
+
* @experimental
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* import { createClient, http } from 'viem'
|
|
111
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
112
|
+
* import { tempoModerato } from 'viem/chains'
|
|
113
|
+
* import { Actions } from 'viem/tempo'
|
|
114
|
+
*
|
|
115
|
+
* const client = createClient({
|
|
116
|
+
* account: privateKeyToAccount('0x...'),
|
|
117
|
+
* chain: tempoModerato,
|
|
118
|
+
* transport: http(),
|
|
119
|
+
* })
|
|
120
|
+
*
|
|
121
|
+
* const hash = await Actions.earn.createErc4626Engine(client, {
|
|
122
|
+
* deploymentId: '0x...',
|
|
123
|
+
* factory: '0x...',
|
|
124
|
+
* venue: '0x...',
|
|
125
|
+
* })
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @param client - Client.
|
|
129
|
+
* @param parameters - Parameters.
|
|
130
|
+
* @returns The transaction hash.
|
|
131
|
+
*/
|
|
132
|
+
export async function createErc4626Engine<
|
|
133
|
+
chain extends Chain | undefined,
|
|
134
|
+
account extends Account | undefined,
|
|
135
|
+
>(
|
|
136
|
+
client: Client<Transport, chain, account>,
|
|
137
|
+
parameters: createErc4626Engine.Parameters<chain, account>,
|
|
138
|
+
): Promise<createErc4626Engine.ReturnValue> {
|
|
139
|
+
return createErc4626Engine.inner(writeContract, client, parameters)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export namespace createErc4626Engine {
|
|
143
|
+
export type Args = {
|
|
144
|
+
/** Stable deterministic deployment identifier. */
|
|
145
|
+
deploymentId: Hex.Hex
|
|
146
|
+
/** Reviewed `ERC4626EngineFactory` address. */
|
|
147
|
+
factory: Address
|
|
148
|
+
/** Optional engine name override. Empty derives the venue name. */
|
|
149
|
+
name?: string | undefined
|
|
150
|
+
/** Final engine owner. */
|
|
151
|
+
owner: Address
|
|
152
|
+
/** Optional engine symbol override. Empty derives the venue symbol. */
|
|
153
|
+
symbol?: string | undefined
|
|
154
|
+
/** ERC-4626 venue address. */
|
|
155
|
+
venue: Address
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type Parameters<
|
|
159
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
160
|
+
account extends Account | undefined = Account | undefined,
|
|
161
|
+
> = WriteParameters<chain, account> &
|
|
162
|
+
Omit<Args, 'owner'> & {
|
|
163
|
+
/** Final engine owner. @default `account.address` */
|
|
164
|
+
owner?: Account | Address | undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export type ReturnValue = WriteContractReturnType
|
|
168
|
+
|
|
169
|
+
// TODO: exhaustive error type
|
|
170
|
+
export type ErrorType = BaseErrorType
|
|
171
|
+
|
|
172
|
+
/** @internal */
|
|
173
|
+
export async function inner<
|
|
174
|
+
action extends typeof writeContract | typeof writeContractSync,
|
|
175
|
+
chain extends Chain | undefined,
|
|
176
|
+
account extends Account | undefined,
|
|
177
|
+
>(
|
|
178
|
+
action: action,
|
|
179
|
+
client: Client<Transport, chain, account>,
|
|
180
|
+
parameters: Parameters<chain, account>,
|
|
181
|
+
): Promise<ReturnType<action>> {
|
|
182
|
+
const {
|
|
183
|
+
account = client.account,
|
|
184
|
+
chain = client.chain,
|
|
185
|
+
deploymentId,
|
|
186
|
+
factory,
|
|
187
|
+
name,
|
|
188
|
+
owner: owner_ = account,
|
|
189
|
+
symbol,
|
|
190
|
+
venue,
|
|
191
|
+
...rest
|
|
192
|
+
} = parameters
|
|
193
|
+
if (!account) throw new AccountNotFoundError()
|
|
194
|
+
if (!owner_) throw new Error('`owner` is required.')
|
|
195
|
+
const owner = parseAccount(owner_).address
|
|
196
|
+
return (await action(client, {
|
|
197
|
+
...rest,
|
|
198
|
+
account,
|
|
199
|
+
chain,
|
|
200
|
+
...createErc4626Engine.call({
|
|
201
|
+
deploymentId,
|
|
202
|
+
factory,
|
|
203
|
+
name,
|
|
204
|
+
owner,
|
|
205
|
+
symbol,
|
|
206
|
+
venue,
|
|
207
|
+
}),
|
|
208
|
+
} as never)) as never
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Defines a call to `ERC4626EngineFactory.deploy`.
|
|
213
|
+
*
|
|
214
|
+
* Can be passed as a parameter to:
|
|
215
|
+
* - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
|
|
216
|
+
* - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
|
|
217
|
+
* - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* import { createClient, http, walletActions } from 'viem'
|
|
222
|
+
* import { tempoModerato } from 'viem/chains'
|
|
223
|
+
* import { Actions } from 'viem/tempo'
|
|
224
|
+
*
|
|
225
|
+
* const client = createClient({ chain: tempoModerato, transport: http() })
|
|
226
|
+
* .extend(walletActions)
|
|
227
|
+
* await client.sendTransaction({
|
|
228
|
+
* calls: [Actions.earn.createErc4626Engine.call({
|
|
229
|
+
* deploymentId: '0x...',
|
|
230
|
+
* factory: '0x...',
|
|
231
|
+
* owner: '0x...',
|
|
232
|
+
* venue: '0x...',
|
|
233
|
+
* })],
|
|
234
|
+
* })
|
|
235
|
+
* ```
|
|
236
|
+
*
|
|
237
|
+
* @param args - Arguments.
|
|
238
|
+
* @returns The call.
|
|
239
|
+
*/
|
|
240
|
+
export function call(args: Args) {
|
|
241
|
+
validateDeploymentId(args.deploymentId)
|
|
242
|
+
return defineCall({
|
|
243
|
+
address: args.factory,
|
|
244
|
+
abi: Abis.erc4626EngineFactory,
|
|
245
|
+
functionName: 'deploy',
|
|
246
|
+
args: [
|
|
247
|
+
args.deploymentId,
|
|
248
|
+
args.venue,
|
|
249
|
+
args.owner,
|
|
250
|
+
args.name ?? '',
|
|
251
|
+
args.symbol ?? '',
|
|
252
|
+
],
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Predicts the deterministic engine address.
|
|
258
|
+
*
|
|
259
|
+
* @param client - Client.
|
|
260
|
+
* @param args - Engine deployment arguments.
|
|
261
|
+
* @returns The predicted engine address.
|
|
262
|
+
*/
|
|
263
|
+
export async function predict<chain extends Chain | undefined>(
|
|
264
|
+
client: Client<Transport, chain>,
|
|
265
|
+
args: Args,
|
|
266
|
+
) {
|
|
267
|
+
validateDeploymentId(args.deploymentId)
|
|
268
|
+
return readContract(client, {
|
|
269
|
+
address: args.factory,
|
|
270
|
+
abi: Abis.erc4626EngineFactory,
|
|
271
|
+
functionName: 'predictEngine',
|
|
272
|
+
args: [
|
|
273
|
+
args.deploymentId,
|
|
274
|
+
args.venue,
|
|
275
|
+
args.owner,
|
|
276
|
+
args.name ?? '',
|
|
277
|
+
args.symbol ?? '',
|
|
278
|
+
],
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Extracts the `ERC4626EngineDeployed` event from factory logs.
|
|
284
|
+
*
|
|
285
|
+
* @param logs - The logs.
|
|
286
|
+
* @param parameters - Factory address used to filter the logs.
|
|
287
|
+
* @returns The deployment event.
|
|
288
|
+
*/
|
|
289
|
+
export function extractEvent(logs: Log[], parameters: { factory: Address }) {
|
|
290
|
+
const [log] = parseEventLogs({
|
|
291
|
+
abi: Abis.erc4626EngineFactory,
|
|
292
|
+
eventName: 'ERC4626EngineDeployed',
|
|
293
|
+
logs: logs.filter((log) =>
|
|
294
|
+
isAddressEqual(log.address, parameters.factory),
|
|
295
|
+
),
|
|
296
|
+
strict: true,
|
|
297
|
+
})
|
|
298
|
+
if (!log) throw new Error('`ERC4626EngineDeployed` event not found.')
|
|
299
|
+
return log
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Deploys an ERC-4626 engine and waits for confirmation.
|
|
305
|
+
*
|
|
306
|
+
* @experimental
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* import { createClient, http } from 'viem'
|
|
311
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
312
|
+
* import { tempoModerato } from 'viem/chains'
|
|
313
|
+
* import { Actions } from 'viem/tempo'
|
|
314
|
+
*
|
|
315
|
+
* const client = createClient({
|
|
316
|
+
* account: privateKeyToAccount('0x...'),
|
|
317
|
+
* chain: tempoModerato,
|
|
318
|
+
* transport: http(),
|
|
319
|
+
* })
|
|
320
|
+
* const result = await Actions.earn.createErc4626EngineSync(client, {
|
|
321
|
+
* deploymentId: '0x...',
|
|
322
|
+
* factory: '0x...',
|
|
323
|
+
* venue: '0x...',
|
|
324
|
+
* })
|
|
325
|
+
* ```
|
|
326
|
+
*
|
|
327
|
+
* @param client - Client.
|
|
328
|
+
* @param parameters - Parameters.
|
|
329
|
+
* @returns The receipt and deployed engine metadata.
|
|
330
|
+
*/
|
|
331
|
+
export async function createErc4626EngineSync<
|
|
332
|
+
chain extends Chain | undefined,
|
|
333
|
+
account extends Account | undefined,
|
|
334
|
+
>(
|
|
335
|
+
client: Client<Transport, chain, account>,
|
|
336
|
+
parameters: createErc4626EngineSync.Parameters<chain, account>,
|
|
337
|
+
): Promise<createErc4626EngineSync.ReturnValue> {
|
|
338
|
+
const { factory, throwOnReceiptRevert = true } = parameters
|
|
339
|
+
const receipt = await createErc4626Engine.inner(writeContractSync, client, {
|
|
340
|
+
...parameters,
|
|
341
|
+
throwOnReceiptRevert,
|
|
342
|
+
} as never)
|
|
343
|
+
const { args } = createErc4626Engine.extractEvent(receipt.logs, { factory })
|
|
344
|
+
return { ...args, receipt }
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export namespace createErc4626EngineSync {
|
|
348
|
+
export type Args = createErc4626Engine.Args
|
|
349
|
+
export type Parameters<
|
|
350
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
351
|
+
account extends Account | undefined = Account | undefined,
|
|
352
|
+
> = createErc4626Engine.Parameters<chain, account> &
|
|
353
|
+
WriteSyncParameters<chain, account>
|
|
354
|
+
export type ReturnValue = Compute<
|
|
355
|
+
GetEventArgs<
|
|
356
|
+
typeof Abis.erc4626EngineFactory,
|
|
357
|
+
'ERC4626EngineDeployed',
|
|
358
|
+
{ IndexedOnly: false; Required: true }
|
|
359
|
+
> & { receipt: TransactionReceipt }
|
|
360
|
+
>
|
|
361
|
+
// TODO: exhaustive error type
|
|
362
|
+
export type ErrorType = BaseErrorType
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Creates an EarnShare, EarnVault, and EarnFees stack around an unbound engine.
|
|
367
|
+
*
|
|
368
|
+
* @experimental
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* import { createClient, http } from 'viem'
|
|
373
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
374
|
+
* import { tempoModerato } from 'viem/chains'
|
|
375
|
+
* import { Actions } from 'viem/tempo'
|
|
376
|
+
*
|
|
377
|
+
* const client = createClient({
|
|
378
|
+
* account: privateKeyToAccount('0x...'),
|
|
379
|
+
* chain: tempoModerato,
|
|
380
|
+
* transport: http(),
|
|
381
|
+
* })
|
|
382
|
+
* const hash = await Actions.earn.createStack(client, {
|
|
383
|
+
* deploymentId: '0x...',
|
|
384
|
+
* engine: '0x...',
|
|
385
|
+
* factory: '0x...',
|
|
386
|
+
* })
|
|
387
|
+
* ```
|
|
388
|
+
*
|
|
389
|
+
* @param client - Client.
|
|
390
|
+
* @param parameters - Parameters.
|
|
391
|
+
* @returns The transaction hash.
|
|
392
|
+
*/
|
|
393
|
+
export async function createStack<
|
|
394
|
+
chain extends Chain | undefined,
|
|
395
|
+
account extends Account | undefined,
|
|
396
|
+
>(
|
|
397
|
+
client: Client<Transport, chain, account>,
|
|
398
|
+
parameters: createStack.Parameters<chain, account>,
|
|
399
|
+
): Promise<createStack.ReturnValue> {
|
|
400
|
+
return createStack.inner(writeContract, client, parameters)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export namespace createStack {
|
|
404
|
+
export type Args = {
|
|
405
|
+
/** Initial Earn vault controls. */
|
|
406
|
+
controls?: EarnVaultControls | undefined
|
|
407
|
+
/** Stable deterministic deployment identifier. */
|
|
408
|
+
deploymentId: Hex.Hex
|
|
409
|
+
/** Optional protected fee distributor. */
|
|
410
|
+
distributor?: EarnDistributorConfiguration | undefined
|
|
411
|
+
/** Engine address. */
|
|
412
|
+
engine: Address
|
|
413
|
+
/** Reviewed `EarnFactory` address. */
|
|
414
|
+
factory: Address
|
|
415
|
+
/** Initial fee configuration. Omit for fee-free deployment. */
|
|
416
|
+
fees?: EarnFeeConfiguration | undefined
|
|
417
|
+
/** Final stack owner and operator. */
|
|
418
|
+
owner: Address
|
|
419
|
+
/** Existing simple whitelist policy. Zero selects always-allow. @default 0 */
|
|
420
|
+
transferPolicyId?: bigint | undefined
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export type Parameters<
|
|
424
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
425
|
+
account extends Account | undefined = Account | undefined,
|
|
426
|
+
> = WriteParameters<chain, account> &
|
|
427
|
+
Omit<Args, 'owner'> & {
|
|
428
|
+
/** Final stack owner and operator. @default `account.address` */
|
|
429
|
+
owner?: Account | Address | undefined
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export type ReturnValue = WriteContractReturnType
|
|
433
|
+
|
|
434
|
+
// TODO: exhaustive error type
|
|
435
|
+
export type ErrorType = BaseErrorType
|
|
436
|
+
|
|
437
|
+
/** @internal */
|
|
438
|
+
export async function inner<
|
|
439
|
+
action extends typeof writeContract | typeof writeContractSync,
|
|
440
|
+
chain extends Chain | undefined,
|
|
441
|
+
account extends Account | undefined,
|
|
442
|
+
>(
|
|
443
|
+
action: action,
|
|
444
|
+
client: Client<Transport, chain, account>,
|
|
445
|
+
parameters: Parameters<chain, account>,
|
|
446
|
+
): Promise<ReturnType<action>> {
|
|
447
|
+
const {
|
|
448
|
+
account = client.account,
|
|
449
|
+
chain = client.chain,
|
|
450
|
+
controls,
|
|
451
|
+
deploymentId,
|
|
452
|
+
distributor,
|
|
453
|
+
engine,
|
|
454
|
+
factory,
|
|
455
|
+
fees,
|
|
456
|
+
owner: owner_ = account,
|
|
457
|
+
transferPolicyId,
|
|
458
|
+
...rest
|
|
459
|
+
} = parameters
|
|
460
|
+
if (!account) throw new AccountNotFoundError()
|
|
461
|
+
if (!owner_) throw new Error('`owner` is required.')
|
|
462
|
+
return (await action(client, {
|
|
463
|
+
...rest,
|
|
464
|
+
account,
|
|
465
|
+
chain,
|
|
466
|
+
...createStack.call({
|
|
467
|
+
controls,
|
|
468
|
+
deploymentId,
|
|
469
|
+
distributor,
|
|
470
|
+
engine,
|
|
471
|
+
factory,
|
|
472
|
+
fees,
|
|
473
|
+
owner: parseAccount(owner_).address,
|
|
474
|
+
transferPolicyId,
|
|
475
|
+
}),
|
|
476
|
+
} as never)) as never
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Defines a call to `EarnFactory.deploy`.
|
|
481
|
+
*
|
|
482
|
+
* Can be passed as a parameter to:
|
|
483
|
+
* - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
|
|
484
|
+
* - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
|
|
485
|
+
* - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* ```ts
|
|
489
|
+
* import { createClient, http, walletActions } from 'viem'
|
|
490
|
+
* import { tempoModerato } from 'viem/chains'
|
|
491
|
+
* import { Actions } from 'viem/tempo'
|
|
492
|
+
*
|
|
493
|
+
* const client = createClient({ chain: tempoModerato, transport: http() })
|
|
494
|
+
* .extend(walletActions)
|
|
495
|
+
* await client.sendTransaction({
|
|
496
|
+
* calls: [Actions.earn.createStack.call({
|
|
497
|
+
* deploymentId: '0x...',
|
|
498
|
+
* engine: '0x...',
|
|
499
|
+
* factory: '0x...',
|
|
500
|
+
* owner: '0x...',
|
|
501
|
+
* })],
|
|
502
|
+
* })
|
|
503
|
+
* ```
|
|
504
|
+
*
|
|
505
|
+
* @param args - Arguments.
|
|
506
|
+
* @returns The call.
|
|
507
|
+
*/
|
|
508
|
+
export function call(args: Args) {
|
|
509
|
+
validateDeploymentId(args.deploymentId)
|
|
510
|
+
return defineCall({
|
|
511
|
+
address: args.factory,
|
|
512
|
+
abi: Abis.earnFactory,
|
|
513
|
+
functionName: 'deploy',
|
|
514
|
+
args: [toDeployParameters(args)],
|
|
515
|
+
})
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Predicts the deterministic EarnShare and EarnFees addresses.
|
|
520
|
+
*
|
|
521
|
+
* @param client - Client.
|
|
522
|
+
* @param args - Stack deployment arguments.
|
|
523
|
+
* @returns The predicted EarnShare and EarnFees addresses.
|
|
524
|
+
*/
|
|
525
|
+
export async function predict<chain extends Chain | undefined>(
|
|
526
|
+
client: Client<Transport, chain>,
|
|
527
|
+
args: Args,
|
|
528
|
+
) {
|
|
529
|
+
validateDeploymentId(args.deploymentId)
|
|
530
|
+
const parameters = toDeployParameters(args)
|
|
531
|
+
const [earnShare, earnFees] = await Promise.all([
|
|
532
|
+
readContract(client, {
|
|
533
|
+
address: args.factory,
|
|
534
|
+
abi: Abis.earnFactory,
|
|
535
|
+
functionName: 'predictEarnShare',
|
|
536
|
+
args: [parameters],
|
|
537
|
+
}),
|
|
538
|
+
readContract(client, {
|
|
539
|
+
address: args.factory,
|
|
540
|
+
abi: Abis.earnFactory,
|
|
541
|
+
functionName: 'predictEarnFees',
|
|
542
|
+
args: [parameters],
|
|
543
|
+
}),
|
|
544
|
+
])
|
|
545
|
+
return { earnFees, earnShare }
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Extracts the `EarnStackDeployed` event from factory logs.
|
|
550
|
+
*
|
|
551
|
+
* @param logs - The logs.
|
|
552
|
+
* @param parameters - Factory address used to filter the logs.
|
|
553
|
+
* @returns The deployment event.
|
|
554
|
+
*/
|
|
555
|
+
export function extractEvent(logs: Log[], parameters: { factory: Address }) {
|
|
556
|
+
const [log] = parseEventLogs({
|
|
557
|
+
abi: Abis.earnFactory,
|
|
558
|
+
eventName: 'EarnStackDeployed',
|
|
559
|
+
logs: logs.filter((log) =>
|
|
560
|
+
isAddressEqual(log.address, parameters.factory),
|
|
561
|
+
),
|
|
562
|
+
strict: true,
|
|
563
|
+
})
|
|
564
|
+
if (!log) throw new Error('`EarnStackDeployed` event not found.')
|
|
565
|
+
return log
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Creates an Earn core stack and waits for confirmation.
|
|
571
|
+
*
|
|
572
|
+
* @experimental
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* ```ts
|
|
576
|
+
* import { createClient, http } from 'viem'
|
|
577
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
578
|
+
* import { tempoModerato } from 'viem/chains'
|
|
579
|
+
* import { Actions } from 'viem/tempo'
|
|
580
|
+
*
|
|
581
|
+
* const client = createClient({
|
|
582
|
+
* account: privateKeyToAccount('0x...'),
|
|
583
|
+
* chain: tempoModerato,
|
|
584
|
+
* transport: http(),
|
|
585
|
+
* })
|
|
586
|
+
* const result = await Actions.earn.createStackSync(client, {
|
|
587
|
+
* deploymentId: '0x...',
|
|
588
|
+
* engine: '0x...',
|
|
589
|
+
* factory: '0x...',
|
|
590
|
+
* })
|
|
591
|
+
* ```
|
|
592
|
+
*
|
|
593
|
+
* @param client - Client.
|
|
594
|
+
* @param parameters - Parameters.
|
|
595
|
+
* @returns The receipt and deployed stack addresses.
|
|
596
|
+
*/
|
|
597
|
+
export async function createStackSync<
|
|
598
|
+
chain extends Chain | undefined,
|
|
599
|
+
account extends Account | undefined,
|
|
600
|
+
>(
|
|
601
|
+
client: Client<Transport, chain, account>,
|
|
602
|
+
parameters: createStackSync.Parameters<chain, account>,
|
|
603
|
+
): Promise<createStackSync.ReturnValue> {
|
|
604
|
+
const { factory, throwOnReceiptRevert = true } = parameters
|
|
605
|
+
const receipt = await createStack.inner(writeContractSync, client, {
|
|
606
|
+
...parameters,
|
|
607
|
+
throwOnReceiptRevert,
|
|
608
|
+
} as never)
|
|
609
|
+
const { args } = createStack.extractEvent(receipt.logs, { factory })
|
|
610
|
+
return { ...args, receipt }
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export namespace createStackSync {
|
|
614
|
+
export type Args = createStack.Args
|
|
615
|
+
export type Parameters<
|
|
616
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
617
|
+
account extends Account | undefined = Account | undefined,
|
|
618
|
+
> = createStack.Parameters<chain, account> &
|
|
619
|
+
WriteSyncParameters<chain, account>
|
|
620
|
+
export type ReturnValue = Compute<
|
|
621
|
+
GetEventArgs<
|
|
622
|
+
typeof Abis.earnFactory,
|
|
623
|
+
'EarnStackDeployed',
|
|
624
|
+
{ IndexedOnly: false; Required: true }
|
|
625
|
+
> & { receipt: TransactionReceipt }
|
|
626
|
+
>
|
|
627
|
+
// TODO: exhaustive error type
|
|
628
|
+
export type ErrorType = BaseErrorType
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Permanently binds an ERC-4626 engine to its EarnVault.
|
|
633
|
+
*
|
|
634
|
+
* @experimental
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* ```ts
|
|
638
|
+
* import { createClient, http } from 'viem'
|
|
639
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
640
|
+
* import { tempoModerato } from 'viem/chains'
|
|
641
|
+
* import { Actions } from 'viem/tempo'
|
|
642
|
+
*
|
|
643
|
+
* const client = createClient({
|
|
644
|
+
* account: privateKeyToAccount('0x...'),
|
|
645
|
+
* chain: tempoModerato,
|
|
646
|
+
* transport: http(),
|
|
647
|
+
* })
|
|
648
|
+
* const hash = await Actions.earn.bindErc4626Engine(client, {
|
|
649
|
+
* engine: '0x...',
|
|
650
|
+
* vault: '0x...',
|
|
651
|
+
* })
|
|
652
|
+
* ```
|
|
653
|
+
*
|
|
654
|
+
* @param client - Client controlled by the final engine owner.
|
|
655
|
+
* @param parameters - Parameters.
|
|
656
|
+
* @returns The transaction hash.
|
|
657
|
+
*/
|
|
658
|
+
export async function bindErc4626Engine<
|
|
659
|
+
chain extends Chain | undefined,
|
|
660
|
+
account extends Account | undefined,
|
|
661
|
+
>(
|
|
662
|
+
client: Client<Transport, chain, account>,
|
|
663
|
+
parameters: bindErc4626Engine.Parameters<chain, account>,
|
|
664
|
+
): Promise<bindErc4626Engine.ReturnValue> {
|
|
665
|
+
return bindErc4626Engine.inner(writeContract, client, parameters)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export namespace bindErc4626Engine {
|
|
669
|
+
export type Args = {
|
|
670
|
+
/** ERC-4626 engine address. */
|
|
671
|
+
engine: Address
|
|
672
|
+
/** Factory-created EarnVault address. */
|
|
673
|
+
vault: Address
|
|
674
|
+
}
|
|
675
|
+
export type Parameters<
|
|
676
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
677
|
+
account extends Account | undefined = Account | undefined,
|
|
678
|
+
> = WriteParameters<chain, account> & Args
|
|
679
|
+
export type ReturnValue = WriteContractReturnType
|
|
680
|
+
// TODO: exhaustive error type
|
|
681
|
+
export type ErrorType = BaseErrorType
|
|
682
|
+
|
|
683
|
+
/** @internal */
|
|
684
|
+
export async function inner<
|
|
685
|
+
action extends typeof writeContract | typeof writeContractSync,
|
|
686
|
+
chain extends Chain | undefined,
|
|
687
|
+
account extends Account | undefined,
|
|
688
|
+
>(
|
|
689
|
+
action: action,
|
|
690
|
+
client: Client<Transport, chain, account>,
|
|
691
|
+
parameters: Parameters<chain, account>,
|
|
692
|
+
): Promise<ReturnType<action>> {
|
|
693
|
+
const { engine, vault, ...rest } = parameters
|
|
694
|
+
return (await action(client, {
|
|
695
|
+
...rest,
|
|
696
|
+
...bindErc4626Engine.call({ engine, vault }),
|
|
697
|
+
} as never)) as never
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Defines a call to `ERC4626Engine.initializeEarnVault`.
|
|
702
|
+
*
|
|
703
|
+
* Can be passed as a parameter to:
|
|
704
|
+
* - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
|
|
705
|
+
* - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
|
|
706
|
+
* - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls
|
|
707
|
+
*
|
|
708
|
+
* @example
|
|
709
|
+
* ```ts
|
|
710
|
+
* import { createClient, http, walletActions } from 'viem'
|
|
711
|
+
* import { tempoModerato } from 'viem/chains'
|
|
712
|
+
* import { Actions } from 'viem/tempo'
|
|
713
|
+
*
|
|
714
|
+
* const client = createClient({ chain: tempoModerato, transport: http() })
|
|
715
|
+
* .extend(walletActions)
|
|
716
|
+
* await client.sendTransaction({
|
|
717
|
+
* calls: [Actions.earn.bindErc4626Engine.call({
|
|
718
|
+
* engine: '0x...',
|
|
719
|
+
* vault: '0x...',
|
|
720
|
+
* })],
|
|
721
|
+
* })
|
|
722
|
+
* ```
|
|
723
|
+
*
|
|
724
|
+
* @param args - Arguments.
|
|
725
|
+
* @returns The call.
|
|
726
|
+
*/
|
|
727
|
+
export function call(args: Args) {
|
|
728
|
+
return defineCall({
|
|
729
|
+
address: args.engine,
|
|
730
|
+
abi: Abis.erc4626Engine,
|
|
731
|
+
functionName: 'initializeEarnVault',
|
|
732
|
+
args: [args.vault],
|
|
733
|
+
})
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Extracts the `EarnVaultInitialized` event from engine logs.
|
|
738
|
+
*
|
|
739
|
+
* @param logs - The logs.
|
|
740
|
+
* @param parameters - Engine address used to filter the logs.
|
|
741
|
+
* @returns The initialization event.
|
|
742
|
+
*/
|
|
743
|
+
export function extractEvent(logs: Log[], parameters: { engine: Address }) {
|
|
744
|
+
const [log] = parseEventLogs({
|
|
745
|
+
abi: Abis.erc4626Engine,
|
|
746
|
+
eventName: 'EarnVaultInitialized',
|
|
747
|
+
logs: logs.filter((log) =>
|
|
748
|
+
isAddressEqual(log.address, parameters.engine),
|
|
749
|
+
),
|
|
750
|
+
strict: true,
|
|
751
|
+
})
|
|
752
|
+
if (!log) throw new Error('`EarnVaultInitialized` event not found.')
|
|
753
|
+
return log
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Binds an ERC-4626 engine and waits for confirmation.
|
|
759
|
+
*
|
|
760
|
+
* @experimental
|
|
761
|
+
*
|
|
762
|
+
* @example
|
|
763
|
+
* ```ts
|
|
764
|
+
* import { createClient, http } from 'viem'
|
|
765
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
766
|
+
* import { tempoModerato } from 'viem/chains'
|
|
767
|
+
* import { Actions } from 'viem/tempo'
|
|
768
|
+
*
|
|
769
|
+
* const client = createClient({
|
|
770
|
+
* account: privateKeyToAccount('0x...'),
|
|
771
|
+
* chain: tempoModerato,
|
|
772
|
+
* transport: http(),
|
|
773
|
+
* })
|
|
774
|
+
* const result = await Actions.earn.bindErc4626EngineSync(client, {
|
|
775
|
+
* engine: '0x...',
|
|
776
|
+
* vault: '0x...',
|
|
777
|
+
* })
|
|
778
|
+
* ```
|
|
779
|
+
*
|
|
780
|
+
* @param client - Client controlled by the final engine owner.
|
|
781
|
+
* @param parameters - Parameters.
|
|
782
|
+
* @returns The receipt and bound addresses.
|
|
783
|
+
*/
|
|
784
|
+
export async function bindErc4626EngineSync<
|
|
785
|
+
chain extends Chain | undefined,
|
|
786
|
+
account extends Account | undefined,
|
|
787
|
+
>(
|
|
788
|
+
client: Client<Transport, chain, account>,
|
|
789
|
+
parameters: bindErc4626EngineSync.Parameters<chain, account>,
|
|
790
|
+
): Promise<bindErc4626EngineSync.ReturnValue> {
|
|
791
|
+
const { engine, throwOnReceiptRevert = true } = parameters
|
|
792
|
+
const receipt = await bindErc4626Engine.inner(writeContractSync, client, {
|
|
793
|
+
...parameters,
|
|
794
|
+
throwOnReceiptRevert,
|
|
795
|
+
} as never)
|
|
796
|
+
const { args } = bindErc4626Engine.extractEvent(receipt.logs, { engine })
|
|
797
|
+
return { engine, vault: args.earnVault, receipt }
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export namespace bindErc4626EngineSync {
|
|
801
|
+
export type Args = bindErc4626Engine.Args
|
|
802
|
+
export type Parameters<
|
|
803
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
804
|
+
account extends Account | undefined = Account | undefined,
|
|
805
|
+
> = bindErc4626Engine.Parameters<chain, account> &
|
|
806
|
+
WriteSyncParameters<chain, account>
|
|
807
|
+
export type ReturnValue = {
|
|
808
|
+
/** ERC-4626 engine address. */
|
|
809
|
+
engine: Address
|
|
810
|
+
/** Transaction receipt. */
|
|
811
|
+
receipt: TransactionReceipt
|
|
812
|
+
/** Bound EarnVault address. */
|
|
813
|
+
vault: Address
|
|
814
|
+
}
|
|
815
|
+
// TODO: exhaustive error type
|
|
816
|
+
export type ErrorType = BaseErrorType
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export type DeployErc4626StackErrorType = DeployErc4626StackError & {
|
|
820
|
+
name: 'DeployErc4626StackError'
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Error thrown after a partially completed Earn stack deployment.
|
|
825
|
+
*
|
|
826
|
+
* @experimental
|
|
827
|
+
*/
|
|
828
|
+
export class DeployErc4626StackError extends BaseError {
|
|
829
|
+
receipts: deployErc4626StackSync.Receipts
|
|
830
|
+
stage: deployErc4626StackSync.Stage
|
|
831
|
+
state: deployErc4626StackSync.State
|
|
832
|
+
|
|
833
|
+
constructor(
|
|
834
|
+
cause: Error,
|
|
835
|
+
parameters: {
|
|
836
|
+
receipts: deployErc4626StackSync.Receipts
|
|
837
|
+
stage: deployErc4626StackSync.Stage
|
|
838
|
+
state: deployErc4626StackSync.State
|
|
839
|
+
},
|
|
840
|
+
) {
|
|
841
|
+
super(`ERC-4626 Earn deployment failed during ${parameters.stage}.`, {
|
|
842
|
+
cause,
|
|
843
|
+
name: 'DeployErc4626StackError',
|
|
844
|
+
})
|
|
845
|
+
this.receipts = parameters.receipts
|
|
846
|
+
this.stage = parameters.stage
|
|
847
|
+
this.state = parameters.state
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Deploys and binds a complete ERC-4626 Earn stack through sequential,
|
|
853
|
+
* resumable transactions.
|
|
854
|
+
*
|
|
855
|
+
* @experimental
|
|
856
|
+
*
|
|
857
|
+
* @example
|
|
858
|
+
* ```ts
|
|
859
|
+
* import { createClient, http } from 'viem'
|
|
860
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
861
|
+
* import { tempoModerato } from 'viem/chains'
|
|
862
|
+
* import { Actions } from 'viem/tempo'
|
|
863
|
+
*
|
|
864
|
+
* const client = createClient({
|
|
865
|
+
* account: privateKeyToAccount('0x...'),
|
|
866
|
+
* chain: tempoModerato,
|
|
867
|
+
* transport: http(),
|
|
868
|
+
* })
|
|
869
|
+
* const result = await Actions.earn.deployErc4626StackSync(client, {
|
|
870
|
+
* deploymentId: '0x...',
|
|
871
|
+
* factories: { earn: '0x...', erc4626Engine: '0x...' },
|
|
872
|
+
* venue: '0x...',
|
|
873
|
+
* })
|
|
874
|
+
* ```
|
|
875
|
+
*
|
|
876
|
+
* @param client - Client.
|
|
877
|
+
* @param parameters - Parameters.
|
|
878
|
+
* @returns The deployed addresses and receipts created by this run.
|
|
879
|
+
*/
|
|
880
|
+
export async function deployErc4626StackSync<
|
|
881
|
+
chain extends Chain | undefined,
|
|
882
|
+
account extends Account | undefined,
|
|
883
|
+
>(
|
|
884
|
+
client: Client<Transport, chain, account>,
|
|
885
|
+
parameters: deployErc4626StackSync.Parameters<chain, account>,
|
|
886
|
+
): Promise<deployErc4626StackSync.ReturnValue> {
|
|
887
|
+
const deployer = parameters.account ?? client.account
|
|
888
|
+
if (!deployer) throw new AccountNotFoundError()
|
|
889
|
+
const owner_ = parameters.owner ?? deployer
|
|
890
|
+
const owner = parseAccount(owner_).address
|
|
891
|
+
const deployerAddress = parseAccount(deployer).address
|
|
892
|
+
const bindingAccount = (() => {
|
|
893
|
+
if (parameters.bindingAccount) return parameters.bindingAccount
|
|
894
|
+
if (typeof owner_ !== 'string') return owner_
|
|
895
|
+
if (isAddressEqual(owner, deployerAddress)) return deployer
|
|
896
|
+
return undefined
|
|
897
|
+
})()
|
|
898
|
+
if (
|
|
899
|
+
bindingAccount &&
|
|
900
|
+
!isAddressEqual(parseAccount(bindingAccount).address, owner)
|
|
901
|
+
)
|
|
902
|
+
throw new Error('`bindingAccount` must match `owner`.')
|
|
903
|
+
|
|
904
|
+
validateDeploymentId(parameters.deploymentId)
|
|
905
|
+
if (
|
|
906
|
+
parameters.resume &&
|
|
907
|
+
parameters.resume.deploymentId.toLowerCase() !==
|
|
908
|
+
parameters.deploymentId.toLowerCase()
|
|
909
|
+
)
|
|
910
|
+
throw new Error('The resumed deployment ID does not match `deploymentId`.')
|
|
911
|
+
await validateContracts(client, {
|
|
912
|
+
factories: parameters.factories,
|
|
913
|
+
venue: parameters.venue,
|
|
914
|
+
})
|
|
915
|
+
|
|
916
|
+
const {
|
|
917
|
+
gas: _,
|
|
918
|
+
keyAuthorization: __,
|
|
919
|
+
nonce: ___,
|
|
920
|
+
...sharedWriteParameters
|
|
921
|
+
} = pickWriteParameters(parameters as never)
|
|
922
|
+
const writeParameters = {
|
|
923
|
+
...sharedWriteParameters,
|
|
924
|
+
...pickWriteSyncParameters(parameters as never),
|
|
925
|
+
account: deployer,
|
|
926
|
+
throwOnReceiptRevert: true,
|
|
927
|
+
}
|
|
928
|
+
const engineArgs = {
|
|
929
|
+
deploymentId: parameters.deploymentId,
|
|
930
|
+
factory: parameters.factories.erc4626Engine,
|
|
931
|
+
name: parameters.name,
|
|
932
|
+
owner,
|
|
933
|
+
symbol: parameters.symbol,
|
|
934
|
+
venue: parameters.venue,
|
|
935
|
+
} satisfies createErc4626Engine.Args
|
|
936
|
+
const predictedEngine = await createErc4626Engine.predict(client, engineArgs)
|
|
937
|
+
const state: deployErc4626StackSync.State = {
|
|
938
|
+
deploymentId: parameters.deploymentId,
|
|
939
|
+
earnShare: parameters.resume?.earnShare,
|
|
940
|
+
engine: predictedEngine,
|
|
941
|
+
fees: parameters.resume?.fees,
|
|
942
|
+
vault: parameters.resume?.vault,
|
|
943
|
+
}
|
|
944
|
+
if (
|
|
945
|
+
parameters.resume?.engine &&
|
|
946
|
+
!isAddressEqual(parameters.resume.engine, predictedEngine)
|
|
947
|
+
)
|
|
948
|
+
throw new Error('The resumed engine does not match the factory prediction.')
|
|
949
|
+
|
|
950
|
+
const engineExists = await hasCode(client, predictedEngine)
|
|
951
|
+
if (!bindingAccount) {
|
|
952
|
+
const boundVault = engineExists
|
|
953
|
+
? await readContract(client, {
|
|
954
|
+
address: predictedEngine,
|
|
955
|
+
abi: Abis.erc4626Engine,
|
|
956
|
+
functionName: 'earnVault',
|
|
957
|
+
})
|
|
958
|
+
: zeroAddress
|
|
959
|
+
if (isAddressEqual(boundVault, zeroAddress))
|
|
960
|
+
throw new Error(
|
|
961
|
+
'`bindingAccount` is required when the final owner differs from the deployment account.',
|
|
962
|
+
)
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const receipts: deployErc4626StackSync.Receipts = {}
|
|
966
|
+
try {
|
|
967
|
+
if (!engineExists) {
|
|
968
|
+
await simulateContract(client, {
|
|
969
|
+
...sharedWriteParameters,
|
|
970
|
+
account: deployer,
|
|
971
|
+
...createErc4626Engine.call(engineArgs),
|
|
972
|
+
} as never)
|
|
973
|
+
const receipt = await createErc4626Engine.inner(
|
|
974
|
+
writeContractSync,
|
|
975
|
+
client,
|
|
976
|
+
{
|
|
977
|
+
...writeParameters,
|
|
978
|
+
...engineArgs,
|
|
979
|
+
} as never,
|
|
980
|
+
)
|
|
981
|
+
receipts.engine = receipt
|
|
982
|
+
createErc4626Engine.extractEvent(receipt.logs, {
|
|
983
|
+
factory: parameters.factories.erc4626Engine,
|
|
984
|
+
})
|
|
985
|
+
}
|
|
986
|
+
await verifyEngine(client, engineArgs, predictedEngine)
|
|
987
|
+
} catch (error) {
|
|
988
|
+
throw deploymentError(error, 'engine', state, receipts)
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const stackArgs = {
|
|
992
|
+
controls: parameters.controls,
|
|
993
|
+
deploymentId: parameters.deploymentId,
|
|
994
|
+
distributor: parameters.distributor,
|
|
995
|
+
engine: predictedEngine,
|
|
996
|
+
factory: parameters.factories.earn,
|
|
997
|
+
fees: parameters.fees,
|
|
998
|
+
owner,
|
|
999
|
+
transferPolicyId: parameters.transferPolicyId,
|
|
1000
|
+
} satisfies createStack.Args
|
|
1001
|
+
|
|
1002
|
+
try {
|
|
1003
|
+
const predicted = await createStack.predict(client, stackArgs)
|
|
1004
|
+
if (
|
|
1005
|
+
parameters.resume?.earnShare &&
|
|
1006
|
+
!isAddressEqual(parameters.resume.earnShare, predicted.earnShare)
|
|
1007
|
+
)
|
|
1008
|
+
throw new Error(
|
|
1009
|
+
'The resumed EarnShare does not match the factory prediction.',
|
|
1010
|
+
)
|
|
1011
|
+
if (
|
|
1012
|
+
parameters.resume?.fees &&
|
|
1013
|
+
!isAddressEqual(parameters.resume.fees, predicted.earnFees)
|
|
1014
|
+
)
|
|
1015
|
+
throw new Error(
|
|
1016
|
+
'The resumed EarnFees does not match the factory prediction.',
|
|
1017
|
+
)
|
|
1018
|
+
state.earnShare = predicted.earnShare
|
|
1019
|
+
state.fees = predicted.earnFees
|
|
1020
|
+
|
|
1021
|
+
if (!(await hasCode(client, predicted.earnShare))) {
|
|
1022
|
+
if (await hasCode(client, predicted.earnFees))
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
'Predicted EarnFees exists without the predicted EarnShare.',
|
|
1025
|
+
)
|
|
1026
|
+
await simulateContract(client, {
|
|
1027
|
+
...sharedWriteParameters,
|
|
1028
|
+
account: deployer,
|
|
1029
|
+
...createStack.call(stackArgs),
|
|
1030
|
+
} as never)
|
|
1031
|
+
const receipt = await createStack.inner(writeContractSync, client, {
|
|
1032
|
+
...writeParameters,
|
|
1033
|
+
...stackArgs,
|
|
1034
|
+
} as never)
|
|
1035
|
+
receipts.stack = receipt
|
|
1036
|
+
const { args } = createStack.extractEvent(receipt.logs, {
|
|
1037
|
+
factory: parameters.factories.earn,
|
|
1038
|
+
})
|
|
1039
|
+
state.vault = args.earnVault
|
|
1040
|
+
if (
|
|
1041
|
+
parameters.resume?.vault &&
|
|
1042
|
+
!isAddressEqual(parameters.resume.vault, args.earnVault)
|
|
1043
|
+
)
|
|
1044
|
+
throw new Error(
|
|
1045
|
+
'The resumed EarnVault does not match the factory deployment.',
|
|
1046
|
+
)
|
|
1047
|
+
} else {
|
|
1048
|
+
if (!(await hasCode(client, predicted.earnFees)))
|
|
1049
|
+
throw new Error(
|
|
1050
|
+
'Predicted EarnShare exists without the predicted EarnFees.',
|
|
1051
|
+
)
|
|
1052
|
+
state.vault = parameters.resume?.vault
|
|
1053
|
+
if (!state.vault) {
|
|
1054
|
+
const event = await findStackDeployment(client, {
|
|
1055
|
+
earnShare: predicted.earnShare,
|
|
1056
|
+
factory: parameters.factories.earn,
|
|
1057
|
+
fromBlock: parameters.fromBlock,
|
|
1058
|
+
})
|
|
1059
|
+
state.vault = event.args.earnVault
|
|
1060
|
+
if (!isAddressEqual(event.args.earnFees, predicted.earnFees))
|
|
1061
|
+
throw new Error(
|
|
1062
|
+
'Recovered EarnFees does not match the factory prediction.',
|
|
1063
|
+
)
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (!state.vault) throw new Error('EarnVault address was not recovered.')
|
|
1067
|
+
await verifyStack(client, {
|
|
1068
|
+
engine: predictedEngine,
|
|
1069
|
+
fees: predicted.earnFees,
|
|
1070
|
+
owner,
|
|
1071
|
+
share: predicted.earnShare,
|
|
1072
|
+
vault: state.vault,
|
|
1073
|
+
})
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
throw deploymentError(error, 'stack', state, receipts)
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
try {
|
|
1079
|
+
const vault = state.vault!
|
|
1080
|
+
const boundVault = await readContract(client, {
|
|
1081
|
+
address: predictedEngine,
|
|
1082
|
+
abi: Abis.erc4626Engine,
|
|
1083
|
+
functionName: 'earnVault',
|
|
1084
|
+
})
|
|
1085
|
+
if (isAddressEqual(boundVault, zeroAddress)) {
|
|
1086
|
+
if (!bindingAccount)
|
|
1087
|
+
throw new Error(
|
|
1088
|
+
'`bindingAccount` is required when the final owner differs from the deployment account.',
|
|
1089
|
+
)
|
|
1090
|
+
const bindingParameters = {
|
|
1091
|
+
...writeParameters,
|
|
1092
|
+
account: bindingAccount,
|
|
1093
|
+
engine: predictedEngine,
|
|
1094
|
+
vault,
|
|
1095
|
+
}
|
|
1096
|
+
await simulateContract(client, {
|
|
1097
|
+
...sharedWriteParameters,
|
|
1098
|
+
account: bindingAccount,
|
|
1099
|
+
...bindErc4626Engine.call({ engine: predictedEngine, vault }),
|
|
1100
|
+
} as never)
|
|
1101
|
+
const receipt = await bindErc4626Engine.inner(
|
|
1102
|
+
writeContractSync,
|
|
1103
|
+
client,
|
|
1104
|
+
bindingParameters as never,
|
|
1105
|
+
)
|
|
1106
|
+
receipts.binding = receipt
|
|
1107
|
+
bindErc4626Engine.extractEvent(receipt.logs, { engine: predictedEngine })
|
|
1108
|
+
} else if (!isAddressEqual(boundVault, vault)) {
|
|
1109
|
+
throw new Error(`Engine is already bound to ${boundVault}.`)
|
|
1110
|
+
}
|
|
1111
|
+
const verified = await readContract(client, {
|
|
1112
|
+
address: predictedEngine,
|
|
1113
|
+
abi: Abis.erc4626Engine,
|
|
1114
|
+
functionName: 'earnVault',
|
|
1115
|
+
})
|
|
1116
|
+
if (!isAddressEqual(verified, vault))
|
|
1117
|
+
throw new Error('Engine binding verification failed.')
|
|
1118
|
+
} catch (error) {
|
|
1119
|
+
throw deploymentError(error, 'binding', state, receipts)
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
return {
|
|
1123
|
+
deploymentId: parameters.deploymentId,
|
|
1124
|
+
earnShare: state.earnShare!,
|
|
1125
|
+
engine: predictedEngine,
|
|
1126
|
+
fees: state.fees!,
|
|
1127
|
+
receipts,
|
|
1128
|
+
vault: state.vault!,
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
export namespace deployErc4626StackSync {
|
|
1133
|
+
export type Parameters<
|
|
1134
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
1135
|
+
account extends Account | undefined = Account | undefined,
|
|
1136
|
+
> = Omit<
|
|
1137
|
+
WriteParameters<chain, account>,
|
|
1138
|
+
'gas' | 'keyAuthorization' | 'nonce' | 'throwOnReceiptRevert'
|
|
1139
|
+
> &
|
|
1140
|
+
WriteSyncParameters<chain, account> & {
|
|
1141
|
+
/** Account used only for the final owner binding. */
|
|
1142
|
+
bindingAccount?: Account | Address | undefined
|
|
1143
|
+
/** Initial Earn vault controls. */
|
|
1144
|
+
controls?: EarnVaultControls | undefined
|
|
1145
|
+
/** Stable deterministic deployment identifier. */
|
|
1146
|
+
deploymentId: Hex.Hex
|
|
1147
|
+
/** Optional protected fee distributor. */
|
|
1148
|
+
distributor?: EarnDistributorConfiguration | undefined
|
|
1149
|
+
/** Reviewed factory pair from one Earn release. */
|
|
1150
|
+
factories: EarnFactoryAddresses
|
|
1151
|
+
/** Initial fee configuration. Omit for fee-free deployment. */
|
|
1152
|
+
fees?: EarnFeeConfiguration | undefined
|
|
1153
|
+
/** First block to search for a prior factory deployment event. */
|
|
1154
|
+
fromBlock?: bigint | undefined
|
|
1155
|
+
/** Optional engine name override. */
|
|
1156
|
+
name?: string | undefined
|
|
1157
|
+
/** Final stack owner and operator. @default `account.address` */
|
|
1158
|
+
owner?: Account | Address | undefined
|
|
1159
|
+
/** Previously persisted deployment state. */
|
|
1160
|
+
resume?: State | undefined
|
|
1161
|
+
/** Optional engine symbol override. */
|
|
1162
|
+
symbol?: string | undefined
|
|
1163
|
+
/** Existing simple whitelist policy. Zero selects always-allow. */
|
|
1164
|
+
transferPolicyId?: bigint | undefined
|
|
1165
|
+
/** ERC-4626 venue address. */
|
|
1166
|
+
venue: Address
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
export type Stage = 'binding' | 'engine' | 'stack'
|
|
1170
|
+
|
|
1171
|
+
export type State = {
|
|
1172
|
+
deploymentId: Hex.Hex
|
|
1173
|
+
earnShare?: Address | undefined
|
|
1174
|
+
engine: Address
|
|
1175
|
+
fees?: Address | undefined
|
|
1176
|
+
vault?: Address | undefined
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
export type Receipts = {
|
|
1180
|
+
binding?: TransactionReceipt | undefined
|
|
1181
|
+
engine?: TransactionReceipt | undefined
|
|
1182
|
+
stack?: TransactionReceipt | undefined
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
export type ReturnValue = {
|
|
1186
|
+
deploymentId: Hex.Hex
|
|
1187
|
+
earnShare: Address
|
|
1188
|
+
engine: Address
|
|
1189
|
+
fees: Address
|
|
1190
|
+
receipts: Receipts
|
|
1191
|
+
vault: Address
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
export type ErrorType = DeployErc4626StackErrorType | BaseErrorType
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function validateDeploymentId(deploymentId: Hex.Hex) {
|
|
1198
|
+
if (Hex.size(deploymentId) !== 32 || BigInt(deploymentId) === 0n)
|
|
1199
|
+
throw new Error('`deploymentId` must be a nonzero 32-byte hex value.')
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function toDeployParameters(args: createStack.Args) {
|
|
1203
|
+
const fixedFees = args.fees?.fixedFees ?? []
|
|
1204
|
+
if (fixedFees.length > 4)
|
|
1205
|
+
throw new Error('Earn supports at most four fixed fee recipients.')
|
|
1206
|
+
const seen = new Set<string>()
|
|
1207
|
+
let totalRateBps = 0
|
|
1208
|
+
for (const fee of fixedFees) {
|
|
1209
|
+
validateFeeRate(fee.rateBps, '`fixedFees[].rateBps`', false)
|
|
1210
|
+
if (isAddressEqual(fee.account, zeroAddress))
|
|
1211
|
+
throw new Error('Fixed fee recipients cannot be the zero address.')
|
|
1212
|
+
const account = fee.account.toLowerCase()
|
|
1213
|
+
if (seen.has(account))
|
|
1214
|
+
throw new Error('Fixed fee recipients must be unique.')
|
|
1215
|
+
seen.add(account)
|
|
1216
|
+
totalRateBps += fee.rateBps
|
|
1217
|
+
}
|
|
1218
|
+
if (totalRateBps > 10_000)
|
|
1219
|
+
throw new Error('The total fixed fee rate cannot exceed 10,000 bps.')
|
|
1220
|
+
|
|
1221
|
+
const excess = args.fees?.excess
|
|
1222
|
+
if (excess) {
|
|
1223
|
+
validateFeeRate(excess.annualTargetRateBps, '`annualTargetRateBps`', true)
|
|
1224
|
+
validateFeeRate(excess.rateBps, '`excess.rateBps`', false)
|
|
1225
|
+
if (isAddressEqual(excess.account, zeroAddress))
|
|
1226
|
+
throw new Error('The excess fee recipient cannot be the zero address.')
|
|
1227
|
+
}
|
|
1228
|
+
const zeroFee = { account: zeroAddress, rateBps: 0 } as const
|
|
1229
|
+
const distributor = args.distributor?.distributor ?? zeroAddress
|
|
1230
|
+
const updateDelay = args.distributor?.updateDelay ?? 0
|
|
1231
|
+
if (args.distributor) {
|
|
1232
|
+
if (isAddressEqual(distributor, zeroAddress))
|
|
1233
|
+
throw new Error('An enabled distributor cannot be the zero address.')
|
|
1234
|
+
if (updateDelay <= 0)
|
|
1235
|
+
throw new Error(
|
|
1236
|
+
'An enabled distributor requires a positive update delay.',
|
|
1237
|
+
)
|
|
1238
|
+
if (fixedFees.length === 0)
|
|
1239
|
+
throw new Error('An enabled distributor requires at least one fixed fee.')
|
|
1240
|
+
}
|
|
1241
|
+
if (!Number.isSafeInteger(updateDelay) || updateDelay > 0xffffffffff)
|
|
1242
|
+
throw new Error('`updateDelay` must fit into uint40 seconds.')
|
|
1243
|
+
const transferPolicyId = args.transferPolicyId ?? 0n
|
|
1244
|
+
if (transferPolicyId < 0n || transferPolicyId > maxUint64)
|
|
1245
|
+
throw new Error('`transferPolicyId` must fit into uint64.')
|
|
1246
|
+
const maxManagedAssets = args.controls?.maxManagedAssets ?? 0n
|
|
1247
|
+
if (maxManagedAssets < 0n || maxManagedAssets > maxUint256)
|
|
1248
|
+
throw new Error('`maxManagedAssets` must fit into uint256.')
|
|
1249
|
+
|
|
1250
|
+
return {
|
|
1251
|
+
controls: {
|
|
1252
|
+
asyncJanitor: args.controls?.asyncJanitor ?? zeroAddress,
|
|
1253
|
+
emergencyGuardian: args.controls?.emergencyGuardian ?? zeroAddress,
|
|
1254
|
+
maxManagedAssets,
|
|
1255
|
+
migrationMode:
|
|
1256
|
+
(args.controls?.migrationMode ?? 'userOnly') === 'userOnly' ? 0 : 1,
|
|
1257
|
+
},
|
|
1258
|
+
deploymentId: args.deploymentId,
|
|
1259
|
+
distributorConfig: { distributor, updateDelay },
|
|
1260
|
+
engine: args.engine,
|
|
1261
|
+
fees: {
|
|
1262
|
+
excess: excess
|
|
1263
|
+
? {
|
|
1264
|
+
account: excess.account,
|
|
1265
|
+
annualTargetRateBps: excess.annualTargetRateBps,
|
|
1266
|
+
enabled: true,
|
|
1267
|
+
excessFeeRateBps: excess.rateBps,
|
|
1268
|
+
}
|
|
1269
|
+
: {
|
|
1270
|
+
account: zeroAddress,
|
|
1271
|
+
annualTargetRateBps: 0,
|
|
1272
|
+
enabled: false,
|
|
1273
|
+
excessFeeRateBps: 0,
|
|
1274
|
+
},
|
|
1275
|
+
fixedFeeCount: fixedFees.length,
|
|
1276
|
+
fixedFees: [
|
|
1277
|
+
fixedFees[0] ?? zeroFee,
|
|
1278
|
+
fixedFees[1] ?? zeroFee,
|
|
1279
|
+
fixedFees[2] ?? zeroFee,
|
|
1280
|
+
fixedFees[3] ?? zeroFee,
|
|
1281
|
+
],
|
|
1282
|
+
},
|
|
1283
|
+
owner: args.owner,
|
|
1284
|
+
transferPolicyId,
|
|
1285
|
+
} as const
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function validateFeeRate(rate: number, name: string, allowZero: boolean) {
|
|
1289
|
+
if (!Number.isInteger(rate) || rate < (allowZero ? 0 : 1) || rate > 10_000)
|
|
1290
|
+
throw new Error(
|
|
1291
|
+
`${name} must be an integer between ${allowZero ? 0 : 1} and 10,000.`,
|
|
1292
|
+
)
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
async function hasCode(
|
|
1296
|
+
client: Client<Transport, Chain | undefined>,
|
|
1297
|
+
address: Address,
|
|
1298
|
+
) {
|
|
1299
|
+
const code = await getCode(client, { address })
|
|
1300
|
+
return code !== undefined && code !== '0x'
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
async function validateContracts(
|
|
1304
|
+
client: Client<Transport, Chain | undefined>,
|
|
1305
|
+
parameters: { factories: EarnFactoryAddresses; venue: Address },
|
|
1306
|
+
) {
|
|
1307
|
+
const [engineFactory, earnFactory, venue] = await Promise.all([
|
|
1308
|
+
hasCode(client, parameters.factories.erc4626Engine),
|
|
1309
|
+
hasCode(client, parameters.factories.earn),
|
|
1310
|
+
hasCode(client, parameters.venue),
|
|
1311
|
+
])
|
|
1312
|
+
if (!engineFactory) throw new Error('ERC4626EngineFactory has no code.')
|
|
1313
|
+
if (!earnFactory) throw new Error('EarnFactory has no code.')
|
|
1314
|
+
if (!venue) throw new Error('ERC-4626 venue has no code.')
|
|
1315
|
+
const tip20Factory = await readContract(client, {
|
|
1316
|
+
address: parameters.factories.earn,
|
|
1317
|
+
abi: Abis.earnFactory,
|
|
1318
|
+
functionName: 'tip20Factory',
|
|
1319
|
+
})
|
|
1320
|
+
if (!isAddressEqual(tip20Factory, Addresses.tip20Factory))
|
|
1321
|
+
throw new Error('EarnFactory uses an unexpected TIP-20 factory.')
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
async function verifyEngine(
|
|
1325
|
+
client: Client<Transport, Chain | undefined>,
|
|
1326
|
+
args: createErc4626Engine.Args,
|
|
1327
|
+
engine: Address,
|
|
1328
|
+
) {
|
|
1329
|
+
const [venue, owner] = await Promise.all([
|
|
1330
|
+
readContract(client, {
|
|
1331
|
+
address: engine,
|
|
1332
|
+
abi: Abis.erc4626Engine,
|
|
1333
|
+
functionName: 'vault',
|
|
1334
|
+
}),
|
|
1335
|
+
readContract(client, {
|
|
1336
|
+
address: engine,
|
|
1337
|
+
abi: Abis.erc4626Engine,
|
|
1338
|
+
functionName: 'owner',
|
|
1339
|
+
}),
|
|
1340
|
+
])
|
|
1341
|
+
if (!isAddressEqual(venue, args.venue))
|
|
1342
|
+
throw new Error('Engine venue verification failed.')
|
|
1343
|
+
if (!isAddressEqual(owner, args.owner))
|
|
1344
|
+
throw new Error('Engine owner verification failed.')
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
async function verifyStack(
|
|
1348
|
+
client: Client<Transport, Chain | undefined>,
|
|
1349
|
+
parameters: {
|
|
1350
|
+
engine: Address
|
|
1351
|
+
fees: Address
|
|
1352
|
+
owner: Address
|
|
1353
|
+
share: Address
|
|
1354
|
+
vault: Address
|
|
1355
|
+
},
|
|
1356
|
+
) {
|
|
1357
|
+
const [engine, fees, operator, share] = await Promise.all([
|
|
1358
|
+
readContract(client, {
|
|
1359
|
+
address: parameters.vault,
|
|
1360
|
+
abi: Abis.earnVault,
|
|
1361
|
+
functionName: 'engine',
|
|
1362
|
+
}),
|
|
1363
|
+
readContract(client, {
|
|
1364
|
+
address: parameters.vault,
|
|
1365
|
+
abi: Abis.earnVault,
|
|
1366
|
+
functionName: 'earnFees',
|
|
1367
|
+
}),
|
|
1368
|
+
readContract(client, {
|
|
1369
|
+
address: parameters.vault,
|
|
1370
|
+
abi: Abis.earnVault,
|
|
1371
|
+
functionName: 'operator',
|
|
1372
|
+
}),
|
|
1373
|
+
readContract(client, {
|
|
1374
|
+
address: parameters.vault,
|
|
1375
|
+
abi: Abis.earnVault,
|
|
1376
|
+
functionName: 'earnShare',
|
|
1377
|
+
}),
|
|
1378
|
+
])
|
|
1379
|
+
if (!isAddressEqual(engine, parameters.engine))
|
|
1380
|
+
throw new Error('EarnVault engine verification failed.')
|
|
1381
|
+
if (!isAddressEqual(fees, parameters.fees))
|
|
1382
|
+
throw new Error('EarnVault fees verification failed.')
|
|
1383
|
+
if (!isAddressEqual(operator, parameters.owner))
|
|
1384
|
+
throw new Error('EarnVault operator verification failed.')
|
|
1385
|
+
if (!isAddressEqual(share, parameters.share))
|
|
1386
|
+
throw new Error('EarnVault share verification failed.')
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
async function findStackDeployment(
|
|
1390
|
+
client: Client<Transport, Chain | undefined>,
|
|
1391
|
+
parameters: {
|
|
1392
|
+
earnShare: Address
|
|
1393
|
+
factory: Address
|
|
1394
|
+
fromBlock?: bigint | undefined
|
|
1395
|
+
},
|
|
1396
|
+
) {
|
|
1397
|
+
const event = getAbiItem({
|
|
1398
|
+
abi: Abis.earnFactory,
|
|
1399
|
+
name: 'EarnStackDeployed',
|
|
1400
|
+
})
|
|
1401
|
+
const logs = await getLogs(client, {
|
|
1402
|
+
address: parameters.factory,
|
|
1403
|
+
args: { earnShare: parameters.earnShare },
|
|
1404
|
+
event,
|
|
1405
|
+
fromBlock: parameters.fromBlock ?? 0n,
|
|
1406
|
+
strict: true,
|
|
1407
|
+
toBlock: 'latest',
|
|
1408
|
+
})
|
|
1409
|
+
const log = logs.at(-1)
|
|
1410
|
+
if (!log) throw new Error('Prior `EarnStackDeployed` event not found.')
|
|
1411
|
+
return log
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function deploymentError(
|
|
1415
|
+
error: unknown,
|
|
1416
|
+
stage: deployErc4626StackSync.Stage,
|
|
1417
|
+
state: deployErc4626StackSync.State,
|
|
1418
|
+
receipts: deployErc4626StackSync.Receipts,
|
|
1419
|
+
) {
|
|
1420
|
+
if (error instanceof DeployErc4626StackError) return error
|
|
1421
|
+
if (error instanceof TransactionReceiptRevertedError)
|
|
1422
|
+
receipts[stage] = error.receipt
|
|
1423
|
+
return new DeployErc4626StackError(
|
|
1424
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
1425
|
+
{ receipts: { ...receipts }, stage, state: { ...state } },
|
|
1426
|
+
)
|
|
1427
|
+
}
|