viem 2.55.5 → 2.55.7
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 +22 -0
- package/_cjs/errors/version.js +1 -1
- package/_cjs/tempo/Abis.js +1809 -1
- package/_cjs/tempo/Abis.js.map +1 -1
- package/_cjs/tempo/Decorator.js +25 -2
- package/_cjs/tempo/Decorator.js.map +1 -1
- package/_cjs/tempo/actions/earn.js +1368 -0
- package/_cjs/tempo/actions/earn.js.map +1 -0
- package/_cjs/tempo/actions/index.js +2 -1
- package/_cjs/tempo/actions/index.js.map +1 -1
- package/_cjs/tempo/actions/zone.js +47 -61
- package/_cjs/tempo/actions/zone.js.map +1 -1
- package/_cjs/tempo/chainConfig.js +1 -1
- package/_cjs/tempo/chainConfig.js.map +1 -1
- package/_cjs/tempo/errors.js +28 -4
- package/_cjs/tempo/errors.js.map +1 -1
- package/_cjs/tempo/index.js +2 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/tempo/internal/WithdrawalSenderTag.js +9 -0
- package/_cjs/tempo/internal/WithdrawalSenderTag.js.map +1 -0
- package/_cjs/tempo/zones/Abis.js +46 -0
- package/_cjs/tempo/zones/Abis.js.map +1 -1
- package/_cjs/utils/blob/fromBlobs.js +5 -2
- package/_cjs/utils/blob/fromBlobs.js.map +1 -1
- package/_cjs/utils/encoding/fromBytes.js +2 -2
- package/_cjs/utils/encoding/fromBytes.js.map +1 -1
- package/_esm/errors/version.js +1 -1
- package/_esm/tempo/Abis.js +1810 -0
- package/_esm/tempo/Abis.js.map +1 -1
- package/_esm/tempo/Decorator.js +25 -2
- package/_esm/tempo/Decorator.js.map +1 -1
- package/_esm/tempo/actions/earn.js +2100 -0
- package/_esm/tempo/actions/earn.js.map +1 -0
- package/_esm/tempo/actions/index.js +1 -0
- package/_esm/tempo/actions/index.js.map +1 -1
- package/_esm/tempo/actions/zone.js +104 -119
- package/_esm/tempo/actions/zone.js.map +1 -1
- package/_esm/tempo/chainConfig.js +2 -1
- package/_esm/tempo/chainConfig.js.map +1 -1
- package/_esm/tempo/errors.js +25 -2
- package/_esm/tempo/errors.js.map +1 -1
- package/_esm/tempo/index.js +1 -1
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/tempo/internal/WithdrawalSenderTag.js +7 -0
- package/_esm/tempo/internal/WithdrawalSenderTag.js.map +1 -0
- package/_esm/tempo/zones/Abis.js +46 -0
- package/_esm/tempo/zones/Abis.js.map +1 -1
- package/_esm/utils/blob/fromBlobs.js +6 -2
- package/_esm/utils/blob/fromBlobs.js.map +1 -1
- package/_esm/utils/encoding/fromBytes.js +2 -2
- package/_esm/utils/encoding/fromBytes.js.map +1 -1
- package/_types/errors/version.d.ts +1 -1
- package/_types/tempo/Abis.d.ts +2880 -0
- package/_types/tempo/Abis.d.ts.map +1 -1
- package/_types/tempo/Decorator.d.ts +499 -41
- package/_types/tempo/Decorator.d.ts.map +1 -1
- package/_types/tempo/actions/earn.d.ts +7549 -0
- package/_types/tempo/actions/earn.d.ts.map +1 -0
- package/_types/tempo/actions/index.d.ts +1 -0
- package/_types/tempo/actions/index.d.ts.map +1 -1
- package/_types/tempo/actions/zone.d.ts +86 -109
- package/_types/tempo/actions/zone.d.ts.map +1 -1
- package/_types/tempo/chainConfig.d.ts.map +1 -1
- package/_types/tempo/errors.d.ts +33 -3
- package/_types/tempo/errors.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +1 -1
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/tempo/internal/WithdrawalSenderTag.d.ts +11 -0
- package/_types/tempo/internal/WithdrawalSenderTag.d.ts.map +1 -0
- package/_types/tempo/zones/Abis.d.ts +87 -0
- package/_types/tempo/zones/Abis.d.ts.map +1 -1
- package/_types/utils/blob/fromBlobs.d.ts.map +1 -1
- package/errors/version.ts +1 -1
- package/package.json +2 -2
- package/tempo/Abis.ts +1822 -0
- package/tempo/Decorator.ts +572 -47
- package/tempo/actions/earn.ts +3319 -0
- package/tempo/actions/index.ts +1 -0
- package/tempo/actions/zone.ts +183 -230
- package/tempo/chainConfig.ts +3 -1
- package/tempo/errors.ts +54 -6
- package/tempo/index.ts +1 -0
- package/tempo/internal/WithdrawalSenderTag.ts +20 -0
- package/tempo/zones/Abis.ts +46 -0
- package/utils/blob/fromBlobs.ts +6 -2
- package/utils/encoding/fromBytes.ts +2 -2
|
@@ -0,0 +1,3319 @@
|
|
|
1
|
+
import type { Address } from 'abitype'
|
|
2
|
+
import { Hex } from 'ox'
|
|
3
|
+
import { EarnShares, TokenId } from 'ox/tempo'
|
|
4
|
+
import type { Account } from '../../accounts/types.js'
|
|
5
|
+
import { parseAccount } from '../../accounts/utils/parseAccount.js'
|
|
6
|
+
import { estimateContractGas } from '../../actions/public/estimateContractGas.js'
|
|
7
|
+
import { getBlockNumber } from '../../actions/public/getBlockNumber.js'
|
|
8
|
+
import { type GetLogsErrorType, getLogs } from '../../actions/public/getLogs.js'
|
|
9
|
+
import { multicall } from '../../actions/public/multicall.js'
|
|
10
|
+
import {
|
|
11
|
+
type ReadContractReturnType,
|
|
12
|
+
readContract,
|
|
13
|
+
} from '../../actions/public/readContract.js'
|
|
14
|
+
import {
|
|
15
|
+
type SimulateContractReturnType,
|
|
16
|
+
simulateContract,
|
|
17
|
+
} from '../../actions/public/simulateContract.js'
|
|
18
|
+
import * as internal_Token from '../../actions/token/internal.js'
|
|
19
|
+
import {
|
|
20
|
+
type SendTransactionReturnType,
|
|
21
|
+
sendTransaction,
|
|
22
|
+
} from '../../actions/wallet/sendTransaction.js'
|
|
23
|
+
import { sendTransactionSync } from '../../actions/wallet/sendTransactionSync.js'
|
|
24
|
+
import { writeContractSync } from '../../actions/wallet/writeContractSync.js'
|
|
25
|
+
import type { Client } from '../../clients/createClient.js'
|
|
26
|
+
import type { Transport } from '../../clients/transports/createTransport.js'
|
|
27
|
+
import { AccountNotFoundError } from '../../errors/account.js'
|
|
28
|
+
import type { BaseErrorType } from '../../errors/base.js'
|
|
29
|
+
import type { Chain } from '../../types/chain.js'
|
|
30
|
+
import type { Log } from '../../types/log.js'
|
|
31
|
+
import type { Compute, OneOf } from '../../types/utils.js'
|
|
32
|
+
import { encodeAbiParameters } from '../../utils/abi/encodeAbiParameters.js'
|
|
33
|
+
import { getAbiItem } from '../../utils/abi/getAbiItem.js'
|
|
34
|
+
import { parseEventLogs } from '../../utils/abi/parseEventLogs.js'
|
|
35
|
+
import { getAddress } from '../../utils/address/getAddress.js'
|
|
36
|
+
import { isAddressEqual } from '../../utils/address/isAddressEqual.js'
|
|
37
|
+
import { type ObserveErrorType, observe } from '../../utils/observe.js'
|
|
38
|
+
import { type PollErrorType, poll } from '../../utils/poll.js'
|
|
39
|
+
import { withResolvers } from '../../utils/promise/withResolvers.js'
|
|
40
|
+
import { stringify } from '../../utils/stringify.js'
|
|
41
|
+
import * as Abis from '../Abis.js'
|
|
42
|
+
import * as Addresses from '../Addresses.js'
|
|
43
|
+
import {
|
|
44
|
+
GetVaultEngineChangedError,
|
|
45
|
+
type GetVaultEngineChangedErrorType,
|
|
46
|
+
WaitForPrivateDepositTimeoutError,
|
|
47
|
+
type WaitForPrivateDepositTimeoutErrorType,
|
|
48
|
+
WaitForPrivateRedeemTimeoutError,
|
|
49
|
+
type WaitForPrivateRedeemTimeoutErrorType,
|
|
50
|
+
} from '../errors.js'
|
|
51
|
+
import type {
|
|
52
|
+
GetAccountParameter,
|
|
53
|
+
ReadParameters,
|
|
54
|
+
WriteParameters,
|
|
55
|
+
WriteSyncParameters,
|
|
56
|
+
} from '../internal/types.js'
|
|
57
|
+
import {
|
|
58
|
+
type CallParameters,
|
|
59
|
+
defineCall,
|
|
60
|
+
pickWriteParameters,
|
|
61
|
+
resolveCallParameters,
|
|
62
|
+
resolveTokenWithDecimals,
|
|
63
|
+
} from '../internal/utils.js'
|
|
64
|
+
import type { TransactionReceipt } from '../Transaction.js'
|
|
65
|
+
import * as policyActions from './policy.js'
|
|
66
|
+
import * as tokenActions from './token.js'
|
|
67
|
+
import * as zoneActions from './zone.js'
|
|
68
|
+
|
|
69
|
+
/** TIP-403 policy ID that allows every sender, recipient, and mint recipient. */
|
|
70
|
+
export const alwaysAllowPolicyId = 1n
|
|
71
|
+
|
|
72
|
+
/** Admission-only TIP-403 policy attached to an Earn vault share token. */
|
|
73
|
+
export type ExitSafePolicy = {
|
|
74
|
+
/** Compound policy attached to the vault share token. */
|
|
75
|
+
transferPolicyId: bigint
|
|
76
|
+
/** Sender policy. Must be {@link alwaysAllowPolicyId} so holders can exit. */
|
|
77
|
+
senderPolicyId: bigint
|
|
78
|
+
/** Recipient eligibility policy. */
|
|
79
|
+
recipientPolicyId: bigint
|
|
80
|
+
/** Mint-recipient eligibility policy. Must match `recipientPolicyId`. */
|
|
81
|
+
mintRecipientPolicyId: bigint
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Receipts produced while configuring an exit-safe Earn policy. */
|
|
85
|
+
export type ExitSafePolicyReceipts = {
|
|
86
|
+
/** Whitelist policy creation receipt. */
|
|
87
|
+
eligibilityPolicy: TransactionReceipt
|
|
88
|
+
/** Compound policy creation receipt. */
|
|
89
|
+
compoundPolicy: TransactionReceipt
|
|
90
|
+
/** Vault share token policy update receipt. */
|
|
91
|
+
tokenPolicy: TransactionReceipt
|
|
92
|
+
/** Eligibility policy admin transfer receipt, when an administrator is provided. */
|
|
93
|
+
policyAdmin?: TransactionReceipt | undefined
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates and attaches an admission-only TIP-403 policy to an Earn vault share
|
|
98
|
+
* token. Existing holders remain able to send shares while recipients and mint
|
|
99
|
+
* recipients must belong to the same whitelist.
|
|
100
|
+
*
|
|
101
|
+
* The action submits three or four sequential transactions and is not atomic.
|
|
102
|
+
* Use {@link validateExitSafePolicy} to verify the final state.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { createClient, http } from 'viem'
|
|
107
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
108
|
+
* import { tempoModerato } from 'viem/chains'
|
|
109
|
+
* import { Actions } from 'viem/tempo'
|
|
110
|
+
*
|
|
111
|
+
* const account = privateKeyToAccount('0x...')
|
|
112
|
+
* const client = createClient({
|
|
113
|
+
* account,
|
|
114
|
+
* chain: tempoModerato,
|
|
115
|
+
* transport: http(),
|
|
116
|
+
* })
|
|
117
|
+
*
|
|
118
|
+
* const { policy, receipts } =
|
|
119
|
+
* await Actions.earn.configureExitSafePolicy(client, {
|
|
120
|
+
* accessAdministrator: '0x...',
|
|
121
|
+
* initialMembers: ['0x...', '0x...'],
|
|
122
|
+
* shareToken: '0x...',
|
|
123
|
+
* })
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* @param client - Client authorized to change the vault share token policy.
|
|
127
|
+
* @param parameters - Share token, administrator, and initial members.
|
|
128
|
+
* @returns The configured policy IDs and transaction receipts.
|
|
129
|
+
*/
|
|
130
|
+
export async function configureExitSafePolicy<
|
|
131
|
+
chain extends Chain | undefined,
|
|
132
|
+
account extends Account | undefined,
|
|
133
|
+
>(
|
|
134
|
+
client: Client<Transport, chain, account>,
|
|
135
|
+
parameters: configureExitSafePolicy.Parameters<account>,
|
|
136
|
+
): Promise<configureExitSafePolicy.ReturnValue> {
|
|
137
|
+
const account_ = parameters.account ?? client.account
|
|
138
|
+
if (!account_) throw new AccountNotFoundError()
|
|
139
|
+
const account = parseAccount(account_)
|
|
140
|
+
const initialMembers = [
|
|
141
|
+
...new Set(parameters.initialMembers.map((member) => getAddress(member))),
|
|
142
|
+
]
|
|
143
|
+
if (initialMembers.length === 0)
|
|
144
|
+
throw new Error('At least one initial policy member is required.')
|
|
145
|
+
|
|
146
|
+
const eligibility = await policyActions.createSync(client, {
|
|
147
|
+
account,
|
|
148
|
+
addresses: initialMembers,
|
|
149
|
+
chain: client.chain,
|
|
150
|
+
type: 'whitelist',
|
|
151
|
+
} as never)
|
|
152
|
+
const compoundPolicy = await writeContractSync(client, {
|
|
153
|
+
account,
|
|
154
|
+
abi: Abis.tip403Registry,
|
|
155
|
+
address: Addresses.tip403Registry,
|
|
156
|
+
args: [alwaysAllowPolicyId, eligibility.policyId, eligibility.policyId],
|
|
157
|
+
chain: client.chain,
|
|
158
|
+
functionName: 'createCompoundPolicy',
|
|
159
|
+
throwOnReceiptRevert: true,
|
|
160
|
+
} as never)
|
|
161
|
+
const [compoundEvent] = parseEventLogs({
|
|
162
|
+
abi: Abis.tip403Registry,
|
|
163
|
+
eventName: 'CompoundPolicyCreated',
|
|
164
|
+
logs: compoundPolicy.logs,
|
|
165
|
+
strict: true,
|
|
166
|
+
})
|
|
167
|
+
if (!compoundEvent)
|
|
168
|
+
throw new Error('`CompoundPolicyCreated` event not found.')
|
|
169
|
+
|
|
170
|
+
const tokenPolicy = await tokenActions.changeTransferPolicySync(client, {
|
|
171
|
+
account,
|
|
172
|
+
chain: client.chain,
|
|
173
|
+
policyId: compoundEvent.args.policyId,
|
|
174
|
+
token: parameters.shareToken,
|
|
175
|
+
} as never)
|
|
176
|
+
const policyAdmin = isAddressEqual(
|
|
177
|
+
parameters.accessAdministrator,
|
|
178
|
+
account.address,
|
|
179
|
+
)
|
|
180
|
+
? undefined
|
|
181
|
+
: await policyActions.setAdminSync(client, {
|
|
182
|
+
account,
|
|
183
|
+
admin: parameters.accessAdministrator,
|
|
184
|
+
chain: client.chain,
|
|
185
|
+
policyId: eligibility.policyId,
|
|
186
|
+
} as never)
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
policy: {
|
|
190
|
+
transferPolicyId: compoundEvent.args.policyId,
|
|
191
|
+
senderPolicyId: alwaysAllowPolicyId,
|
|
192
|
+
recipientPolicyId: eligibility.policyId,
|
|
193
|
+
mintRecipientPolicyId: eligibility.policyId,
|
|
194
|
+
},
|
|
195
|
+
receipts: {
|
|
196
|
+
eligibilityPolicy: eligibility.receipt,
|
|
197
|
+
compoundPolicy,
|
|
198
|
+
tokenPolicy: tokenPolicy.receipt,
|
|
199
|
+
policyAdmin: policyAdmin?.receipt,
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export namespace configureExitSafePolicy {
|
|
205
|
+
export type Args = {
|
|
206
|
+
/** Address that will administer recipient eligibility. */
|
|
207
|
+
accessAdministrator: Address
|
|
208
|
+
/** Addresses initially eligible to receive or be minted vault shares. */
|
|
209
|
+
initialMembers: readonly Address[]
|
|
210
|
+
/** Earn vault share token. */
|
|
211
|
+
shareToken: Address
|
|
212
|
+
}
|
|
213
|
+
export type Parameters<
|
|
214
|
+
account extends Account | undefined = Account | undefined,
|
|
215
|
+
> = GetAccountParameter<account> & Args
|
|
216
|
+
export type ReturnValue = Compute<{
|
|
217
|
+
/** Configured onchain policy IDs. */
|
|
218
|
+
policy: ExitSafePolicy
|
|
219
|
+
/** Receipts for each configuration transaction. */
|
|
220
|
+
receipts: ExitSafePolicyReceipts
|
|
221
|
+
}>
|
|
222
|
+
// TODO: exhaustive error type
|
|
223
|
+
export type ErrorType = BaseErrorType
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Verifies that an Earn vault share token uses the expected exit-safe TIP-403
|
|
228
|
+
* policy and that every required member can receive transfers and mints.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* import { createClient, http } from 'viem'
|
|
233
|
+
* import { tempoModerato } from 'viem/chains'
|
|
234
|
+
* import { Actions } from 'viem/tempo'
|
|
235
|
+
*
|
|
236
|
+
* const client = createClient({
|
|
237
|
+
* chain: tempoModerato,
|
|
238
|
+
* transport: http(),
|
|
239
|
+
* })
|
|
240
|
+
*
|
|
241
|
+
* await Actions.earn.validateExitSafePolicy(client, {
|
|
242
|
+
* accessAdministrator: '0x...',
|
|
243
|
+
* policy: {
|
|
244
|
+
* transferPolicyId: 3n,
|
|
245
|
+
* senderPolicyId: 1n,
|
|
246
|
+
* recipientPolicyId: 2n,
|
|
247
|
+
* mintRecipientPolicyId: 2n,
|
|
248
|
+
* },
|
|
249
|
+
* requiredMembers: ['0x...', '0x...'],
|
|
250
|
+
* shareToken: '0x...',
|
|
251
|
+
* })
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* @param client - Client.
|
|
255
|
+
* @param parameters - Expected policy, administrator, and required members.
|
|
256
|
+
* @returns Nothing when the policy is valid.
|
|
257
|
+
*/
|
|
258
|
+
export async function validateExitSafePolicy<chain extends Chain | undefined>(
|
|
259
|
+
client: Client<Transport, chain>,
|
|
260
|
+
parameters: validateExitSafePolicy.Parameters,
|
|
261
|
+
): Promise<validateExitSafePolicy.ReturnValue> {
|
|
262
|
+
const { accessAdministrator, policy, requiredMembers, shareToken, ...rest } =
|
|
263
|
+
parameters
|
|
264
|
+
const [tokenPolicyId, compound, simplePolicy, memberResults] =
|
|
265
|
+
await Promise.all([
|
|
266
|
+
readContract(client, {
|
|
267
|
+
...rest,
|
|
268
|
+
abi: Abis.tip20,
|
|
269
|
+
address: shareToken,
|
|
270
|
+
functionName: 'transferPolicyId',
|
|
271
|
+
}),
|
|
272
|
+
readContract(client, {
|
|
273
|
+
...rest,
|
|
274
|
+
abi: Abis.tip403Registry,
|
|
275
|
+
address: Addresses.tip403Registry,
|
|
276
|
+
args: [policy.transferPolicyId],
|
|
277
|
+
functionName: 'compoundPolicyData',
|
|
278
|
+
}),
|
|
279
|
+
readContract(client, {
|
|
280
|
+
...rest,
|
|
281
|
+
abi: Abis.tip403Registry,
|
|
282
|
+
address: Addresses.tip403Registry,
|
|
283
|
+
args: [policy.recipientPolicyId],
|
|
284
|
+
functionName: 'policyData',
|
|
285
|
+
}),
|
|
286
|
+
Promise.all(
|
|
287
|
+
requiredMembers.map(async (member) => {
|
|
288
|
+
const [recipient, mintRecipient] = await Promise.all([
|
|
289
|
+
readContract(client, {
|
|
290
|
+
...rest,
|
|
291
|
+
abi: Abis.tip403Registry,
|
|
292
|
+
address: Addresses.tip403Registry,
|
|
293
|
+
args: [policy.transferPolicyId, member],
|
|
294
|
+
functionName: 'isAuthorizedRecipient',
|
|
295
|
+
}),
|
|
296
|
+
readContract(client, {
|
|
297
|
+
...rest,
|
|
298
|
+
abi: Abis.tip403Registry,
|
|
299
|
+
address: Addresses.tip403Registry,
|
|
300
|
+
args: [policy.transferPolicyId, member],
|
|
301
|
+
functionName: 'isAuthorizedMintRecipient',
|
|
302
|
+
}),
|
|
303
|
+
])
|
|
304
|
+
return { member, mintRecipient, recipient }
|
|
305
|
+
}),
|
|
306
|
+
),
|
|
307
|
+
])
|
|
308
|
+
|
|
309
|
+
if (tokenPolicyId !== policy.transferPolicyId)
|
|
310
|
+
throw new Error('Earn vault share token transfer policy mismatch.')
|
|
311
|
+
if (
|
|
312
|
+
compound[0] !== policy.senderPolicyId ||
|
|
313
|
+
compound[1] !== policy.recipientPolicyId ||
|
|
314
|
+
compound[2] !== policy.mintRecipientPolicyId
|
|
315
|
+
)
|
|
316
|
+
throw new Error('TIP-403 compound policy components mismatch.')
|
|
317
|
+
if (policy.senderPolicyId !== alwaysAllowPolicyId)
|
|
318
|
+
throw new Error('TIP-403 sender policy is not always allow.')
|
|
319
|
+
if (policy.recipientPolicyId !== policy.mintRecipientPolicyId)
|
|
320
|
+
throw new Error('TIP-403 recipient and mint-recipient policies must match.')
|
|
321
|
+
if (simplePolicy[0] !== 0)
|
|
322
|
+
throw new Error('TIP-403 eligibility policy is not a whitelist.')
|
|
323
|
+
if (!isAddressEqual(simplePolicy[1], accessAdministrator))
|
|
324
|
+
throw new Error('TIP-403 access administrator mismatch.')
|
|
325
|
+
const unauthorized = memberResults.find(
|
|
326
|
+
(result) => !result.recipient || !result.mintRecipient,
|
|
327
|
+
)
|
|
328
|
+
if (unauthorized)
|
|
329
|
+
throw new Error(
|
|
330
|
+
`Required TIP-403 member is unauthorized: ${unauthorized.member}`,
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export namespace validateExitSafePolicy {
|
|
335
|
+
export type Args = {
|
|
336
|
+
/** Expected eligibility policy administrator. */
|
|
337
|
+
accessAdministrator: Address
|
|
338
|
+
/** Expected exit-safe policy IDs. */
|
|
339
|
+
policy: ExitSafePolicy
|
|
340
|
+
/** Addresses that must be eligible to receive or be minted vault shares. */
|
|
341
|
+
requiredMembers: readonly Address[]
|
|
342
|
+
/** Earn vault share token. */
|
|
343
|
+
shareToken: Address
|
|
344
|
+
}
|
|
345
|
+
export type Parameters = Omit<ReadParameters, 'account'> & Args
|
|
346
|
+
export type ReturnValue = void
|
|
347
|
+
// TODO: exhaustive error type
|
|
348
|
+
export type ErrorType = BaseErrorType
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Deposits assets into a vault and mints vault shares to `recipient`. The
|
|
353
|
+
* transaction includes the required asset approval.
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```ts
|
|
357
|
+
* import { createClient, http } from 'viem'
|
|
358
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
359
|
+
* import { tempoModerato } from 'viem/chains'
|
|
360
|
+
* import { Actions } from 'viem/tempo'
|
|
361
|
+
*
|
|
362
|
+
* const client = createClient({
|
|
363
|
+
* account: privateKeyToAccount('0x...'),
|
|
364
|
+
* chain: tempoModerato,
|
|
365
|
+
* transport: http(),
|
|
366
|
+
* })
|
|
367
|
+
*
|
|
368
|
+
* const hash = await Actions.earn.deposit(client, {
|
|
369
|
+
* assetAmount: 100_000_000n,
|
|
370
|
+
* shareAmount: 99_900_000n,
|
|
371
|
+
* slippageBps: 50,
|
|
372
|
+
* vault: '0x...',
|
|
373
|
+
* })
|
|
374
|
+
* ```
|
|
375
|
+
*
|
|
376
|
+
* @param client - Client.
|
|
377
|
+
* @param parameters - Parameters.
|
|
378
|
+
* @returns The transaction hash.
|
|
379
|
+
*/
|
|
380
|
+
export async function deposit<
|
|
381
|
+
chain extends Chain | undefined,
|
|
382
|
+
account extends Account | undefined,
|
|
383
|
+
>(
|
|
384
|
+
client: Client<Transport, chain, account>,
|
|
385
|
+
parameters: deposit.Parameters<chain, account>,
|
|
386
|
+
): Promise<deposit.ReturnValue> {
|
|
387
|
+
return deposit.inner(sendTransaction, client, parameters)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export namespace deposit {
|
|
391
|
+
export type Args = {
|
|
392
|
+
/** Assets to deposit; base units or `{ formatted, decimals? }` (asset decimals). */
|
|
393
|
+
assetAmount: internal_Token.AmountInput
|
|
394
|
+
/** Vault share recipient. @default `account.address` */
|
|
395
|
+
recipient?: Address | undefined
|
|
396
|
+
/** Vault address. */
|
|
397
|
+
vault: Address
|
|
398
|
+
} & OneOf<
|
|
399
|
+
| {
|
|
400
|
+
/** Minimum vault share output to accept; must be greater than zero. */
|
|
401
|
+
shareAmountMin: bigint
|
|
402
|
+
}
|
|
403
|
+
| {
|
|
404
|
+
/** Quoted vault share output; floored by `slippageBps`. */
|
|
405
|
+
shareAmount: bigint
|
|
406
|
+
/** Slippage tolerance in basis points under `shareAmount` (50 = 0.5%). */
|
|
407
|
+
slippageBps: number
|
|
408
|
+
}
|
|
409
|
+
>
|
|
410
|
+
export type Parameters<
|
|
411
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
412
|
+
account extends Account | undefined = Account | undefined,
|
|
413
|
+
> = WriteParameters<chain, account> & Args
|
|
414
|
+
export type ReturnValue = SendTransactionReturnType
|
|
415
|
+
// TODO: exhaustive error type
|
|
416
|
+
export type ErrorType = BaseErrorType
|
|
417
|
+
|
|
418
|
+
/** @internal Shared dispatch; reads the asset for the approval. */
|
|
419
|
+
export async function inner<
|
|
420
|
+
action extends typeof sendTransaction | typeof sendTransactionSync,
|
|
421
|
+
chain extends Chain | undefined,
|
|
422
|
+
account extends Account | undefined,
|
|
423
|
+
>(
|
|
424
|
+
action: action,
|
|
425
|
+
client: Client<Transport, chain, account>,
|
|
426
|
+
parameters: deposit.Parameters<chain, account>,
|
|
427
|
+
): Promise<ReturnType<action>> {
|
|
428
|
+
const [args, assetToken] = await Promise.all([
|
|
429
|
+
toDepositArgs(client, parameters as never),
|
|
430
|
+
readContract(client, {
|
|
431
|
+
abi: Abis.vaultAdapter,
|
|
432
|
+
address: parameters.vault,
|
|
433
|
+
functionName: 'asset',
|
|
434
|
+
}),
|
|
435
|
+
])
|
|
436
|
+
return (await action(client, {
|
|
437
|
+
...parameters,
|
|
438
|
+
calls: deposit.calls({ ...args, assetToken }),
|
|
439
|
+
} as never)) as never
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Defines a deposit call without an approval. Provide token decimals for
|
|
444
|
+
* formatted inputs and an explicit output bound because this builder performs no reads.
|
|
445
|
+
*
|
|
446
|
+
* @param parameters - Client (optional), followed by the call arguments.
|
|
447
|
+
* @returns The call.
|
|
448
|
+
*/
|
|
449
|
+
export function call<chain extends Chain | undefined>(
|
|
450
|
+
...parameters: CallParameters<call.Args, Client<Transport, chain>>
|
|
451
|
+
) {
|
|
452
|
+
const [, args] = resolveCallParameters(parameters)
|
|
453
|
+
const { recipient, vault } = args
|
|
454
|
+
const shareAmountMin = (() => {
|
|
455
|
+
if (args.shareAmountMin !== undefined) return args.shareAmountMin
|
|
456
|
+
return EarnShares.minimumOutput(args.shareAmount, args.slippageBps)
|
|
457
|
+
})()
|
|
458
|
+
return defineCall({
|
|
459
|
+
address: vault,
|
|
460
|
+
abi: Abis.vaultAdapter,
|
|
461
|
+
functionName: 'deposit',
|
|
462
|
+
args: [
|
|
463
|
+
internal_Token.toBaseUnits(args.assetAmount, undefined),
|
|
464
|
+
recipient,
|
|
465
|
+
shareAmountMin,
|
|
466
|
+
],
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
export namespace call {
|
|
470
|
+
export type Args = {
|
|
471
|
+
/** Assets to deposit; base units or `{ formatted, decimals? }` (asset decimals). */
|
|
472
|
+
assetAmount: internal_Token.AmountInput
|
|
473
|
+
/** Vault share recipient. */
|
|
474
|
+
recipient: Address
|
|
475
|
+
/** Vault address. */
|
|
476
|
+
vault: Address
|
|
477
|
+
} & OneOf<
|
|
478
|
+
| {
|
|
479
|
+
/** Minimum vault share output to accept. */
|
|
480
|
+
shareAmountMin: bigint
|
|
481
|
+
}
|
|
482
|
+
| {
|
|
483
|
+
/** Quoted vault share output; floored by `slippageBps`. */
|
|
484
|
+
shareAmount: bigint
|
|
485
|
+
/** Slippage tolerance in basis points under `shareAmount` (50 = 0.5%). */
|
|
486
|
+
slippageBps: number
|
|
487
|
+
}
|
|
488
|
+
>
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Defines the asset approval and deposit calls for atomic execution. Pass
|
|
493
|
+
* `assetToken` and token decimals explicitly because this builder performs no reads.
|
|
494
|
+
*
|
|
495
|
+
* @param args - Arguments.
|
|
496
|
+
* @returns The calls.
|
|
497
|
+
*/
|
|
498
|
+
export function calls(
|
|
499
|
+
args: call.Args & {
|
|
500
|
+
/** Asset token approved for the deposit. */
|
|
501
|
+
assetToken: TokenId.TokenIdOrAddress
|
|
502
|
+
},
|
|
503
|
+
) {
|
|
504
|
+
const { assetToken, vault } = args
|
|
505
|
+
const assetAmount = internal_Token.toBaseUnits(args.assetAmount, undefined)
|
|
506
|
+
return [
|
|
507
|
+
defineCall({
|
|
508
|
+
address: TokenId.toAddress(assetToken),
|
|
509
|
+
abi: Abis.tip20,
|
|
510
|
+
functionName: 'approve',
|
|
511
|
+
args: [vault, assetAmount],
|
|
512
|
+
}),
|
|
513
|
+
deposit.call({ ...args, assetAmount }),
|
|
514
|
+
]
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Extracts a `Deposited` event from the vault's logs.
|
|
519
|
+
*
|
|
520
|
+
* @param logs - Logs.
|
|
521
|
+
* @param parameters - Parameters.
|
|
522
|
+
* @returns The `Deposited` event.
|
|
523
|
+
*/
|
|
524
|
+
export function extractEvent(logs: Log[], parameters: { vault: Address }) {
|
|
525
|
+
const { vault } = parameters
|
|
526
|
+
// Earn contracts are user-deployed: several adapters can emit the same
|
|
527
|
+
// signature in one receipt, so filter by emitting address before decode.
|
|
528
|
+
const [log] = parseEventLogs({
|
|
529
|
+
abi: Abis.vaultAdapter,
|
|
530
|
+
eventName: 'Deposited',
|
|
531
|
+
logs: logs.filter((log) => isAddressEqual(log.address, vault)),
|
|
532
|
+
})
|
|
533
|
+
if (!log) throw new Error('`Deposited` event not found.')
|
|
534
|
+
return log
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Estimates gas for a deposit, assuming the vault has enough asset allowance.
|
|
539
|
+
*
|
|
540
|
+
* @param client - Client.
|
|
541
|
+
* @param parameters - Parameters.
|
|
542
|
+
* @returns The gas estimate.
|
|
543
|
+
*/
|
|
544
|
+
export async function estimateGas<
|
|
545
|
+
chain extends Chain | undefined,
|
|
546
|
+
account extends Account | undefined,
|
|
547
|
+
>(
|
|
548
|
+
client: Client<Transport, chain, account>,
|
|
549
|
+
parameters: deposit.Parameters<chain, account>,
|
|
550
|
+
): Promise<bigint> {
|
|
551
|
+
return estimateContractGas(client, {
|
|
552
|
+
...pickWriteParameters(parameters as never),
|
|
553
|
+
...deposit.call(await toDepositArgs(client, parameters as never)),
|
|
554
|
+
} as never)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Simulates a deposit, assuming the vault has enough asset allowance.
|
|
559
|
+
*
|
|
560
|
+
* @param client - Client.
|
|
561
|
+
* @param parameters - Parameters.
|
|
562
|
+
* @returns The simulation result and write request.
|
|
563
|
+
*/
|
|
564
|
+
export async function simulate<
|
|
565
|
+
chain extends Chain | undefined,
|
|
566
|
+
account extends Account | undefined,
|
|
567
|
+
>(
|
|
568
|
+
client: Client<Transport, chain, account>,
|
|
569
|
+
parameters: deposit.Parameters<chain, account>,
|
|
570
|
+
): Promise<SimulateContractReturnType<typeof Abis.vaultAdapter, 'deposit'>> {
|
|
571
|
+
return simulateContract(client, {
|
|
572
|
+
...pickWriteParameters(parameters as never),
|
|
573
|
+
...deposit.call(await toDepositArgs(client, parameters as never)),
|
|
574
|
+
} as never) as never
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Deposits assets and returns the confirmed receipt and event data.
|
|
580
|
+
*
|
|
581
|
+
* @example
|
|
582
|
+
* ```ts
|
|
583
|
+
* import { createClient, http } from 'viem'
|
|
584
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
585
|
+
* import { tempoModerato } from 'viem/chains'
|
|
586
|
+
* import { Actions, EarnShares } from 'viem/tempo'
|
|
587
|
+
*
|
|
588
|
+
* const client = createClient({
|
|
589
|
+
* account: privateKeyToAccount('0x...'),
|
|
590
|
+
* chain: tempoModerato,
|
|
591
|
+
* transport: http(),
|
|
592
|
+
* })
|
|
593
|
+
*
|
|
594
|
+
* const { shareAmount } = await Actions.earn.depositSync(client, {
|
|
595
|
+
* assetAmount: 100_000_000n,
|
|
596
|
+
* shareAmountMin: EarnShares.minimumOutput(99_900_000n, 50),
|
|
597
|
+
* vault: '0x...',
|
|
598
|
+
* })
|
|
599
|
+
* ```
|
|
600
|
+
*
|
|
601
|
+
* @param client - Client.
|
|
602
|
+
* @param parameters - Parameters.
|
|
603
|
+
* @returns The transaction receipt and event data.
|
|
604
|
+
*/
|
|
605
|
+
export async function depositSync<
|
|
606
|
+
chain extends Chain | undefined,
|
|
607
|
+
account extends Account | undefined,
|
|
608
|
+
>(
|
|
609
|
+
client: Client<Transport, chain, account>,
|
|
610
|
+
parameters: depositSync.Parameters<chain, account>,
|
|
611
|
+
): Promise<depositSync.ReturnValue> {
|
|
612
|
+
const { throwOnReceiptRevert = true, vault } = parameters
|
|
613
|
+
const receipt = await deposit.inner(sendTransactionSync, client, {
|
|
614
|
+
...parameters,
|
|
615
|
+
throwOnReceiptRevert,
|
|
616
|
+
} as never)
|
|
617
|
+
const { args } = deposit.extractEvent(receipt.logs, { vault })
|
|
618
|
+
return {
|
|
619
|
+
assetAmount: args.assets,
|
|
620
|
+
caller: args.caller,
|
|
621
|
+
receipt,
|
|
622
|
+
recipient: args.receiver,
|
|
623
|
+
shareAmount: args.shares,
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export namespace depositSync {
|
|
628
|
+
export type Args = deposit.Args
|
|
629
|
+
export type Parameters<
|
|
630
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
631
|
+
account extends Account | undefined = Account | undefined,
|
|
632
|
+
> = deposit.Parameters<chain, account> & WriteSyncParameters<chain, account>
|
|
633
|
+
export type ReturnValue = Compute<{
|
|
634
|
+
/** Assets deposited. */
|
|
635
|
+
assetAmount: bigint
|
|
636
|
+
/** Depositing caller. */
|
|
637
|
+
caller: Address
|
|
638
|
+
/** Transaction receipt. */
|
|
639
|
+
receipt: TransactionReceipt
|
|
640
|
+
/** Vault share recipient. */
|
|
641
|
+
recipient: Address
|
|
642
|
+
/** Vault shares minted. */
|
|
643
|
+
shareAmount: bigint
|
|
644
|
+
}>
|
|
645
|
+
// TODO: exhaustive error type
|
|
646
|
+
export type ErrorType = BaseErrorType
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Deposits venue shares into a vault and mints vault shares to `recipient`.
|
|
651
|
+
* The transaction includes the required venue share approval.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* ```ts
|
|
655
|
+
* import { createClient, http } from 'viem'
|
|
656
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
657
|
+
* import { tempoModerato } from 'viem/chains'
|
|
658
|
+
* import { Actions } from 'viem/tempo'
|
|
659
|
+
*
|
|
660
|
+
* const client = createClient({
|
|
661
|
+
* account: privateKeyToAccount('0x...'),
|
|
662
|
+
* chain: tempoModerato,
|
|
663
|
+
* transport: http(),
|
|
664
|
+
* })
|
|
665
|
+
*
|
|
666
|
+
* const hash = await Actions.earn.depositShares(client, {
|
|
667
|
+
* earnShareAmount: 499_000_000n,
|
|
668
|
+
* slippageBps: 30,
|
|
669
|
+
* vault: '0x...',
|
|
670
|
+
* venueShareAmount: 500_000_000n,
|
|
671
|
+
* venueShareToken: '0x...',
|
|
672
|
+
* })
|
|
673
|
+
* ```
|
|
674
|
+
*
|
|
675
|
+
* @param client - Client.
|
|
676
|
+
* @param parameters - Parameters.
|
|
677
|
+
* @returns The transaction hash.
|
|
678
|
+
*/
|
|
679
|
+
export async function depositShares<
|
|
680
|
+
chain extends Chain | undefined,
|
|
681
|
+
account extends Account | undefined,
|
|
682
|
+
>(
|
|
683
|
+
client: Client<Transport, chain, account>,
|
|
684
|
+
parameters: depositShares.Parameters<chain, account>,
|
|
685
|
+
): Promise<depositShares.ReturnValue> {
|
|
686
|
+
return depositShares.inner(sendTransaction, client, parameters)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export namespace depositShares {
|
|
690
|
+
export type Args = {
|
|
691
|
+
/** Venue shares to deposit, base units. */
|
|
692
|
+
venueShareAmount: bigint
|
|
693
|
+
/** Vault share recipient. @default `account.address` */
|
|
694
|
+
recipient?: Address | undefined
|
|
695
|
+
/** Vault address. */
|
|
696
|
+
vault: Address
|
|
697
|
+
/** Venue share token approved for the deposit. */
|
|
698
|
+
venueShareToken: Address
|
|
699
|
+
} & OneOf<
|
|
700
|
+
| {
|
|
701
|
+
/** Minimum vault share output to accept; must be greater than zero. */
|
|
702
|
+
earnShareAmountMin: bigint
|
|
703
|
+
}
|
|
704
|
+
| {
|
|
705
|
+
/** Quoted vault share output; floored by `slippageBps`. */
|
|
706
|
+
earnShareAmount: bigint
|
|
707
|
+
/** Slippage tolerance in basis points under `earnShareAmount` (50 = 0.5%). */
|
|
708
|
+
slippageBps: number
|
|
709
|
+
}
|
|
710
|
+
>
|
|
711
|
+
export type Parameters<
|
|
712
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
713
|
+
account extends Account | undefined = Account | undefined,
|
|
714
|
+
> = WriteParameters<chain, account> & Args
|
|
715
|
+
export type ReturnValue = SendTransactionReturnType
|
|
716
|
+
// TODO: exhaustive error type
|
|
717
|
+
export type ErrorType = BaseErrorType
|
|
718
|
+
|
|
719
|
+
/** @internal Shared dispatch; reads the engine for the approval. */
|
|
720
|
+
export async function inner<
|
|
721
|
+
action extends typeof sendTransaction | typeof sendTransactionSync,
|
|
722
|
+
chain extends Chain | undefined,
|
|
723
|
+
account extends Account | undefined,
|
|
724
|
+
>(
|
|
725
|
+
action: action,
|
|
726
|
+
client: Client<Transport, chain, account>,
|
|
727
|
+
parameters: depositShares.Parameters<chain, account>,
|
|
728
|
+
): Promise<ReturnType<action>> {
|
|
729
|
+
const engine = await readContract(client, {
|
|
730
|
+
abi: Abis.vaultAdapter,
|
|
731
|
+
address: parameters.vault,
|
|
732
|
+
functionName: 'engine',
|
|
733
|
+
})
|
|
734
|
+
return (await action(client, {
|
|
735
|
+
...parameters,
|
|
736
|
+
calls: depositShares.calls({
|
|
737
|
+
...toDepositSharesArgs(client, parameters as never),
|
|
738
|
+
engine,
|
|
739
|
+
venueShareToken: parameters.venueShareToken,
|
|
740
|
+
}),
|
|
741
|
+
} as never)) as never
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Defines a venue share deposit call without an approval. Provide an
|
|
746
|
+
* explicit output bound because this builder performs no reads.
|
|
747
|
+
*
|
|
748
|
+
* @param parameters - Client (optional), followed by the call arguments.
|
|
749
|
+
* @returns The call.
|
|
750
|
+
*/
|
|
751
|
+
export function call<chain extends Chain | undefined>(
|
|
752
|
+
...parameters: CallParameters<call.Args, Client<Transport, chain>>
|
|
753
|
+
) {
|
|
754
|
+
const [, args] = resolveCallParameters(parameters)
|
|
755
|
+
const { recipient, vault, venueShareAmount } = args
|
|
756
|
+
const earnShareAmountMin = (() => {
|
|
757
|
+
if (args.earnShareAmountMin !== undefined) return args.earnShareAmountMin
|
|
758
|
+
return EarnShares.minimumOutput(args.earnShareAmount, args.slippageBps)
|
|
759
|
+
})()
|
|
760
|
+
return defineCall({
|
|
761
|
+
address: vault,
|
|
762
|
+
abi: Abis.vaultAdapter,
|
|
763
|
+
functionName: 'depositShares',
|
|
764
|
+
args: [venueShareAmount, recipient, earnShareAmountMin],
|
|
765
|
+
})
|
|
766
|
+
}
|
|
767
|
+
export namespace call {
|
|
768
|
+
export type Args = {
|
|
769
|
+
/** Venue shares to deposit, base units. */
|
|
770
|
+
venueShareAmount: bigint
|
|
771
|
+
/** Vault share recipient. */
|
|
772
|
+
recipient: Address
|
|
773
|
+
/** Vault address. */
|
|
774
|
+
vault: Address
|
|
775
|
+
} & OneOf<
|
|
776
|
+
| {
|
|
777
|
+
/** Minimum vault share output to accept. */
|
|
778
|
+
earnShareAmountMin: bigint
|
|
779
|
+
}
|
|
780
|
+
| {
|
|
781
|
+
/** Quoted vault share output; floored by `slippageBps`. */
|
|
782
|
+
earnShareAmount: bigint
|
|
783
|
+
/** Slippage tolerance in basis points under `earnShareAmount` (50 = 0.5%). */
|
|
784
|
+
slippageBps: number
|
|
785
|
+
}
|
|
786
|
+
>
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Defines the venue share approval and deposit calls for atomic execution.
|
|
791
|
+
* Pass the vault's current `engine` and `venueShareToken` explicitly.
|
|
792
|
+
*
|
|
793
|
+
* @param args - Arguments.
|
|
794
|
+
* @returns The calls.
|
|
795
|
+
*/
|
|
796
|
+
export function calls(
|
|
797
|
+
args: call.Args & {
|
|
798
|
+
/** Current vault engine that pulls the venue shares. */
|
|
799
|
+
engine: Address
|
|
800
|
+
/** Venue share token pulled by the engine. */
|
|
801
|
+
venueShareToken: Address
|
|
802
|
+
},
|
|
803
|
+
) {
|
|
804
|
+
const { engine, venueShareAmount, venueShareToken } = args
|
|
805
|
+
return [
|
|
806
|
+
defineCall({
|
|
807
|
+
address: venueShareToken,
|
|
808
|
+
abi: Abis.tip20,
|
|
809
|
+
functionName: 'approve',
|
|
810
|
+
args: [engine, venueShareAmount],
|
|
811
|
+
}),
|
|
812
|
+
depositShares.call(args),
|
|
813
|
+
]
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Extracts a `VenueSharesDeposited` event from the vault's logs.
|
|
818
|
+
*
|
|
819
|
+
* @param logs - Logs.
|
|
820
|
+
* @param parameters - Parameters.
|
|
821
|
+
* @returns The `VenueSharesDeposited` event.
|
|
822
|
+
*/
|
|
823
|
+
export function extractEvent(logs: Log[], parameters: { vault: Address }) {
|
|
824
|
+
const { vault } = parameters
|
|
825
|
+
// Earn contracts are user-deployed: several adapters can emit the same
|
|
826
|
+
// signature in one receipt, so filter by emitting address before decode.
|
|
827
|
+
const [log] = parseEventLogs({
|
|
828
|
+
abi: Abis.vaultAdapter,
|
|
829
|
+
eventName: 'VenueSharesDeposited',
|
|
830
|
+
logs: logs.filter((log) => isAddressEqual(log.address, vault)),
|
|
831
|
+
})
|
|
832
|
+
if (!log) throw new Error('`VenueSharesDeposited` event not found.')
|
|
833
|
+
return log
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Estimates gas for a venue share deposit, assuming enough allowance.
|
|
838
|
+
*
|
|
839
|
+
* @param client - Client.
|
|
840
|
+
* @param parameters - Parameters.
|
|
841
|
+
* @returns The gas estimate.
|
|
842
|
+
*/
|
|
843
|
+
export async function estimateGas<
|
|
844
|
+
chain extends Chain | undefined,
|
|
845
|
+
account extends Account | undefined,
|
|
846
|
+
>(
|
|
847
|
+
client: Client<Transport, chain, account>,
|
|
848
|
+
parameters: depositShares.Parameters<chain, account>,
|
|
849
|
+
): Promise<bigint> {
|
|
850
|
+
return estimateContractGas(client, {
|
|
851
|
+
...pickWriteParameters(parameters as never),
|
|
852
|
+
...depositShares.call(toDepositSharesArgs(client, parameters as never)),
|
|
853
|
+
} as never)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Simulates a venue share deposit, assuming enough allowance.
|
|
858
|
+
*
|
|
859
|
+
* @param client - Client.
|
|
860
|
+
* @param parameters - Parameters.
|
|
861
|
+
* @returns The simulation result and write request.
|
|
862
|
+
*/
|
|
863
|
+
export async function simulate<
|
|
864
|
+
chain extends Chain | undefined,
|
|
865
|
+
account extends Account | undefined,
|
|
866
|
+
>(
|
|
867
|
+
client: Client<Transport, chain, account>,
|
|
868
|
+
parameters: depositShares.Parameters<chain, account>,
|
|
869
|
+
): Promise<
|
|
870
|
+
SimulateContractReturnType<typeof Abis.vaultAdapter, 'depositShares'>
|
|
871
|
+
> {
|
|
872
|
+
return simulateContract(client, {
|
|
873
|
+
...pickWriteParameters(parameters as never),
|
|
874
|
+
...depositShares.call(toDepositSharesArgs(client, parameters as never)),
|
|
875
|
+
} as never) as never
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Deposits venue shares and returns the confirmed receipt and event data.
|
|
881
|
+
*
|
|
882
|
+
* @example
|
|
883
|
+
* ```ts
|
|
884
|
+
* import { createClient, http } from 'viem'
|
|
885
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
886
|
+
* import { tempoModerato } from 'viem/chains'
|
|
887
|
+
* import { Actions, EarnShares } from 'viem/tempo'
|
|
888
|
+
*
|
|
889
|
+
* const client = createClient({
|
|
890
|
+
* account: privateKeyToAccount('0x...'),
|
|
891
|
+
* chain: tempoModerato,
|
|
892
|
+
* transport: http(),
|
|
893
|
+
* })
|
|
894
|
+
*
|
|
895
|
+
* const { earnShareAmount } = await Actions.earn.depositSharesSync(client, {
|
|
896
|
+
* earnShareAmount: 499_000_000n,
|
|
897
|
+
* slippageBps: 30,
|
|
898
|
+
* vault: '0x...',
|
|
899
|
+
* venueShareAmount: 500_000_000n,
|
|
900
|
+
* venueShareToken: '0x...',
|
|
901
|
+
* })
|
|
902
|
+
* ```
|
|
903
|
+
*
|
|
904
|
+
* @param client - Client.
|
|
905
|
+
* @param parameters - Parameters.
|
|
906
|
+
* @returns The transaction receipt and event data.
|
|
907
|
+
*/
|
|
908
|
+
export async function depositSharesSync<
|
|
909
|
+
chain extends Chain | undefined,
|
|
910
|
+
account extends Account | undefined,
|
|
911
|
+
>(
|
|
912
|
+
client: Client<Transport, chain, account>,
|
|
913
|
+
parameters: depositSharesSync.Parameters<chain, account>,
|
|
914
|
+
): Promise<depositSharesSync.ReturnValue> {
|
|
915
|
+
const { throwOnReceiptRevert = true, vault } = parameters
|
|
916
|
+
const receipt = await depositShares.inner(sendTransactionSync, client, {
|
|
917
|
+
...parameters,
|
|
918
|
+
throwOnReceiptRevert,
|
|
919
|
+
} as never)
|
|
920
|
+
const { args } = depositShares.extractEvent(receipt.logs, { vault })
|
|
921
|
+
return {
|
|
922
|
+
caller: args.caller,
|
|
923
|
+
earnShareAmount: args.earnShares,
|
|
924
|
+
receipt,
|
|
925
|
+
receivedVenueShareAmount: args.receivedVenueShares,
|
|
926
|
+
recipient: args.receiver,
|
|
927
|
+
venueShareAmount: args.requestedVenueShares,
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export namespace depositSharesSync {
|
|
932
|
+
export type Args = depositShares.Args
|
|
933
|
+
export type Parameters<
|
|
934
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
935
|
+
account extends Account | undefined = Account | undefined,
|
|
936
|
+
> = depositShares.Parameters<chain, account> &
|
|
937
|
+
WriteSyncParameters<chain, account>
|
|
938
|
+
export type ReturnValue = Compute<{
|
|
939
|
+
/** Depositing caller. */
|
|
940
|
+
caller: Address
|
|
941
|
+
/** Vault shares minted. */
|
|
942
|
+
earnShareAmount: bigint
|
|
943
|
+
/** Transaction receipt. */
|
|
944
|
+
receipt: TransactionReceipt
|
|
945
|
+
/** Venue shares measured as received by the engine. */
|
|
946
|
+
receivedVenueShareAmount: bigint
|
|
947
|
+
/** Vault share recipient. */
|
|
948
|
+
recipient: Address
|
|
949
|
+
/** Venue shares requested for pull. */
|
|
950
|
+
venueShareAmount: bigint
|
|
951
|
+
}>
|
|
952
|
+
// TODO: exhaustive error type
|
|
953
|
+
export type ErrorType = BaseErrorType
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Withdraws assets from a Zone and deposits them into a vault on the parent
|
|
958
|
+
* chain. Use {@link privateDeposit.prepare} to build the encrypted callback.
|
|
959
|
+
*
|
|
960
|
+
* @example
|
|
961
|
+
* ```ts
|
|
962
|
+
* const prepared = await Actions.earn.privateDeposit.prepare(parentClient, {
|
|
963
|
+
* assetAmount: 100_000_000n,
|
|
964
|
+
* assetToken: '0x...',
|
|
965
|
+
* gateway: '0x...',
|
|
966
|
+
* recipient: '0x...',
|
|
967
|
+
* recoveryRecipient: '0x...',
|
|
968
|
+
* shareAmountMin: 99_500_000n,
|
|
969
|
+
* vaultAssetAmountMin: 99_000_000n,
|
|
970
|
+
* })
|
|
971
|
+
* const hash = await Actions.earn.privateDeposit(zoneClient, prepared)
|
|
972
|
+
* ```
|
|
973
|
+
*
|
|
974
|
+
* @param client - Zone client.
|
|
975
|
+
* @param parameters - Prepared deposit and transaction parameters.
|
|
976
|
+
* @returns The transaction hash.
|
|
977
|
+
*/
|
|
978
|
+
export async function privateDeposit<
|
|
979
|
+
chain extends Chain | undefined,
|
|
980
|
+
account extends Account | undefined,
|
|
981
|
+
>(
|
|
982
|
+
client: Client<Transport, chain, account>,
|
|
983
|
+
parameters: privateDeposit.Parameters<chain, account>,
|
|
984
|
+
): Promise<privateDeposit.ReturnValue> {
|
|
985
|
+
await assertPreparedZoneRequestChain(client, parameters)
|
|
986
|
+
return zoneActions.requestWithdrawal(client, parameters)
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export namespace privateDeposit {
|
|
990
|
+
export type Args = prepare.ReturnValue
|
|
991
|
+
export type Parameters<
|
|
992
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
993
|
+
account extends Account | undefined = Account | undefined,
|
|
994
|
+
> = WriteParameters<chain, account> & Args
|
|
995
|
+
export type ReturnValue = SendTransactionReturnType
|
|
996
|
+
export type ErrorType = zoneActions.requestWithdrawal.ErrorType
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Builds an encrypted Zone withdrawal that deposits into the gateway's
|
|
1000
|
+
* vault and returns the resulting shares to the Zone.
|
|
1001
|
+
*
|
|
1002
|
+
* @param client - Parent-chain client.
|
|
1003
|
+
* @param parameters - Deposit intent and recovery parameters.
|
|
1004
|
+
* @returns The prepared withdrawal and correlation data.
|
|
1005
|
+
*/
|
|
1006
|
+
export async function prepare<chain extends Chain | undefined>(
|
|
1007
|
+
client: Client<Transport, chain>,
|
|
1008
|
+
parameters: prepare.Parameters,
|
|
1009
|
+
): Promise<prepare.ReturnValue> {
|
|
1010
|
+
const chainId = client.chain?.id
|
|
1011
|
+
if (!chainId) throw new Error('`chain` is required.')
|
|
1012
|
+
const {
|
|
1013
|
+
actionId = Hex.random(32),
|
|
1014
|
+
assetAmount,
|
|
1015
|
+
callbackGas = zoneGatewayCallbackGas,
|
|
1016
|
+
fallbackRecipient = parameters.recoveryRecipient,
|
|
1017
|
+
gateway,
|
|
1018
|
+
recipient,
|
|
1019
|
+
recoveryRecipient,
|
|
1020
|
+
returnMemo,
|
|
1021
|
+
withdrawalMemo,
|
|
1022
|
+
} = parameters
|
|
1023
|
+
const readParameters = pickReadParameters(parameters)
|
|
1024
|
+
const [fromBlock, config] = await Promise.all([
|
|
1025
|
+
getBlockNumber(client, { cacheTime: 0 }),
|
|
1026
|
+
getZoneGatewayConfig(client, { ...readParameters, gateway }),
|
|
1027
|
+
])
|
|
1028
|
+
const assetToken = parameters.assetToken ?? config.vaultAsset
|
|
1029
|
+
const { encrypted, keyIndex } =
|
|
1030
|
+
await zoneActions.encryptedDeposit.prepareRecipient(client, {
|
|
1031
|
+
...readParameters,
|
|
1032
|
+
memo: returnMemo,
|
|
1033
|
+
portalAddress: config.zonePortal,
|
|
1034
|
+
recipient,
|
|
1035
|
+
zoneId: config.zoneId,
|
|
1036
|
+
})
|
|
1037
|
+
const shareAmountMin = resolveMinimumShareAmount(parameters)
|
|
1038
|
+
const direct = isAddressEqual(assetToken, config.vaultAsset)
|
|
1039
|
+
const data = encodeAbiParameters(Abis.zoneGatewayCallbackData, [
|
|
1040
|
+
{
|
|
1041
|
+
flow: 0,
|
|
1042
|
+
outputToken: config.shareToken,
|
|
1043
|
+
keyIndex,
|
|
1044
|
+
encrypted,
|
|
1045
|
+
minVaultAssets: direct
|
|
1046
|
+
? assetAmount
|
|
1047
|
+
: (parameters.vaultAssetAmountMin ?? 0n),
|
|
1048
|
+
minVaultShares: shareAmountMin,
|
|
1049
|
+
minOutputAmount: 0n,
|
|
1050
|
+
actionId,
|
|
1051
|
+
refundRecipient: recoveryRecipient,
|
|
1052
|
+
},
|
|
1053
|
+
])
|
|
1054
|
+
return {
|
|
1055
|
+
actionId,
|
|
1056
|
+
amount: assetAmount,
|
|
1057
|
+
callbackGas,
|
|
1058
|
+
chainId,
|
|
1059
|
+
data,
|
|
1060
|
+
fallbackRecipient,
|
|
1061
|
+
fromBlock,
|
|
1062
|
+
memo: withdrawalMemo,
|
|
1063
|
+
to: gateway,
|
|
1064
|
+
token: assetToken,
|
|
1065
|
+
zoneId: config.zoneId,
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
export namespace prepare {
|
|
1070
|
+
export type Parameters = Omit<ReadParameters, 'account'> & Args
|
|
1071
|
+
export type Args = PrivatePreparationParameters & {
|
|
1072
|
+
/** Assets withdrawn from the Zone, base units. */
|
|
1073
|
+
assetAmount: bigint
|
|
1074
|
+
/** Asset token withdrawn from the Zone. @default gateway vault asset */
|
|
1075
|
+
assetToken?: Address | undefined
|
|
1076
|
+
/** Minimum gateway vault assets accepted after swapping `assetToken`. @default `0n` */
|
|
1077
|
+
vaultAssetAmountMin?: bigint | undefined
|
|
1078
|
+
} & MinimumShareAmountParameters
|
|
1079
|
+
export type ReturnValue = PreparedZoneRequest
|
|
1080
|
+
export type ErrorType = BaseErrorType
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Defines the approval and Zone withdrawal calls for a prepared deposit.
|
|
1085
|
+
*
|
|
1086
|
+
* @param args - Prepared deposit arguments.
|
|
1087
|
+
* @returns The Zone withdrawal calls.
|
|
1088
|
+
*/
|
|
1089
|
+
export function calls(args: Args) {
|
|
1090
|
+
return zoneActions.requestWithdrawal.calls(args)
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Requests a private Zone deposit and waits for the Zone transaction receipt.
|
|
1096
|
+
* The receipt confirms withdrawal acceptance, not the parent-chain deposit.
|
|
1097
|
+
*
|
|
1098
|
+
* @param client - Zone client.
|
|
1099
|
+
* @param parameters - Prepared deposit and transaction parameters.
|
|
1100
|
+
* @returns The Zone transaction receipt and parent-chain withdrawal sender tag.
|
|
1101
|
+
*/
|
|
1102
|
+
export async function privateDepositSync<
|
|
1103
|
+
chain extends Chain | undefined,
|
|
1104
|
+
account extends Account | undefined,
|
|
1105
|
+
>(
|
|
1106
|
+
client: Client<Transport, chain, account>,
|
|
1107
|
+
parameters: privateDepositSync.Parameters<chain, account>,
|
|
1108
|
+
): Promise<privateDepositSync.ReturnValue> {
|
|
1109
|
+
await assertPreparedZoneRequestChain(client, parameters)
|
|
1110
|
+
return zoneActions.requestWithdrawalSync(client, parameters)
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
export namespace privateDepositSync {
|
|
1114
|
+
export type Args = privateDeposit.Args
|
|
1115
|
+
export type Parameters<
|
|
1116
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
1117
|
+
account extends Account | undefined = Account | undefined,
|
|
1118
|
+
> = privateDeposit.Parameters<chain, account> &
|
|
1119
|
+
WriteSyncParameters<chain, account>
|
|
1120
|
+
export type ReturnValue = zoneActions.requestWithdrawalSync.ReturnValue
|
|
1121
|
+
export type ErrorType = zoneActions.requestWithdrawalSync.ErrorType
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Waits for a Zone gateway deposit to complete on the parent chain.
|
|
1126
|
+
*
|
|
1127
|
+
* @example
|
|
1128
|
+
* ```ts
|
|
1129
|
+
* const result = await Actions.earn.waitForPrivateDeposit(parentClient, {
|
|
1130
|
+
* actionId: prepared.actionId,
|
|
1131
|
+
* fromBlock: prepared.fromBlock,
|
|
1132
|
+
* gateway: '0x...',
|
|
1133
|
+
* })
|
|
1134
|
+
* ```
|
|
1135
|
+
*
|
|
1136
|
+
* @param client - Parent-chain client.
|
|
1137
|
+
* @param parameters - Prepared action correlation and polling parameters.
|
|
1138
|
+
* @returns The completed gateway deposit.
|
|
1139
|
+
*/
|
|
1140
|
+
export async function waitForPrivateDeposit<chain extends Chain | undefined>(
|
|
1141
|
+
client: Client<Transport, chain>,
|
|
1142
|
+
parameters: waitForPrivateDeposit.Parameters,
|
|
1143
|
+
): Promise<waitForPrivateDeposit.ReturnType> {
|
|
1144
|
+
const {
|
|
1145
|
+
actionId,
|
|
1146
|
+
fromBlock,
|
|
1147
|
+
gateway,
|
|
1148
|
+
pollingInterval = client.pollingInterval,
|
|
1149
|
+
timeout = 60_000,
|
|
1150
|
+
} = parameters
|
|
1151
|
+
const event = getAbiItem({
|
|
1152
|
+
abi: Abis.zoneGateway,
|
|
1153
|
+
name: 'EarnDeposit',
|
|
1154
|
+
})
|
|
1155
|
+
const observerId = stringify([
|
|
1156
|
+
'waitForPrivateDeposit',
|
|
1157
|
+
client.uid,
|
|
1158
|
+
gateway,
|
|
1159
|
+
actionId,
|
|
1160
|
+
fromBlock,
|
|
1161
|
+
])
|
|
1162
|
+
const { promise, reject, resolve } =
|
|
1163
|
+
withResolvers<waitForPrivateDeposit.ReturnType>()
|
|
1164
|
+
|
|
1165
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
1166
|
+
let unobserve: () => void
|
|
1167
|
+
const cleanup = () => {
|
|
1168
|
+
clearTimeout(timer)
|
|
1169
|
+
unobserve()
|
|
1170
|
+
}
|
|
1171
|
+
const resolve_ = (result: waitForPrivateDeposit.ReturnType) => {
|
|
1172
|
+
cleanup()
|
|
1173
|
+
resolve(result)
|
|
1174
|
+
}
|
|
1175
|
+
const reject_ = (error: unknown) => {
|
|
1176
|
+
cleanup()
|
|
1177
|
+
reject(error)
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
unobserve = observe(
|
|
1181
|
+
observerId,
|
|
1182
|
+
{ reject: reject_, resolve: resolve_ },
|
|
1183
|
+
(emit) => {
|
|
1184
|
+
const unpoll = poll(
|
|
1185
|
+
async () => {
|
|
1186
|
+
try {
|
|
1187
|
+
const [log] = await getLogs(client, {
|
|
1188
|
+
address: gateway,
|
|
1189
|
+
args: { actionId },
|
|
1190
|
+
event,
|
|
1191
|
+
fromBlock,
|
|
1192
|
+
strict: true,
|
|
1193
|
+
toBlock: 'latest',
|
|
1194
|
+
})
|
|
1195
|
+
if (!log) return
|
|
1196
|
+
unpoll()
|
|
1197
|
+
emit.resolve({
|
|
1198
|
+
actionId: log.args.actionId,
|
|
1199
|
+
inputAmount: log.args.inputAmount,
|
|
1200
|
+
inputToken: log.args.inputToken,
|
|
1201
|
+
shares: log.args.shares,
|
|
1202
|
+
tempoBlockNumber: log.blockNumber,
|
|
1203
|
+
vaultAssets: log.args.vaultAssets,
|
|
1204
|
+
zoneDepositHash: log.args.zoneDepositHash,
|
|
1205
|
+
})
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
unpoll()
|
|
1208
|
+
emit.reject(error)
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
{ emitOnBegin: true, interval: pollingInterval },
|
|
1212
|
+
)
|
|
1213
|
+
|
|
1214
|
+
return unpoll
|
|
1215
|
+
},
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
timer = timeout
|
|
1219
|
+
? setTimeout(() => {
|
|
1220
|
+
reject_(new WaitForPrivateDepositTimeoutError({ actionId, gateway }))
|
|
1221
|
+
}, timeout)
|
|
1222
|
+
: undefined
|
|
1223
|
+
|
|
1224
|
+
return await promise
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
export namespace waitForPrivateDeposit {
|
|
1228
|
+
export type Parameters = {
|
|
1229
|
+
/** Correlation id from {@link privateDeposit.prepare}. */
|
|
1230
|
+
actionId: Hex.Hex
|
|
1231
|
+
/** Lower bound for the parent-chain log scan. */
|
|
1232
|
+
fromBlock: bigint
|
|
1233
|
+
/** Zone gateway address. */
|
|
1234
|
+
gateway: Address
|
|
1235
|
+
/** Polling frequency in milliseconds. @default `client.pollingInterval` */
|
|
1236
|
+
pollingInterval?: number | undefined
|
|
1237
|
+
/** Timeout in milliseconds; `0` disables it. @default `60_000` */
|
|
1238
|
+
timeout?: number | undefined
|
|
1239
|
+
}
|
|
1240
|
+
export type ReturnType = {
|
|
1241
|
+
/** Correlation id for the completed deposit. */
|
|
1242
|
+
actionId: Hex.Hex
|
|
1243
|
+
/** Tokens delivered to the gateway, base units. */
|
|
1244
|
+
inputAmount: bigint
|
|
1245
|
+
/** Token delivered to the gateway. */
|
|
1246
|
+
inputToken: Address
|
|
1247
|
+
/** Vault shares returned to the Zone. */
|
|
1248
|
+
shares: bigint
|
|
1249
|
+
/** Parent-chain block containing the gateway event. */
|
|
1250
|
+
tempoBlockNumber: bigint
|
|
1251
|
+
/** Vault assets deposited after any swap. */
|
|
1252
|
+
vaultAssets: bigint
|
|
1253
|
+
/** Encrypted return deposit hash. */
|
|
1254
|
+
zoneDepositHash: Hex.Hex
|
|
1255
|
+
}
|
|
1256
|
+
export type ErrorType =
|
|
1257
|
+
| GetLogsErrorType
|
|
1258
|
+
| ObserveErrorType
|
|
1259
|
+
| PollErrorType
|
|
1260
|
+
| WaitForPrivateDepositTimeoutErrorType
|
|
1261
|
+
| BaseErrorType
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Gets the vault's active fee configuration, pending fees, and fee baselines.
|
|
1266
|
+
*
|
|
1267
|
+
* @example
|
|
1268
|
+
* ```ts
|
|
1269
|
+
* import { createClient, http } from 'viem'
|
|
1270
|
+
* import { tempoModerato } from 'viem/chains'
|
|
1271
|
+
* import { Actions } from 'viem/tempo'
|
|
1272
|
+
*
|
|
1273
|
+
* const client = createClient({
|
|
1274
|
+
* chain: tempoModerato,
|
|
1275
|
+
* transport: http(),
|
|
1276
|
+
* })
|
|
1277
|
+
*
|
|
1278
|
+
* const feeState = await Actions.earn.getFeeState(client, {
|
|
1279
|
+
* vault: '0x...',
|
|
1280
|
+
* })
|
|
1281
|
+
* ```
|
|
1282
|
+
*
|
|
1283
|
+
* @param client - Client.
|
|
1284
|
+
* @param parameters - Parameters.
|
|
1285
|
+
* @returns The active fee configuration, pending fees, and baselines.
|
|
1286
|
+
*/
|
|
1287
|
+
export async function getFeeState<chain extends Chain | undefined>(
|
|
1288
|
+
client: Client<Transport, chain>,
|
|
1289
|
+
parameters: getFeeState.Parameters,
|
|
1290
|
+
): Promise<getFeeState.ReturnValue> {
|
|
1291
|
+
const { recipient, vault, ...rest } = parameters
|
|
1292
|
+
const contracts = [
|
|
1293
|
+
defineCall({
|
|
1294
|
+
address: vault,
|
|
1295
|
+
abi: Abis.vaultAdapter,
|
|
1296
|
+
functionName: 'currentFeeConfigId',
|
|
1297
|
+
}),
|
|
1298
|
+
defineCall({
|
|
1299
|
+
address: vault,
|
|
1300
|
+
abi: Abis.vaultAdapter,
|
|
1301
|
+
functionName: 'feesActive',
|
|
1302
|
+
}),
|
|
1303
|
+
defineCall({
|
|
1304
|
+
address: vault,
|
|
1305
|
+
abi: Abis.vaultAdapter,
|
|
1306
|
+
functionName: 'highWaterMark',
|
|
1307
|
+
}),
|
|
1308
|
+
defineCall({
|
|
1309
|
+
address: vault,
|
|
1310
|
+
abi: Abis.vaultAdapter,
|
|
1311
|
+
functionName: 'previewAccruedFees',
|
|
1312
|
+
}),
|
|
1313
|
+
defineCall({
|
|
1314
|
+
address: vault,
|
|
1315
|
+
abi: Abis.vaultAdapter,
|
|
1316
|
+
functionName: 'targetBase',
|
|
1317
|
+
}),
|
|
1318
|
+
] as const
|
|
1319
|
+
// Stored configs are immutable per id, so a follow-up `feeConfig` read stays
|
|
1320
|
+
// consistent with the batched id.
|
|
1321
|
+
const feeConfig = async (configId: bigint) =>
|
|
1322
|
+
toFeeConfig(
|
|
1323
|
+
await readContract(client, {
|
|
1324
|
+
...rest,
|
|
1325
|
+
abi: Abis.vaultAdapter,
|
|
1326
|
+
address: vault,
|
|
1327
|
+
functionName: 'feeConfig',
|
|
1328
|
+
args: [configId],
|
|
1329
|
+
}),
|
|
1330
|
+
)
|
|
1331
|
+
if (recipient !== undefined) {
|
|
1332
|
+
const [configId, feesActive, highWaterMark, preview, targetBase, shares] =
|
|
1333
|
+
await multicall(client, {
|
|
1334
|
+
...rest,
|
|
1335
|
+
allowFailure: false,
|
|
1336
|
+
contracts: [
|
|
1337
|
+
...contracts,
|
|
1338
|
+
defineCall({
|
|
1339
|
+
address: vault,
|
|
1340
|
+
abi: Abis.vaultAdapter,
|
|
1341
|
+
functionName: 'claimableFeeShares',
|
|
1342
|
+
args: [recipient],
|
|
1343
|
+
}),
|
|
1344
|
+
],
|
|
1345
|
+
deployless: true,
|
|
1346
|
+
})
|
|
1347
|
+
return {
|
|
1348
|
+
claimableShares: shares,
|
|
1349
|
+
config: await feeConfig(configId),
|
|
1350
|
+
configId,
|
|
1351
|
+
feesActive,
|
|
1352
|
+
highWaterMark,
|
|
1353
|
+
preview: toFeePreview(preview),
|
|
1354
|
+
targetBase,
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
const [configId, feesActive, highWaterMark, preview, targetBase] =
|
|
1358
|
+
await multicall(client, {
|
|
1359
|
+
...rest,
|
|
1360
|
+
allowFailure: false,
|
|
1361
|
+
contracts,
|
|
1362
|
+
deployless: true,
|
|
1363
|
+
})
|
|
1364
|
+
return {
|
|
1365
|
+
config: await feeConfig(configId),
|
|
1366
|
+
configId,
|
|
1367
|
+
feesActive,
|
|
1368
|
+
highWaterMark,
|
|
1369
|
+
preview: toFeePreview(preview),
|
|
1370
|
+
targetBase,
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
export namespace getFeeState {
|
|
1375
|
+
export type Args = {
|
|
1376
|
+
/** Optional fee recipient whose claimable vault shares are included. */
|
|
1377
|
+
recipient?: Address | undefined
|
|
1378
|
+
/** Vault address. */
|
|
1379
|
+
vault: Address
|
|
1380
|
+
}
|
|
1381
|
+
export type Parameters = Omit<ReadParameters, 'account'> & Args
|
|
1382
|
+
export type ReturnValue = {
|
|
1383
|
+
/** Claimable fee shares for `recipient`; present when provided. */
|
|
1384
|
+
claimableShares?: bigint | undefined
|
|
1385
|
+
/** Active fee configuration. */
|
|
1386
|
+
config: FeeConfig
|
|
1387
|
+
/** Active fee configuration id (starts at `1`). */
|
|
1388
|
+
configId: bigint
|
|
1389
|
+
/** Whether fees are configured and not emergency-disabled. */
|
|
1390
|
+
feesActive: boolean
|
|
1391
|
+
/** Post-fee high-water mark per vault share. */
|
|
1392
|
+
highWaterMark: bigint
|
|
1393
|
+
/** Pending fee amounts. */
|
|
1394
|
+
preview: FeePreview
|
|
1395
|
+
/** Excess-return fee target per vault share. */
|
|
1396
|
+
targetBase: bigint
|
|
1397
|
+
}
|
|
1398
|
+
// TODO: exhaustive error type
|
|
1399
|
+
export type ErrorType = BaseErrorType
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/** Vault fee configuration. */
|
|
1403
|
+
export type FeeConfig = {
|
|
1404
|
+
/** Optional excess-return fee over a growing target line. */
|
|
1405
|
+
excess: {
|
|
1406
|
+
/** Excess fee recipient. */
|
|
1407
|
+
account: Address
|
|
1408
|
+
/** Annual target growth rate, scaled to 18 decimals. */
|
|
1409
|
+
annualTargetRate: bigint
|
|
1410
|
+
/** Whether the excess fee is active. */
|
|
1411
|
+
enabled: boolean
|
|
1412
|
+
/** Rate applied above the target, scaled to 18 decimals. */
|
|
1413
|
+
excessFeeRate: bigint
|
|
1414
|
+
}
|
|
1415
|
+
/** Fixed fee recipients and their 18-decimal rates. */
|
|
1416
|
+
fixedFees: readonly { account: Address; rate: bigint }[]
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/** Pending vault fee amounts. */
|
|
1420
|
+
export type FeePreview = {
|
|
1421
|
+
/** Assets backing active vault shares. */
|
|
1422
|
+
activeAssets: bigint
|
|
1423
|
+
/** Fee allocations in assets and vault shares. */
|
|
1424
|
+
allocations: readonly {
|
|
1425
|
+
account: Address
|
|
1426
|
+
feeAssets: bigint
|
|
1427
|
+
feeShares: bigint
|
|
1428
|
+
}[]
|
|
1429
|
+
/** Excess-return fee portion, asset units. */
|
|
1430
|
+
excessFeeAssets: bigint
|
|
1431
|
+
/** Fixed fee portion, asset units. */
|
|
1432
|
+
fixedFeeAssets: bigint
|
|
1433
|
+
/** Accrual above the high-water mark, asset units. */
|
|
1434
|
+
positiveAccrualAssets: bigint
|
|
1435
|
+
/** Scaled asset value per vault share after fees. */
|
|
1436
|
+
postFeeValuePerShare: bigint
|
|
1437
|
+
/** Scaled asset value per vault share before fees. */
|
|
1438
|
+
preFeeValuePerShare: bigint
|
|
1439
|
+
/** Scaled excess-fee target per vault share. */
|
|
1440
|
+
targetValuePerShare: bigint
|
|
1441
|
+
/** Total fee liability, asset units. */
|
|
1442
|
+
totalFeeAssets: bigint
|
|
1443
|
+
/** Vault shares minted to cover the total fee. */
|
|
1444
|
+
totalFeeShares: bigint
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* Gets an account's asset and vault share balances, allowances, and current
|
|
1449
|
+
* share value. The value includes fees.
|
|
1450
|
+
*
|
|
1451
|
+
* @example
|
|
1452
|
+
* ```ts
|
|
1453
|
+
* import { createClient, http } from 'viem'
|
|
1454
|
+
* import { tempoModerato } from 'viem/chains'
|
|
1455
|
+
* import { Actions } from 'viem/tempo'
|
|
1456
|
+
*
|
|
1457
|
+
* const client = createClient({
|
|
1458
|
+
* chain: tempoModerato,
|
|
1459
|
+
* transport: http(),
|
|
1460
|
+
* })
|
|
1461
|
+
*
|
|
1462
|
+
* const position = await Actions.earn.getPosition(client, {
|
|
1463
|
+
* account: '0x...',
|
|
1464
|
+
* vault: '0x...',
|
|
1465
|
+
* })
|
|
1466
|
+
* ```
|
|
1467
|
+
*
|
|
1468
|
+
* @param client - Client.
|
|
1469
|
+
* @param parameters - Parameters.
|
|
1470
|
+
* @returns The asset and vault share balances, allowances, and value.
|
|
1471
|
+
*/
|
|
1472
|
+
export async function getPosition<
|
|
1473
|
+
chain extends Chain | undefined,
|
|
1474
|
+
account extends Account | undefined,
|
|
1475
|
+
>(
|
|
1476
|
+
client: Client<Transport, chain, account>,
|
|
1477
|
+
parameters: getPosition.Parameters<account>,
|
|
1478
|
+
): Promise<getPosition.ReturnValue> {
|
|
1479
|
+
const { account: account_ = client.account, vault, ...rest } = parameters
|
|
1480
|
+
if (!account_) throw new AccountNotFoundError()
|
|
1481
|
+
const account = parseAccount(account_).address
|
|
1482
|
+
const [assetToken, shareToken] = await multicall(client, {
|
|
1483
|
+
...rest,
|
|
1484
|
+
allowFailure: false,
|
|
1485
|
+
contracts: [
|
|
1486
|
+
defineCall({
|
|
1487
|
+
address: vault,
|
|
1488
|
+
abi: Abis.vaultAdapter,
|
|
1489
|
+
functionName: 'asset',
|
|
1490
|
+
}),
|
|
1491
|
+
defineCall({
|
|
1492
|
+
address: vault,
|
|
1493
|
+
abi: Abis.vaultAdapter,
|
|
1494
|
+
functionName: 'shareToken',
|
|
1495
|
+
}),
|
|
1496
|
+
],
|
|
1497
|
+
deployless: true,
|
|
1498
|
+
})
|
|
1499
|
+
const [assetAllowance, assetBalance, shareAllowance, shareBalance] =
|
|
1500
|
+
await multicall(client, {
|
|
1501
|
+
...rest,
|
|
1502
|
+
allowFailure: false,
|
|
1503
|
+
contracts: [
|
|
1504
|
+
defineCall({
|
|
1505
|
+
address: assetToken,
|
|
1506
|
+
abi: Abis.tip20,
|
|
1507
|
+
functionName: 'allowance',
|
|
1508
|
+
args: [account, vault],
|
|
1509
|
+
}),
|
|
1510
|
+
defineCall({
|
|
1511
|
+
address: assetToken,
|
|
1512
|
+
abi: Abis.tip20,
|
|
1513
|
+
functionName: 'balanceOf',
|
|
1514
|
+
args: [account],
|
|
1515
|
+
}),
|
|
1516
|
+
defineCall({
|
|
1517
|
+
address: shareToken,
|
|
1518
|
+
abi: Abis.tip20,
|
|
1519
|
+
functionName: 'allowance',
|
|
1520
|
+
args: [account, vault],
|
|
1521
|
+
}),
|
|
1522
|
+
defineCall({
|
|
1523
|
+
address: shareToken,
|
|
1524
|
+
abi: Abis.tip20,
|
|
1525
|
+
functionName: 'balanceOf',
|
|
1526
|
+
args: [account],
|
|
1527
|
+
}),
|
|
1528
|
+
],
|
|
1529
|
+
deployless: true,
|
|
1530
|
+
})
|
|
1531
|
+
const value = await readContract(client, {
|
|
1532
|
+
...rest,
|
|
1533
|
+
abi: Abis.vaultAdapter,
|
|
1534
|
+
address: vault,
|
|
1535
|
+
args: [shareBalance],
|
|
1536
|
+
functionName: 'previewRedeem',
|
|
1537
|
+
})
|
|
1538
|
+
return {
|
|
1539
|
+
assetAllowance,
|
|
1540
|
+
assetBalance,
|
|
1541
|
+
assetToken,
|
|
1542
|
+
shareAllowance,
|
|
1543
|
+
shareBalance,
|
|
1544
|
+
shareToken,
|
|
1545
|
+
value,
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
export namespace getPosition {
|
|
1550
|
+
export type Args<account extends Account | undefined = Account | undefined> =
|
|
1551
|
+
GetAccountParameter<account> & {
|
|
1552
|
+
/** Vault address. */
|
|
1553
|
+
vault: Address
|
|
1554
|
+
}
|
|
1555
|
+
export type Parameters<
|
|
1556
|
+
account extends Account | undefined = Account | undefined,
|
|
1557
|
+
> = Omit<ReadParameters, 'account'> & Args<account>
|
|
1558
|
+
export type ReturnValue = {
|
|
1559
|
+
/** Assets the vault may spend from the account. */
|
|
1560
|
+
assetAllowance: bigint
|
|
1561
|
+
/** Asset balance held by the account. */
|
|
1562
|
+
assetBalance: bigint
|
|
1563
|
+
/** Token accepted by the vault. */
|
|
1564
|
+
assetToken: Address
|
|
1565
|
+
/** Vault shares the vault may spend from the account. */
|
|
1566
|
+
shareAllowance: bigint
|
|
1567
|
+
/** Vault share balance held by the account. */
|
|
1568
|
+
shareBalance: bigint
|
|
1569
|
+
/** Token representing vault shares. */
|
|
1570
|
+
shareToken: Address
|
|
1571
|
+
/** Current asset value of the vault share balance, including fees. */
|
|
1572
|
+
value: bigint
|
|
1573
|
+
}
|
|
1574
|
+
// TODO: exhaustive error type
|
|
1575
|
+
export type ErrorType = BaseErrorType
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/**
|
|
1579
|
+
* Gets the asset output for an exact vault share input, including fees.
|
|
1580
|
+
*
|
|
1581
|
+
* @example
|
|
1582
|
+
* ```ts
|
|
1583
|
+
* import { createClient, http } from 'viem'
|
|
1584
|
+
* import { tempoModerato } from 'viem/chains'
|
|
1585
|
+
* import { Actions } from 'viem/tempo'
|
|
1586
|
+
*
|
|
1587
|
+
* const client = createClient({
|
|
1588
|
+
* chain: tempoModerato,
|
|
1589
|
+
* transport: http(),
|
|
1590
|
+
* })
|
|
1591
|
+
*
|
|
1592
|
+
* const assetAmount = await Actions.earn.getRedeemQuote(client, {
|
|
1593
|
+
* shareAmount: 100_000_000n,
|
|
1594
|
+
* vault: '0x...',
|
|
1595
|
+
* })
|
|
1596
|
+
* ```
|
|
1597
|
+
*
|
|
1598
|
+
* @param client - Client.
|
|
1599
|
+
* @param parameters - Parameters.
|
|
1600
|
+
* @returns The asset output, including fees.
|
|
1601
|
+
*/
|
|
1602
|
+
export async function getRedeemQuote<chain extends Chain | undefined>(
|
|
1603
|
+
client: Client<Transport, chain>,
|
|
1604
|
+
parameters: getRedeemQuote.Parameters,
|
|
1605
|
+
): Promise<getRedeemQuote.ReturnValue> {
|
|
1606
|
+
const { shareAmount, vault, ...rest } = parameters
|
|
1607
|
+
return readContract(client, {
|
|
1608
|
+
...rest,
|
|
1609
|
+
...getRedeemQuote.call({ shareAmount, vault }),
|
|
1610
|
+
})
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
export namespace getRedeemQuote {
|
|
1614
|
+
export type Args = {
|
|
1615
|
+
/** Exact vault share input, base units. */
|
|
1616
|
+
shareAmount: bigint
|
|
1617
|
+
/** Vault address. */
|
|
1618
|
+
vault: Address
|
|
1619
|
+
}
|
|
1620
|
+
export type Parameters = Omit<ReadParameters, 'account'> & Args
|
|
1621
|
+
/** Asset output, including fees. */
|
|
1622
|
+
export type ReturnValue = ReadContractReturnType<
|
|
1623
|
+
typeof Abis.vaultAdapter,
|
|
1624
|
+
'previewRedeem',
|
|
1625
|
+
never
|
|
1626
|
+
>
|
|
1627
|
+
// TODO: exhaustive error type
|
|
1628
|
+
export type ErrorType = BaseErrorType
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* Defines a call to the vault's `previewRedeem` function.
|
|
1632
|
+
*
|
|
1633
|
+
* Can be passed as a parameter to:
|
|
1634
|
+
* - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
|
|
1635
|
+
* - [`multicall`](https://viem.sh/docs/contract/multicall): batch the call with other contract reads
|
|
1636
|
+
* - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
|
|
1637
|
+
*
|
|
1638
|
+
* @example
|
|
1639
|
+
* ```ts
|
|
1640
|
+
* import { Actions } from 'viem/tempo'
|
|
1641
|
+
*
|
|
1642
|
+
* const call = Actions.earn.getRedeemQuote.call({
|
|
1643
|
+
* shareAmount: 100_000_000n,
|
|
1644
|
+
* vault: '0x...',
|
|
1645
|
+
* })
|
|
1646
|
+
* ```
|
|
1647
|
+
*
|
|
1648
|
+
* @param args - Arguments.
|
|
1649
|
+
* @returns The call.
|
|
1650
|
+
*/
|
|
1651
|
+
export function call(args: Args) {
|
|
1652
|
+
const { shareAmount, vault } = args
|
|
1653
|
+
return defineCall({
|
|
1654
|
+
address: vault,
|
|
1655
|
+
abi: Abis.vaultAdapter,
|
|
1656
|
+
args: [shareAmount],
|
|
1657
|
+
functionName: 'previewRedeem',
|
|
1658
|
+
})
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Gets the vault's addresses, configuration, accounting state, and supported
|
|
1664
|
+
* actions. Throws {@link GetVaultEngineChangedError} if its engine changes mid-read.
|
|
1665
|
+
*
|
|
1666
|
+
* @example
|
|
1667
|
+
* ```ts
|
|
1668
|
+
* import { createClient, http } from 'viem'
|
|
1669
|
+
* import { tempoModerato } from 'viem/chains'
|
|
1670
|
+
* import { Actions } from 'viem/tempo'
|
|
1671
|
+
*
|
|
1672
|
+
* const client = createClient({
|
|
1673
|
+
* chain: tempoModerato,
|
|
1674
|
+
* transport: http(),
|
|
1675
|
+
* })
|
|
1676
|
+
*
|
|
1677
|
+
* const vault = await Actions.earn.getVault(client, {
|
|
1678
|
+
* vault: '0x...',
|
|
1679
|
+
* })
|
|
1680
|
+
* ```
|
|
1681
|
+
*
|
|
1682
|
+
* @param client - Client.
|
|
1683
|
+
* @param parameters - Parameters.
|
|
1684
|
+
* @returns The vault state and metadata.
|
|
1685
|
+
*/
|
|
1686
|
+
export async function getVault<chain extends Chain | undefined>(
|
|
1687
|
+
client: Client<Transport, chain>,
|
|
1688
|
+
parameters: getVault.Parameters,
|
|
1689
|
+
): Promise<getVault.ReturnValue> {
|
|
1690
|
+
const { vault, ...rest } = parameters
|
|
1691
|
+
const engine = await readContract(client, {
|
|
1692
|
+
...rest,
|
|
1693
|
+
abi: Abis.vaultAdapter,
|
|
1694
|
+
address: vault,
|
|
1695
|
+
functionName: 'engine',
|
|
1696
|
+
})
|
|
1697
|
+
const [
|
|
1698
|
+
assetToken,
|
|
1699
|
+
engine_,
|
|
1700
|
+
shareToken,
|
|
1701
|
+
operator,
|
|
1702
|
+
emergencyGuardian,
|
|
1703
|
+
asyncJanitor,
|
|
1704
|
+
engineMigrationMode,
|
|
1705
|
+
depositsPaused,
|
|
1706
|
+
engineShares,
|
|
1707
|
+
shareSupply,
|
|
1708
|
+
isSynced,
|
|
1709
|
+
pendingRedeemCount,
|
|
1710
|
+
feesActive,
|
|
1711
|
+
totalAssets,
|
|
1712
|
+
name,
|
|
1713
|
+
symbol,
|
|
1714
|
+
asyncRedeem,
|
|
1715
|
+
exactWithdraw,
|
|
1716
|
+
inKindDeposit,
|
|
1717
|
+
syncRedeem,
|
|
1718
|
+
] = await multicall(client, {
|
|
1719
|
+
...rest,
|
|
1720
|
+
allowFailure: false,
|
|
1721
|
+
contracts: getVault.calls({ engine, vault }),
|
|
1722
|
+
deployless: true,
|
|
1723
|
+
})
|
|
1724
|
+
if (!isAddressEqual(engine, engine_))
|
|
1725
|
+
throw new GetVaultEngineChangedError({ vault })
|
|
1726
|
+
return {
|
|
1727
|
+
assetToken,
|
|
1728
|
+
asyncJanitor,
|
|
1729
|
+
capabilities: { asyncRedeem, exactWithdraw, inKindDeposit, syncRedeem },
|
|
1730
|
+
depositsPaused,
|
|
1731
|
+
emergencyGuardian,
|
|
1732
|
+
engine: { address: engine_, name, symbol, totalAssets },
|
|
1733
|
+
// `EngineMigrationMode`: 0 = UserOnly, 1 = OperatorEnabled.
|
|
1734
|
+
engineMigrationMode:
|
|
1735
|
+
engineMigrationMode === 0 ? 'userOnly' : 'operatorEnabled',
|
|
1736
|
+
engineShares,
|
|
1737
|
+
feesActive,
|
|
1738
|
+
isSynced,
|
|
1739
|
+
operator,
|
|
1740
|
+
pendingRedeemCount,
|
|
1741
|
+
shareSupply,
|
|
1742
|
+
shareToken,
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
export namespace getVault {
|
|
1747
|
+
export type Args = {
|
|
1748
|
+
/** Vault address. */
|
|
1749
|
+
vault: Address
|
|
1750
|
+
}
|
|
1751
|
+
export type Parameters = Omit<ReadParameters, 'account'> & Args
|
|
1752
|
+
export type ReturnValue = {
|
|
1753
|
+
/** Token accepted by the vault. */
|
|
1754
|
+
assetToken: Address
|
|
1755
|
+
/** Address allowed to cancel queued redemptions; zero when disabled. */
|
|
1756
|
+
asyncJanitor: Address
|
|
1757
|
+
/** Actions supported by the current venue integration. */
|
|
1758
|
+
capabilities: {
|
|
1759
|
+
/** Queued redemptions. */
|
|
1760
|
+
asyncRedeem: boolean
|
|
1761
|
+
/** Exact asset withdrawals. */
|
|
1762
|
+
exactWithdraw: boolean
|
|
1763
|
+
/** Venue share deposits. */
|
|
1764
|
+
inKindDeposit: boolean
|
|
1765
|
+
/** Immediate redemptions. */
|
|
1766
|
+
syncRedeem: boolean
|
|
1767
|
+
}
|
|
1768
|
+
/** Whether new deposits are paused. */
|
|
1769
|
+
depositsPaused: boolean
|
|
1770
|
+
/** Address allowed to pause deposits; zero when disabled. */
|
|
1771
|
+
emergencyGuardian: Address
|
|
1772
|
+
/** Current venue integration. */
|
|
1773
|
+
engine: {
|
|
1774
|
+
/** Integration address. */
|
|
1775
|
+
address: Address
|
|
1776
|
+
/** Engine display name. */
|
|
1777
|
+
name: string
|
|
1778
|
+
/** Engine display symbol. */
|
|
1779
|
+
symbol: string
|
|
1780
|
+
/** Asset value of the active backing. */
|
|
1781
|
+
totalAssets: bigint
|
|
1782
|
+
}
|
|
1783
|
+
/** Whether only users may change the venue integration. */
|
|
1784
|
+
engineMigrationMode: 'operatorEnabled' | 'userOnly'
|
|
1785
|
+
/** Venue shares held for the vault. */
|
|
1786
|
+
engineShares: bigint
|
|
1787
|
+
/** Whether fees are configured and not emergency-disabled. */
|
|
1788
|
+
feesActive: boolean
|
|
1789
|
+
/** Whether vault share supply matches its asset backing. */
|
|
1790
|
+
isSynced: boolean
|
|
1791
|
+
/** Vault governance address. */
|
|
1792
|
+
operator: Address
|
|
1793
|
+
/** Open queued redemptions. */
|
|
1794
|
+
pendingRedeemCount: bigint
|
|
1795
|
+
/** Active vault share supply. */
|
|
1796
|
+
shareSupply: bigint
|
|
1797
|
+
/** Token representing vault shares. */
|
|
1798
|
+
shareToken: Address
|
|
1799
|
+
}
|
|
1800
|
+
// TODO: exhaustive error type
|
|
1801
|
+
export type ErrorType = GetVaultEngineChangedErrorType | BaseErrorType
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* Defines the reads used by {@link getVault}. Pass the vault's current
|
|
1805
|
+
* engine address.
|
|
1806
|
+
*
|
|
1807
|
+
* @param args - Arguments.
|
|
1808
|
+
* @returns The calls.
|
|
1809
|
+
*/
|
|
1810
|
+
export function calls(args: Args & { engine: Address }) {
|
|
1811
|
+
const { engine, vault } = args
|
|
1812
|
+
return [
|
|
1813
|
+
defineCall({
|
|
1814
|
+
address: vault,
|
|
1815
|
+
abi: Abis.vaultAdapter,
|
|
1816
|
+
functionName: 'asset',
|
|
1817
|
+
}),
|
|
1818
|
+
defineCall({
|
|
1819
|
+
address: vault,
|
|
1820
|
+
abi: Abis.vaultAdapter,
|
|
1821
|
+
functionName: 'engine',
|
|
1822
|
+
}),
|
|
1823
|
+
defineCall({
|
|
1824
|
+
address: vault,
|
|
1825
|
+
abi: Abis.vaultAdapter,
|
|
1826
|
+
functionName: 'shareToken',
|
|
1827
|
+
}),
|
|
1828
|
+
defineCall({
|
|
1829
|
+
address: vault,
|
|
1830
|
+
abi: Abis.vaultAdapter,
|
|
1831
|
+
functionName: 'operator',
|
|
1832
|
+
}),
|
|
1833
|
+
defineCall({
|
|
1834
|
+
address: vault,
|
|
1835
|
+
abi: Abis.vaultAdapter,
|
|
1836
|
+
functionName: 'emergencyGuardian',
|
|
1837
|
+
}),
|
|
1838
|
+
defineCall({
|
|
1839
|
+
address: vault,
|
|
1840
|
+
abi: Abis.vaultAdapter,
|
|
1841
|
+
functionName: 'asyncJanitor',
|
|
1842
|
+
}),
|
|
1843
|
+
defineCall({
|
|
1844
|
+
address: vault,
|
|
1845
|
+
abi: Abis.vaultAdapter,
|
|
1846
|
+
functionName: 'engineMigrationMode',
|
|
1847
|
+
}),
|
|
1848
|
+
defineCall({
|
|
1849
|
+
address: vault,
|
|
1850
|
+
abi: Abis.vaultAdapter,
|
|
1851
|
+
functionName: 'depositsPaused',
|
|
1852
|
+
}),
|
|
1853
|
+
defineCall({
|
|
1854
|
+
address: vault,
|
|
1855
|
+
abi: Abis.vaultAdapter,
|
|
1856
|
+
functionName: 'engineShares',
|
|
1857
|
+
}),
|
|
1858
|
+
defineCall({
|
|
1859
|
+
address: vault,
|
|
1860
|
+
abi: Abis.vaultAdapter,
|
|
1861
|
+
functionName: 'shareSupply',
|
|
1862
|
+
}),
|
|
1863
|
+
defineCall({
|
|
1864
|
+
address: vault,
|
|
1865
|
+
abi: Abis.vaultAdapter,
|
|
1866
|
+
functionName: 'isSynced',
|
|
1867
|
+
}),
|
|
1868
|
+
defineCall({
|
|
1869
|
+
address: vault,
|
|
1870
|
+
abi: Abis.vaultAdapter,
|
|
1871
|
+
functionName: 'pendingRedeemCount',
|
|
1872
|
+
}),
|
|
1873
|
+
defineCall({
|
|
1874
|
+
address: vault,
|
|
1875
|
+
abi: Abis.vaultAdapter,
|
|
1876
|
+
functionName: 'feesActive',
|
|
1877
|
+
}),
|
|
1878
|
+
defineCall({
|
|
1879
|
+
address: engine,
|
|
1880
|
+
abi: Abis.vaultEngine,
|
|
1881
|
+
functionName: 'totalAssets',
|
|
1882
|
+
}),
|
|
1883
|
+
defineCall({
|
|
1884
|
+
address: engine,
|
|
1885
|
+
abi: Abis.vaultEngine,
|
|
1886
|
+
functionName: 'name',
|
|
1887
|
+
}),
|
|
1888
|
+
defineCall({
|
|
1889
|
+
address: engine,
|
|
1890
|
+
abi: Abis.vaultEngine,
|
|
1891
|
+
functionName: 'symbol',
|
|
1892
|
+
}),
|
|
1893
|
+
defineCall({
|
|
1894
|
+
address: engine,
|
|
1895
|
+
abi: Abis.vaultEngine,
|
|
1896
|
+
functionName: 'supportsInterface',
|
|
1897
|
+
args: [interfaceIds.asyncRedeem],
|
|
1898
|
+
}),
|
|
1899
|
+
defineCall({
|
|
1900
|
+
address: engine,
|
|
1901
|
+
abi: Abis.vaultEngine,
|
|
1902
|
+
functionName: 'supportsInterface',
|
|
1903
|
+
args: [interfaceIds.exactWithdraw],
|
|
1904
|
+
}),
|
|
1905
|
+
defineCall({
|
|
1906
|
+
address: engine,
|
|
1907
|
+
abi: Abis.vaultEngine,
|
|
1908
|
+
functionName: 'supportsInterface',
|
|
1909
|
+
args: [interfaceIds.inKindDeposit],
|
|
1910
|
+
}),
|
|
1911
|
+
defineCall({
|
|
1912
|
+
address: engine,
|
|
1913
|
+
abi: Abis.vaultEngine,
|
|
1914
|
+
functionName: 'supportsInterface',
|
|
1915
|
+
args: [interfaceIds.syncRedeem],
|
|
1916
|
+
}),
|
|
1917
|
+
] as const
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
/**
|
|
1922
|
+
* Gets the vault shares required for an exact asset output, including fees
|
|
1923
|
+
* and ceiling rounding.
|
|
1924
|
+
*
|
|
1925
|
+
* @example
|
|
1926
|
+
* ```ts
|
|
1927
|
+
* import { createClient, http } from 'viem'
|
|
1928
|
+
* import { tempoModerato } from 'viem/chains'
|
|
1929
|
+
* import { Actions } from 'viem/tempo'
|
|
1930
|
+
*
|
|
1931
|
+
* const client = createClient({
|
|
1932
|
+
* chain: tempoModerato,
|
|
1933
|
+
* transport: http(),
|
|
1934
|
+
* })
|
|
1935
|
+
*
|
|
1936
|
+
* const shareAmount = await Actions.earn.getWithdrawQuote(client, {
|
|
1937
|
+
* assetAmount: 250_000_000n,
|
|
1938
|
+
* vault: '0x...',
|
|
1939
|
+
* })
|
|
1940
|
+
* ```
|
|
1941
|
+
*
|
|
1942
|
+
* @param client - Client.
|
|
1943
|
+
* @param parameters - Parameters.
|
|
1944
|
+
* @returns The required vault share input, ceiling-rounded.
|
|
1945
|
+
*/
|
|
1946
|
+
export async function getWithdrawQuote<chain extends Chain | undefined>(
|
|
1947
|
+
client: Client<Transport, chain>,
|
|
1948
|
+
parameters: getWithdrawQuote.Parameters,
|
|
1949
|
+
): Promise<getWithdrawQuote.ReturnValue> {
|
|
1950
|
+
const { assetAmount, vault, ...rest } = parameters
|
|
1951
|
+
return readContract(client, {
|
|
1952
|
+
...rest,
|
|
1953
|
+
...getWithdrawQuote.call({ assetAmount, vault }),
|
|
1954
|
+
})
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
export namespace getWithdrawQuote {
|
|
1958
|
+
export type Args = {
|
|
1959
|
+
/** Exact asset output, base units. */
|
|
1960
|
+
assetAmount: bigint
|
|
1961
|
+
/** Vault address. */
|
|
1962
|
+
vault: Address
|
|
1963
|
+
}
|
|
1964
|
+
export type Parameters = Omit<ReadParameters, 'account'> & Args
|
|
1965
|
+
/** Required vault share input, ceiling-rounded. */
|
|
1966
|
+
export type ReturnValue = ReadContractReturnType<
|
|
1967
|
+
typeof Abis.vaultAdapter,
|
|
1968
|
+
'previewWithdraw',
|
|
1969
|
+
never
|
|
1970
|
+
>
|
|
1971
|
+
// TODO: exhaustive error type
|
|
1972
|
+
export type ErrorType = BaseErrorType
|
|
1973
|
+
|
|
1974
|
+
/**
|
|
1975
|
+
* Defines a call to the vault's `previewWithdraw` function.
|
|
1976
|
+
*
|
|
1977
|
+
* Can be passed as a parameter to:
|
|
1978
|
+
* - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call
|
|
1979
|
+
* - [`multicall`](https://viem.sh/docs/contract/multicall): batch the call with other contract reads
|
|
1980
|
+
* - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call
|
|
1981
|
+
*
|
|
1982
|
+
* @example
|
|
1983
|
+
* ```ts
|
|
1984
|
+
* import { Actions } from 'viem/tempo'
|
|
1985
|
+
*
|
|
1986
|
+
* const call = Actions.earn.getWithdrawQuote.call({
|
|
1987
|
+
* assetAmount: 250_000_000n,
|
|
1988
|
+
* vault: '0x...',
|
|
1989
|
+
* })
|
|
1990
|
+
* ```
|
|
1991
|
+
*
|
|
1992
|
+
* @param args - Arguments.
|
|
1993
|
+
* @returns The call.
|
|
1994
|
+
*/
|
|
1995
|
+
export function call(args: Args) {
|
|
1996
|
+
const { assetAmount, vault } = args
|
|
1997
|
+
return defineCall({
|
|
1998
|
+
address: vault,
|
|
1999
|
+
abi: Abis.vaultAdapter,
|
|
2000
|
+
args: [assetAmount],
|
|
2001
|
+
functionName: 'previewWithdraw',
|
|
2002
|
+
})
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* Redeems vault shares for assets sent to `recipient`. The transaction
|
|
2008
|
+
* includes the required vault share approval.
|
|
2009
|
+
*
|
|
2010
|
+
* @example
|
|
2011
|
+
* ```ts
|
|
2012
|
+
* import { createClient, http } from 'viem'
|
|
2013
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
2014
|
+
* import { tempoModerato } from 'viem/chains'
|
|
2015
|
+
* import { Actions } from 'viem/tempo'
|
|
2016
|
+
*
|
|
2017
|
+
* const client = createClient({
|
|
2018
|
+
* account: privateKeyToAccount('0x...'),
|
|
2019
|
+
* chain: tempoModerato,
|
|
2020
|
+
* transport: http(),
|
|
2021
|
+
* })
|
|
2022
|
+
*
|
|
2023
|
+
* const hash = await Actions.earn.redeem(client, {
|
|
2024
|
+
* shareAmount: 100_000_000n,
|
|
2025
|
+
* slippageBps: 50,
|
|
2026
|
+
* vault: '0x...',
|
|
2027
|
+
* })
|
|
2028
|
+
* ```
|
|
2029
|
+
*
|
|
2030
|
+
* @param client - Client.
|
|
2031
|
+
* @param parameters - Parameters.
|
|
2032
|
+
* @returns The transaction hash.
|
|
2033
|
+
*/
|
|
2034
|
+
export async function redeem<
|
|
2035
|
+
chain extends Chain | undefined,
|
|
2036
|
+
account extends Account | undefined,
|
|
2037
|
+
>(
|
|
2038
|
+
client: Client<Transport, chain, account>,
|
|
2039
|
+
parameters: redeem.Parameters<chain, account>,
|
|
2040
|
+
): Promise<redeem.ReturnValue> {
|
|
2041
|
+
return redeem.inner(sendTransaction, client, parameters)
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
export namespace redeem {
|
|
2045
|
+
export type Args = {
|
|
2046
|
+
/** Vault shares to redeem; base units or `{ formatted, decimals? }`. */
|
|
2047
|
+
shareAmount: internal_Token.AmountInput
|
|
2048
|
+
/** Asset recipient. @default `account.address` */
|
|
2049
|
+
recipient?: Address | undefined
|
|
2050
|
+
/** Vault address. */
|
|
2051
|
+
vault: Address
|
|
2052
|
+
} & OneOf<
|
|
2053
|
+
| {
|
|
2054
|
+
/** Minimum asset output to accept; must be greater than zero. */
|
|
2055
|
+
assetAmountMin: bigint
|
|
2056
|
+
}
|
|
2057
|
+
| {
|
|
2058
|
+
/** Slippage tolerance in basis points under a live {@link getRedeemQuote} (50 = 0.5%). */
|
|
2059
|
+
slippageBps: number
|
|
2060
|
+
}
|
|
2061
|
+
| {
|
|
2062
|
+
/** Quoted asset output; floored by `slippageBps`. */
|
|
2063
|
+
assetAmount: bigint
|
|
2064
|
+
/** Slippage tolerance in basis points under `assetAmount` (50 = 0.5%). */
|
|
2065
|
+
slippageBps: number
|
|
2066
|
+
}
|
|
2067
|
+
>
|
|
2068
|
+
export type Parameters<
|
|
2069
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
2070
|
+
account extends Account | undefined = Account | undefined,
|
|
2071
|
+
> = WriteParameters<chain, account> & Args
|
|
2072
|
+
export type ReturnValue = SendTransactionReturnType
|
|
2073
|
+
// TODO: exhaustive error type
|
|
2074
|
+
export type ErrorType = BaseErrorType
|
|
2075
|
+
|
|
2076
|
+
/** @internal Shared dispatch; reads the vault share token for the approval. */
|
|
2077
|
+
export async function inner<
|
|
2078
|
+
action extends typeof sendTransaction | typeof sendTransactionSync,
|
|
2079
|
+
chain extends Chain | undefined,
|
|
2080
|
+
account extends Account | undefined,
|
|
2081
|
+
>(
|
|
2082
|
+
action: action,
|
|
2083
|
+
client: Client<Transport, chain, account>,
|
|
2084
|
+
parameters: redeem.Parameters<chain, account>,
|
|
2085
|
+
): Promise<ReturnType<action>> {
|
|
2086
|
+
const [args, shareToken] = await Promise.all([
|
|
2087
|
+
toRedeemArgs(client, parameters as never),
|
|
2088
|
+
readContract(client, {
|
|
2089
|
+
abi: Abis.vaultAdapter,
|
|
2090
|
+
address: parameters.vault,
|
|
2091
|
+
functionName: 'shareToken',
|
|
2092
|
+
}),
|
|
2093
|
+
])
|
|
2094
|
+
return (await action(client, {
|
|
2095
|
+
...parameters,
|
|
2096
|
+
calls: redeem.calls({ ...args, shareToken }),
|
|
2097
|
+
} as never)) as never
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
/**
|
|
2101
|
+
* Defines a redeem call without an approval. Provide vault share decimals
|
|
2102
|
+
* for formatted inputs and an explicit output bound because this builder performs no reads.
|
|
2103
|
+
*
|
|
2104
|
+
* @param parameters - Client (optional), followed by the call arguments.
|
|
2105
|
+
* @returns The call.
|
|
2106
|
+
*/
|
|
2107
|
+
export function call<chain extends Chain | undefined>(
|
|
2108
|
+
...parameters: CallParameters<call.Args, Client<Transport, chain>>
|
|
2109
|
+
) {
|
|
2110
|
+
const [, args] = resolveCallParameters(parameters)
|
|
2111
|
+
const { recipient, vault } = args
|
|
2112
|
+
const assetAmountMin = (() => {
|
|
2113
|
+
if (args.assetAmountMin !== undefined) return args.assetAmountMin
|
|
2114
|
+
return EarnShares.minimumOutput(args.assetAmount, args.slippageBps)
|
|
2115
|
+
})()
|
|
2116
|
+
return defineCall({
|
|
2117
|
+
address: vault,
|
|
2118
|
+
abi: Abis.vaultAdapter,
|
|
2119
|
+
functionName: 'redeem',
|
|
2120
|
+
args: [
|
|
2121
|
+
internal_Token.toBaseUnits(args.shareAmount, undefined),
|
|
2122
|
+
recipient,
|
|
2123
|
+
assetAmountMin,
|
|
2124
|
+
],
|
|
2125
|
+
})
|
|
2126
|
+
}
|
|
2127
|
+
export namespace call {
|
|
2128
|
+
export type Args = {
|
|
2129
|
+
/** Vault shares to redeem; base units or `{ formatted, decimals? }`. */
|
|
2130
|
+
shareAmount: internal_Token.AmountInput
|
|
2131
|
+
/** Asset recipient. */
|
|
2132
|
+
recipient: Address
|
|
2133
|
+
/** Vault address. */
|
|
2134
|
+
vault: Address
|
|
2135
|
+
} & OneOf<
|
|
2136
|
+
| {
|
|
2137
|
+
/** Minimum asset output to accept. */
|
|
2138
|
+
assetAmountMin: bigint
|
|
2139
|
+
}
|
|
2140
|
+
| {
|
|
2141
|
+
/** Quoted asset output; floored by `slippageBps`. */
|
|
2142
|
+
assetAmount: bigint
|
|
2143
|
+
/** Slippage tolerance in basis points under `assetAmount` (50 = 0.5%). */
|
|
2144
|
+
slippageBps: number
|
|
2145
|
+
}
|
|
2146
|
+
>
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
/**
|
|
2150
|
+
* Defines the vault share approval and redeem calls for atomic execution.
|
|
2151
|
+
* Pass `shareToken` explicitly because this builder performs no reads.
|
|
2152
|
+
*
|
|
2153
|
+
* @param args - Arguments.
|
|
2154
|
+
* @returns The calls.
|
|
2155
|
+
*/
|
|
2156
|
+
export function calls(
|
|
2157
|
+
args: call.Args & {
|
|
2158
|
+
/** Vault share token approved for the redemption. */
|
|
2159
|
+
shareToken: Address
|
|
2160
|
+
},
|
|
2161
|
+
) {
|
|
2162
|
+
const { shareToken, vault } = args
|
|
2163
|
+
const shareAmount = internal_Token.toBaseUnits(args.shareAmount, undefined)
|
|
2164
|
+
return [
|
|
2165
|
+
defineCall({
|
|
2166
|
+
address: shareToken,
|
|
2167
|
+
abi: Abis.tip20,
|
|
2168
|
+
functionName: 'approve',
|
|
2169
|
+
args: [vault, shareAmount],
|
|
2170
|
+
}),
|
|
2171
|
+
redeem.call({ ...args, shareAmount }),
|
|
2172
|
+
]
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
/**
|
|
2176
|
+
* Extracts a `Redeemed` event from the vault's logs.
|
|
2177
|
+
*
|
|
2178
|
+
* @param logs - Logs.
|
|
2179
|
+
* @param parameters - Parameters.
|
|
2180
|
+
* @returns The `Redeemed` event.
|
|
2181
|
+
*/
|
|
2182
|
+
export function extractEvent(logs: Log[], parameters: { vault: Address }) {
|
|
2183
|
+
const { vault } = parameters
|
|
2184
|
+
// Earn contracts are user-deployed: several adapters can emit the same
|
|
2185
|
+
// signature in one receipt, so filter by emitting address before decode.
|
|
2186
|
+
const [log] = parseEventLogs({
|
|
2187
|
+
abi: Abis.vaultAdapter,
|
|
2188
|
+
eventName: 'Redeemed',
|
|
2189
|
+
logs: logs.filter((log) => isAddressEqual(log.address, vault)),
|
|
2190
|
+
})
|
|
2191
|
+
if (!log) throw new Error('`Redeemed` event not found.')
|
|
2192
|
+
return log
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
/**
|
|
2196
|
+
* Estimates gas for a redemption, assuming enough vault share allowance.
|
|
2197
|
+
*
|
|
2198
|
+
* @param client - Client.
|
|
2199
|
+
* @param parameters - Parameters.
|
|
2200
|
+
* @returns The gas estimate.
|
|
2201
|
+
*/
|
|
2202
|
+
export async function estimateGas<
|
|
2203
|
+
chain extends Chain | undefined,
|
|
2204
|
+
account extends Account | undefined,
|
|
2205
|
+
>(
|
|
2206
|
+
client: Client<Transport, chain, account>,
|
|
2207
|
+
parameters: redeem.Parameters<chain, account>,
|
|
2208
|
+
): Promise<bigint> {
|
|
2209
|
+
return estimateContractGas(client, {
|
|
2210
|
+
...pickWriteParameters(parameters as never),
|
|
2211
|
+
...redeem.call(await toRedeemArgs(client, parameters as never)),
|
|
2212
|
+
} as never)
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* Simulates a redemption, assuming enough vault share allowance.
|
|
2217
|
+
*
|
|
2218
|
+
* @param client - Client.
|
|
2219
|
+
* @param parameters - Parameters.
|
|
2220
|
+
* @returns The simulation result and write request.
|
|
2221
|
+
*/
|
|
2222
|
+
export async function simulate<
|
|
2223
|
+
chain extends Chain | undefined,
|
|
2224
|
+
account extends Account | undefined,
|
|
2225
|
+
>(
|
|
2226
|
+
client: Client<Transport, chain, account>,
|
|
2227
|
+
parameters: redeem.Parameters<chain, account>,
|
|
2228
|
+
): Promise<SimulateContractReturnType<typeof Abis.vaultAdapter, 'redeem'>> {
|
|
2229
|
+
return simulateContract(client, {
|
|
2230
|
+
...pickWriteParameters(parameters as never),
|
|
2231
|
+
...redeem.call(await toRedeemArgs(client, parameters as never)),
|
|
2232
|
+
} as never) as never
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
/**
|
|
2237
|
+
* Redeems vault shares and returns the confirmed receipt and event data.
|
|
2238
|
+
*
|
|
2239
|
+
* @example
|
|
2240
|
+
* ```ts
|
|
2241
|
+
* import { createClient, http } from 'viem'
|
|
2242
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
2243
|
+
* import { tempoModerato } from 'viem/chains'
|
|
2244
|
+
* import { Actions } from 'viem/tempo'
|
|
2245
|
+
*
|
|
2246
|
+
* const client = createClient({
|
|
2247
|
+
* account: privateKeyToAccount('0x...'),
|
|
2248
|
+
* chain: tempoModerato,
|
|
2249
|
+
* transport: http(),
|
|
2250
|
+
* })
|
|
2251
|
+
*
|
|
2252
|
+
* const { assetAmount } = await Actions.earn.redeemSync(client, {
|
|
2253
|
+
* assetAmountMin: 99_500_000n,
|
|
2254
|
+
* shareAmount: 100_000_000n,
|
|
2255
|
+
* vault: '0x...',
|
|
2256
|
+
* })
|
|
2257
|
+
* ```
|
|
2258
|
+
*
|
|
2259
|
+
* @param client - Client.
|
|
2260
|
+
* @param parameters - Parameters.
|
|
2261
|
+
* @returns The transaction receipt and event data.
|
|
2262
|
+
*/
|
|
2263
|
+
export async function redeemSync<
|
|
2264
|
+
chain extends Chain | undefined,
|
|
2265
|
+
account extends Account | undefined,
|
|
2266
|
+
>(
|
|
2267
|
+
client: Client<Transport, chain, account>,
|
|
2268
|
+
parameters: redeemSync.Parameters<chain, account>,
|
|
2269
|
+
): Promise<redeemSync.ReturnValue> {
|
|
2270
|
+
const { throwOnReceiptRevert = true, vault } = parameters
|
|
2271
|
+
const receipt = await redeem.inner(sendTransactionSync, client, {
|
|
2272
|
+
...parameters,
|
|
2273
|
+
throwOnReceiptRevert,
|
|
2274
|
+
} as never)
|
|
2275
|
+
const { args } = redeem.extractEvent(receipt.logs, { vault })
|
|
2276
|
+
return {
|
|
2277
|
+
assetAmount: args.assets,
|
|
2278
|
+
caller: args.caller,
|
|
2279
|
+
receipt,
|
|
2280
|
+
recipient: args.receiver,
|
|
2281
|
+
shareAmount: args.shares,
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
export namespace redeemSync {
|
|
2286
|
+
export type Args = redeem.Args
|
|
2287
|
+
export type Parameters<
|
|
2288
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
2289
|
+
account extends Account | undefined = Account | undefined,
|
|
2290
|
+
> = redeem.Parameters<chain, account> & WriteSyncParameters<chain, account>
|
|
2291
|
+
export type ReturnValue = Compute<{
|
|
2292
|
+
/** Assets paid out. */
|
|
2293
|
+
assetAmount: bigint
|
|
2294
|
+
/** Redeeming caller. */
|
|
2295
|
+
caller: Address
|
|
2296
|
+
/** Transaction receipt. */
|
|
2297
|
+
receipt: TransactionReceipt
|
|
2298
|
+
/** Asset recipient. */
|
|
2299
|
+
recipient: Address
|
|
2300
|
+
/** Vault shares burned. */
|
|
2301
|
+
shareAmount: bigint
|
|
2302
|
+
}>
|
|
2303
|
+
// TODO: exhaustive error type
|
|
2304
|
+
export type ErrorType = BaseErrorType
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
/**
|
|
2308
|
+
* Withdraws vault shares from a Zone and redeems them on the parent chain. Use
|
|
2309
|
+
* {@link privateRedeem.prepare} to build the encrypted callback.
|
|
2310
|
+
*
|
|
2311
|
+
* @example
|
|
2312
|
+
* ```ts
|
|
2313
|
+
* const prepared = await Actions.earn.privateRedeem.prepare(parentClient, {
|
|
2314
|
+
* gateway: '0x...',
|
|
2315
|
+
* recipient: '0x...',
|
|
2316
|
+
* recoveryRecipient: '0x...',
|
|
2317
|
+
* shareAmount: 100_000_000n,
|
|
2318
|
+
* slippageBps: 50,
|
|
2319
|
+
* })
|
|
2320
|
+
* const hash = await Actions.earn.privateRedeem(zoneClient, prepared)
|
|
2321
|
+
* ```
|
|
2322
|
+
*
|
|
2323
|
+
* @param client - Zone client.
|
|
2324
|
+
* @param parameters - Prepared redemption and transaction parameters.
|
|
2325
|
+
* @returns The transaction hash.
|
|
2326
|
+
*/
|
|
2327
|
+
export async function privateRedeem<
|
|
2328
|
+
chain extends Chain | undefined,
|
|
2329
|
+
account extends Account | undefined,
|
|
2330
|
+
>(
|
|
2331
|
+
client: Client<Transport, chain, account>,
|
|
2332
|
+
parameters: privateRedeem.Parameters<chain, account>,
|
|
2333
|
+
): Promise<privateRedeem.ReturnValue> {
|
|
2334
|
+
await assertPreparedZoneRequestChain(client, parameters)
|
|
2335
|
+
return zoneActions.requestWithdrawal(client, parameters)
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
export namespace privateRedeem {
|
|
2339
|
+
export type Args = prepare.ReturnValue
|
|
2340
|
+
export type Parameters<
|
|
2341
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
2342
|
+
account extends Account | undefined = Account | undefined,
|
|
2343
|
+
> = WriteParameters<chain, account> & Args
|
|
2344
|
+
export type ReturnValue = SendTransactionReturnType
|
|
2345
|
+
export type ErrorType = zoneActions.requestWithdrawal.ErrorType
|
|
2346
|
+
|
|
2347
|
+
/**
|
|
2348
|
+
* Builds an encrypted Zone withdrawal that redeems vault shares and returns
|
|
2349
|
+
* the resulting assets to the Zone.
|
|
2350
|
+
*
|
|
2351
|
+
* @param client - Parent-chain client.
|
|
2352
|
+
* @param parameters - Redemption intent and recovery parameters.
|
|
2353
|
+
* @returns The prepared withdrawal and correlation data.
|
|
2354
|
+
*/
|
|
2355
|
+
export async function prepare<chain extends Chain | undefined>(
|
|
2356
|
+
client: Client<Transport, chain>,
|
|
2357
|
+
parameters: prepare.Parameters,
|
|
2358
|
+
): Promise<prepare.ReturnValue> {
|
|
2359
|
+
const chainId = client.chain?.id
|
|
2360
|
+
if (!chainId) throw new Error('`chain` is required.')
|
|
2361
|
+
const {
|
|
2362
|
+
actionId = Hex.random(32),
|
|
2363
|
+
callbackGas = zoneGatewayCallbackGas,
|
|
2364
|
+
fallbackRecipient = parameters.recoveryRecipient,
|
|
2365
|
+
gateway,
|
|
2366
|
+
recipient,
|
|
2367
|
+
recoveryRecipient,
|
|
2368
|
+
returnMemo,
|
|
2369
|
+
shareAmount,
|
|
2370
|
+
withdrawalMemo,
|
|
2371
|
+
} = parameters
|
|
2372
|
+
const readParameters = pickReadParameters(parameters)
|
|
2373
|
+
const [fromBlock, config] = await Promise.all([
|
|
2374
|
+
getBlockNumber(client, { cacheTime: 0 }),
|
|
2375
|
+
getZoneGatewayConfig(client, { ...readParameters, gateway }),
|
|
2376
|
+
])
|
|
2377
|
+
const assetToken = parameters.assetToken ?? config.vaultAsset
|
|
2378
|
+
if (isAddressEqual(assetToken, config.shareToken))
|
|
2379
|
+
throw new Error('`assetToken` cannot be the gateway vault share token.')
|
|
2380
|
+
|
|
2381
|
+
const [{ encrypted, keyIndex }, assetAmountMin] = await Promise.all([
|
|
2382
|
+
zoneActions.encryptedDeposit.prepareRecipient(client, {
|
|
2383
|
+
...readParameters,
|
|
2384
|
+
memo: returnMemo,
|
|
2385
|
+
portalAddress: config.zonePortal,
|
|
2386
|
+
recipient,
|
|
2387
|
+
zoneId: config.zoneId,
|
|
2388
|
+
}),
|
|
2389
|
+
(async () => {
|
|
2390
|
+
if (parameters.assetAmountMin !== undefined)
|
|
2391
|
+
return EarnShares.minimumOutput(parameters.assetAmountMin, 0)
|
|
2392
|
+
if (parameters.assetAmount !== undefined)
|
|
2393
|
+
return EarnShares.minimumOutput(
|
|
2394
|
+
parameters.assetAmount,
|
|
2395
|
+
parameters.slippageBps,
|
|
2396
|
+
)
|
|
2397
|
+
const assetAmount = await getRedeemQuote(client, {
|
|
2398
|
+
...readParameters,
|
|
2399
|
+
shareAmount,
|
|
2400
|
+
vault: config.vaultAdapter,
|
|
2401
|
+
})
|
|
2402
|
+
return EarnShares.minimumOutput(assetAmount, parameters.slippageBps)
|
|
2403
|
+
})(),
|
|
2404
|
+
])
|
|
2405
|
+
const direct = isAddressEqual(assetToken, config.vaultAsset)
|
|
2406
|
+
const data = encodeAbiParameters(Abis.zoneGatewayCallbackData, [
|
|
2407
|
+
{
|
|
2408
|
+
flow: 1,
|
|
2409
|
+
outputToken: assetToken,
|
|
2410
|
+
keyIndex,
|
|
2411
|
+
encrypted,
|
|
2412
|
+
minVaultAssets: direct ? assetAmountMin : 1n,
|
|
2413
|
+
minVaultShares: 0n,
|
|
2414
|
+
minOutputAmount: direct ? 0n : assetAmountMin,
|
|
2415
|
+
actionId,
|
|
2416
|
+
refundRecipient: recoveryRecipient,
|
|
2417
|
+
},
|
|
2418
|
+
])
|
|
2419
|
+
return {
|
|
2420
|
+
actionId,
|
|
2421
|
+
amount: shareAmount,
|
|
2422
|
+
callbackGas,
|
|
2423
|
+
chainId,
|
|
2424
|
+
data,
|
|
2425
|
+
fallbackRecipient,
|
|
2426
|
+
fromBlock,
|
|
2427
|
+
memo: withdrawalMemo,
|
|
2428
|
+
to: gateway,
|
|
2429
|
+
token: config.shareToken,
|
|
2430
|
+
zoneId: config.zoneId,
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
export namespace prepare {
|
|
2435
|
+
export type Parameters = Omit<ReadParameters, 'account'> &
|
|
2436
|
+
PrivatePreparationParameters & {
|
|
2437
|
+
/** Vault shares withdrawn from the Zone, base units. */
|
|
2438
|
+
shareAmount: bigint
|
|
2439
|
+
} & (
|
|
2440
|
+
| ({
|
|
2441
|
+
/** Asset token returned to the Zone. @default gateway vault asset */
|
|
2442
|
+
assetToken?: undefined
|
|
2443
|
+
} & OneOf<
|
|
2444
|
+
| {
|
|
2445
|
+
/** Minimum assets returned to the Zone. */
|
|
2446
|
+
assetAmountMin: bigint
|
|
2447
|
+
}
|
|
2448
|
+
| {
|
|
2449
|
+
/** Quoted assets returned to the Zone. */
|
|
2450
|
+
assetAmount: bigint
|
|
2451
|
+
/** Slippage tolerance under `assetAmount` (50 = 0.5%). */
|
|
2452
|
+
slippageBps: number
|
|
2453
|
+
}
|
|
2454
|
+
| {
|
|
2455
|
+
/** Slippage tolerance under a live vault quote (50 = 0.5%). */
|
|
2456
|
+
slippageBps: number
|
|
2457
|
+
}
|
|
2458
|
+
>)
|
|
2459
|
+
| ({
|
|
2460
|
+
/** Asset token returned to the Zone after a swap. */
|
|
2461
|
+
assetToken: Address
|
|
2462
|
+
} & MinimumAssetAmountParameters)
|
|
2463
|
+
)
|
|
2464
|
+
export type ReturnValue = PreparedZoneRequest
|
|
2465
|
+
export type ErrorType = BaseErrorType
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
/**
|
|
2469
|
+
* Defines the approval and Zone withdrawal calls for a prepared redemption.
|
|
2470
|
+
*
|
|
2471
|
+
* @param args - Prepared redemption arguments.
|
|
2472
|
+
* @returns The Zone withdrawal calls.
|
|
2473
|
+
*/
|
|
2474
|
+
export function calls(args: Args) {
|
|
2475
|
+
return zoneActions.requestWithdrawal.calls(args)
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
/**
|
|
2480
|
+
* Requests a private Zone redemption and waits for the Zone transaction
|
|
2481
|
+
* receipt. The receipt confirms withdrawal acceptance, not redemption.
|
|
2482
|
+
*
|
|
2483
|
+
* @param client - Zone client.
|
|
2484
|
+
* @param parameters - Prepared redemption and transaction parameters.
|
|
2485
|
+
* @returns The Zone transaction receipt and parent-chain withdrawal sender tag.
|
|
2486
|
+
*/
|
|
2487
|
+
export async function privateRedeemSync<
|
|
2488
|
+
chain extends Chain | undefined,
|
|
2489
|
+
account extends Account | undefined,
|
|
2490
|
+
>(
|
|
2491
|
+
client: Client<Transport, chain, account>,
|
|
2492
|
+
parameters: privateRedeemSync.Parameters<chain, account>,
|
|
2493
|
+
): Promise<privateRedeemSync.ReturnValue> {
|
|
2494
|
+
await assertPreparedZoneRequestChain(client, parameters)
|
|
2495
|
+
return zoneActions.requestWithdrawalSync(client, parameters)
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
export namespace privateRedeemSync {
|
|
2499
|
+
export type Args = privateRedeem.Args
|
|
2500
|
+
export type Parameters<
|
|
2501
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
2502
|
+
account extends Account | undefined = Account | undefined,
|
|
2503
|
+
> = privateRedeem.Parameters<chain, account> &
|
|
2504
|
+
WriteSyncParameters<chain, account>
|
|
2505
|
+
export type ReturnValue = zoneActions.requestWithdrawalSync.ReturnValue
|
|
2506
|
+
export type ErrorType = zoneActions.requestWithdrawalSync.ErrorType
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
/**
|
|
2510
|
+
* Waits for a Zone gateway redemption to complete on the parent chain.
|
|
2511
|
+
*
|
|
2512
|
+
* @example
|
|
2513
|
+
* ```ts
|
|
2514
|
+
* const result = await Actions.earn.waitForPrivateRedeem(parentClient, {
|
|
2515
|
+
* actionId: prepared.actionId,
|
|
2516
|
+
* fromBlock: prepared.fromBlock,
|
|
2517
|
+
* gateway: '0x...',
|
|
2518
|
+
* })
|
|
2519
|
+
* ```
|
|
2520
|
+
*
|
|
2521
|
+
* @param client - Parent-chain client.
|
|
2522
|
+
* @param parameters - Prepared action correlation and polling parameters.
|
|
2523
|
+
* @returns The completed gateway redemption.
|
|
2524
|
+
*/
|
|
2525
|
+
export async function waitForPrivateRedeem<chain extends Chain | undefined>(
|
|
2526
|
+
client: Client<Transport, chain>,
|
|
2527
|
+
parameters: waitForPrivateRedeem.Parameters,
|
|
2528
|
+
): Promise<waitForPrivateRedeem.ReturnType> {
|
|
2529
|
+
const {
|
|
2530
|
+
actionId,
|
|
2531
|
+
fromBlock,
|
|
2532
|
+
gateway,
|
|
2533
|
+
pollingInterval = client.pollingInterval,
|
|
2534
|
+
timeout = 60_000,
|
|
2535
|
+
} = parameters
|
|
2536
|
+
const event = getAbiItem({
|
|
2537
|
+
abi: Abis.zoneGateway,
|
|
2538
|
+
name: 'EarnRedeem',
|
|
2539
|
+
})
|
|
2540
|
+
const observerId = stringify([
|
|
2541
|
+
'waitForPrivateRedeem',
|
|
2542
|
+
client.uid,
|
|
2543
|
+
gateway,
|
|
2544
|
+
actionId,
|
|
2545
|
+
fromBlock,
|
|
2546
|
+
])
|
|
2547
|
+
const { promise, reject, resolve } =
|
|
2548
|
+
withResolvers<waitForPrivateRedeem.ReturnType>()
|
|
2549
|
+
|
|
2550
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
2551
|
+
let unobserve: () => void
|
|
2552
|
+
const cleanup = () => {
|
|
2553
|
+
clearTimeout(timer)
|
|
2554
|
+
unobserve()
|
|
2555
|
+
}
|
|
2556
|
+
const resolve_ = (result: waitForPrivateRedeem.ReturnType) => {
|
|
2557
|
+
cleanup()
|
|
2558
|
+
resolve(result)
|
|
2559
|
+
}
|
|
2560
|
+
const reject_ = (error: unknown) => {
|
|
2561
|
+
cleanup()
|
|
2562
|
+
reject(error)
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
unobserve = observe(
|
|
2566
|
+
observerId,
|
|
2567
|
+
{ reject: reject_, resolve: resolve_ },
|
|
2568
|
+
(emit) => {
|
|
2569
|
+
const unpoll = poll(
|
|
2570
|
+
async () => {
|
|
2571
|
+
try {
|
|
2572
|
+
const [log] = await getLogs(client, {
|
|
2573
|
+
address: gateway,
|
|
2574
|
+
args: { actionId },
|
|
2575
|
+
event,
|
|
2576
|
+
fromBlock,
|
|
2577
|
+
strict: true,
|
|
2578
|
+
toBlock: 'latest',
|
|
2579
|
+
})
|
|
2580
|
+
if (!log) return
|
|
2581
|
+
unpoll()
|
|
2582
|
+
emit.resolve({
|
|
2583
|
+
actionId: log.args.actionId,
|
|
2584
|
+
outputAmount: log.args.outputAmount,
|
|
2585
|
+
outputToken: log.args.outputToken,
|
|
2586
|
+
shares: log.args.shares,
|
|
2587
|
+
tempoBlockNumber: log.blockNumber,
|
|
2588
|
+
vaultAssets: log.args.vaultAssets,
|
|
2589
|
+
zoneDepositHash: log.args.zoneDepositHash,
|
|
2590
|
+
})
|
|
2591
|
+
} catch (error) {
|
|
2592
|
+
unpoll()
|
|
2593
|
+
emit.reject(error)
|
|
2594
|
+
}
|
|
2595
|
+
},
|
|
2596
|
+
{ emitOnBegin: true, interval: pollingInterval },
|
|
2597
|
+
)
|
|
2598
|
+
|
|
2599
|
+
return unpoll
|
|
2600
|
+
},
|
|
2601
|
+
)
|
|
2602
|
+
|
|
2603
|
+
timer = timeout
|
|
2604
|
+
? setTimeout(() => {
|
|
2605
|
+
reject_(new WaitForPrivateRedeemTimeoutError({ actionId, gateway }))
|
|
2606
|
+
}, timeout)
|
|
2607
|
+
: undefined
|
|
2608
|
+
|
|
2609
|
+
return await promise
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
export namespace waitForPrivateRedeem {
|
|
2613
|
+
export type Parameters = {
|
|
2614
|
+
/** Correlation id from {@link privateRedeem.prepare}. */
|
|
2615
|
+
actionId: Hex.Hex
|
|
2616
|
+
/** Lower bound for the parent-chain log scan. */
|
|
2617
|
+
fromBlock: bigint
|
|
2618
|
+
/** Zone gateway address. */
|
|
2619
|
+
gateway: Address
|
|
2620
|
+
/** Polling frequency in milliseconds. @default `client.pollingInterval` */
|
|
2621
|
+
pollingInterval?: number | undefined
|
|
2622
|
+
/** Timeout in milliseconds; `0` disables it. @default `60_000` */
|
|
2623
|
+
timeout?: number | undefined
|
|
2624
|
+
}
|
|
2625
|
+
export type ReturnType = {
|
|
2626
|
+
/** Correlation id for the completed redemption. */
|
|
2627
|
+
actionId: Hex.Hex
|
|
2628
|
+
/** Tokens returned to the Zone, base units. */
|
|
2629
|
+
outputAmount: bigint
|
|
2630
|
+
/** Token returned to the Zone. */
|
|
2631
|
+
outputToken: Address
|
|
2632
|
+
/** Vault shares redeemed. */
|
|
2633
|
+
shares: bigint
|
|
2634
|
+
/** Parent-chain block containing the gateway event. */
|
|
2635
|
+
tempoBlockNumber: bigint
|
|
2636
|
+
/** Vault assets produced before any swap. */
|
|
2637
|
+
vaultAssets: bigint
|
|
2638
|
+
/** Encrypted return deposit hash. */
|
|
2639
|
+
zoneDepositHash: Hex.Hex
|
|
2640
|
+
}
|
|
2641
|
+
export type ErrorType =
|
|
2642
|
+
| GetLogsErrorType
|
|
2643
|
+
| ObserveErrorType
|
|
2644
|
+
| PollErrorType
|
|
2645
|
+
| WaitForPrivateRedeemTimeoutErrorType
|
|
2646
|
+
| BaseErrorType
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
/**
|
|
2650
|
+
* Withdraws an exact asset amount to `recipient`, up to the specified vault
|
|
2651
|
+
* share limit. The transaction includes the required vault share approval;
|
|
2652
|
+
* use {@link redeem} for a full exit.
|
|
2653
|
+
*
|
|
2654
|
+
* @example
|
|
2655
|
+
* ```ts
|
|
2656
|
+
* import { createClient, http } from 'viem'
|
|
2657
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
2658
|
+
* import { tempoModerato } from 'viem/chains'
|
|
2659
|
+
* import { Actions } from 'viem/tempo'
|
|
2660
|
+
*
|
|
2661
|
+
* const client = createClient({
|
|
2662
|
+
* account: privateKeyToAccount('0x...'),
|
|
2663
|
+
* chain: tempoModerato,
|
|
2664
|
+
* transport: http(),
|
|
2665
|
+
* })
|
|
2666
|
+
*
|
|
2667
|
+
* const hash = await Actions.earn.withdrawExact(client, {
|
|
2668
|
+
* assetAmount: 40_000_000n,
|
|
2669
|
+
* slippageBps: 50,
|
|
2670
|
+
* vault: '0x...',
|
|
2671
|
+
* })
|
|
2672
|
+
* ```
|
|
2673
|
+
*
|
|
2674
|
+
* @param client - Client.
|
|
2675
|
+
* @param parameters - Parameters.
|
|
2676
|
+
* @returns The transaction hash.
|
|
2677
|
+
*/
|
|
2678
|
+
export async function withdrawExact<
|
|
2679
|
+
chain extends Chain | undefined,
|
|
2680
|
+
account extends Account | undefined,
|
|
2681
|
+
>(
|
|
2682
|
+
client: Client<Transport, chain, account>,
|
|
2683
|
+
parameters: withdrawExact.Parameters<chain, account>,
|
|
2684
|
+
): Promise<withdrawExact.ReturnValue> {
|
|
2685
|
+
return withdrawExact.inner(sendTransaction, client, parameters)
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
export namespace withdrawExact {
|
|
2689
|
+
export type Args = {
|
|
2690
|
+
/** Exact assets to receive; base units or `{ formatted, decimals? }`. */
|
|
2691
|
+
assetAmount: internal_Token.AmountInput
|
|
2692
|
+
/** Asset recipient. @default `account.address` */
|
|
2693
|
+
recipient?: Address | undefined
|
|
2694
|
+
/** Vault address. */
|
|
2695
|
+
vault: Address
|
|
2696
|
+
} & OneOf<
|
|
2697
|
+
| {
|
|
2698
|
+
/** Maximum vault share input to burn. */
|
|
2699
|
+
shareAmountMax: bigint
|
|
2700
|
+
}
|
|
2701
|
+
| {
|
|
2702
|
+
/** Slippage headroom above a live {@link getWithdrawQuote}, ceiling-rounded (50 = 0.5%). */
|
|
2703
|
+
slippageBps: number
|
|
2704
|
+
}
|
|
2705
|
+
| {
|
|
2706
|
+
/** Quoted vault share input; raised by `slippageBps`. */
|
|
2707
|
+
shareAmount: bigint
|
|
2708
|
+
/** Slippage tolerance in basis points over `shareAmount` (50 = 0.5%). */
|
|
2709
|
+
slippageBps: number
|
|
2710
|
+
}
|
|
2711
|
+
>
|
|
2712
|
+
export type Parameters<
|
|
2713
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
2714
|
+
account extends Account | undefined = Account | undefined,
|
|
2715
|
+
> = WriteParameters<chain, account> & Args
|
|
2716
|
+
export type ReturnValue = SendTransactionReturnType
|
|
2717
|
+
// TODO: exhaustive error type
|
|
2718
|
+
export type ErrorType = BaseErrorType
|
|
2719
|
+
|
|
2720
|
+
/** @internal Shared dispatch; reads the vault share token for the approval. */
|
|
2721
|
+
export async function inner<
|
|
2722
|
+
action extends typeof sendTransaction | typeof sendTransactionSync,
|
|
2723
|
+
chain extends Chain | undefined,
|
|
2724
|
+
account extends Account | undefined,
|
|
2725
|
+
>(
|
|
2726
|
+
action: action,
|
|
2727
|
+
client: Client<Transport, chain, account>,
|
|
2728
|
+
parameters: withdrawExact.Parameters<chain, account>,
|
|
2729
|
+
): Promise<ReturnType<action>> {
|
|
2730
|
+
const [args, shareToken] = await Promise.all([
|
|
2731
|
+
toWithdrawExactArgs(client, parameters as never),
|
|
2732
|
+
readContract(client, {
|
|
2733
|
+
abi: Abis.vaultAdapter,
|
|
2734
|
+
address: parameters.vault,
|
|
2735
|
+
functionName: 'shareToken',
|
|
2736
|
+
}),
|
|
2737
|
+
])
|
|
2738
|
+
return (await action(client, {
|
|
2739
|
+
...parameters,
|
|
2740
|
+
calls: withdrawExact.calls({ ...args, shareToken }),
|
|
2741
|
+
} as never)) as never
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
/**
|
|
2745
|
+
* Defines an exact withdrawal call without an approval. Provide asset
|
|
2746
|
+
* decimals and an explicit input limit because this builder performs no reads.
|
|
2747
|
+
*
|
|
2748
|
+
* @param parameters - Client (optional), followed by the call arguments.
|
|
2749
|
+
* @returns The call.
|
|
2750
|
+
*/
|
|
2751
|
+
export function call<chain extends Chain | undefined>(
|
|
2752
|
+
...parameters: CallParameters<call.Args, Client<Transport, chain>>
|
|
2753
|
+
) {
|
|
2754
|
+
const [, args] = resolveCallParameters(parameters)
|
|
2755
|
+
const { recipient, vault } = args
|
|
2756
|
+
const shareAmountMax = (() => {
|
|
2757
|
+
if (args.shareAmountMax !== undefined) return args.shareAmountMax
|
|
2758
|
+
return maximumInput(args.shareAmount, args.slippageBps)
|
|
2759
|
+
})()
|
|
2760
|
+
return defineCall({
|
|
2761
|
+
address: vault,
|
|
2762
|
+
abi: Abis.vaultAdapter,
|
|
2763
|
+
functionName: 'withdrawExact',
|
|
2764
|
+
args: [
|
|
2765
|
+
internal_Token.toBaseUnits(args.assetAmount, undefined),
|
|
2766
|
+
recipient,
|
|
2767
|
+
shareAmountMax,
|
|
2768
|
+
],
|
|
2769
|
+
})
|
|
2770
|
+
}
|
|
2771
|
+
export namespace call {
|
|
2772
|
+
export type Args = {
|
|
2773
|
+
/** Exact assets to receive; base units or `{ formatted, decimals? }`. */
|
|
2774
|
+
assetAmount: internal_Token.AmountInput
|
|
2775
|
+
/** Asset recipient. */
|
|
2776
|
+
recipient: Address
|
|
2777
|
+
/** Vault address. */
|
|
2778
|
+
vault: Address
|
|
2779
|
+
} & OneOf<
|
|
2780
|
+
| {
|
|
2781
|
+
/** Maximum vault share input to burn. */
|
|
2782
|
+
shareAmountMax: bigint
|
|
2783
|
+
}
|
|
2784
|
+
| {
|
|
2785
|
+
/** Quoted vault share input; raised by `slippageBps`. */
|
|
2786
|
+
shareAmount: bigint
|
|
2787
|
+
/** Slippage tolerance in basis points over `shareAmount` (50 = 0.5%). */
|
|
2788
|
+
slippageBps: number
|
|
2789
|
+
}
|
|
2790
|
+
>
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
/**
|
|
2794
|
+
* Defines the vault share approval and withdrawal calls for atomic
|
|
2795
|
+
* execution. Pass `shareToken` explicitly because this builder performs no reads.
|
|
2796
|
+
*
|
|
2797
|
+
* @param args - Arguments.
|
|
2798
|
+
* @returns The calls.
|
|
2799
|
+
*/
|
|
2800
|
+
export function calls(
|
|
2801
|
+
args: call.Args & {
|
|
2802
|
+
/** Vault share token approved for the withdrawal. */
|
|
2803
|
+
shareToken: Address
|
|
2804
|
+
},
|
|
2805
|
+
) {
|
|
2806
|
+
const { shareToken, vault } = args
|
|
2807
|
+
const assetAmount = internal_Token.toBaseUnits(args.assetAmount, undefined)
|
|
2808
|
+
const call = withdrawExact.call({ ...args, assetAmount })
|
|
2809
|
+
const [, , shareAmountMax] = call.args
|
|
2810
|
+
return [
|
|
2811
|
+
defineCall({
|
|
2812
|
+
address: shareToken,
|
|
2813
|
+
abi: Abis.tip20,
|
|
2814
|
+
functionName: 'approve',
|
|
2815
|
+
args: [vault, shareAmountMax],
|
|
2816
|
+
}),
|
|
2817
|
+
call,
|
|
2818
|
+
]
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
/**
|
|
2822
|
+
* Extracts a `WithdrewExact` event from the vault's logs.
|
|
2823
|
+
*
|
|
2824
|
+
* @param logs - Logs.
|
|
2825
|
+
* @param parameters - Parameters.
|
|
2826
|
+
* @returns The `WithdrewExact` event.
|
|
2827
|
+
*/
|
|
2828
|
+
export function extractEvent(logs: Log[], parameters: { vault: Address }) {
|
|
2829
|
+
const { vault } = parameters
|
|
2830
|
+
// Earn contracts are user-deployed: several adapters can emit the same
|
|
2831
|
+
// signature in one receipt, so filter by emitting address before decode.
|
|
2832
|
+
const [log] = parseEventLogs({
|
|
2833
|
+
abi: Abis.vaultAdapter,
|
|
2834
|
+
eventName: 'WithdrewExact',
|
|
2835
|
+
logs: logs.filter((log) => isAddressEqual(log.address, vault)),
|
|
2836
|
+
})
|
|
2837
|
+
if (!log) throw new Error('`WithdrewExact` event not found.')
|
|
2838
|
+
return log
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
/**
|
|
2842
|
+
* Estimates gas for an exact withdrawal, assuming enough vault share allowance.
|
|
2843
|
+
*
|
|
2844
|
+
* @param client - Client.
|
|
2845
|
+
* @param parameters - Parameters.
|
|
2846
|
+
* @returns The gas estimate.
|
|
2847
|
+
*/
|
|
2848
|
+
export async function estimateGas<
|
|
2849
|
+
chain extends Chain | undefined,
|
|
2850
|
+
account extends Account | undefined,
|
|
2851
|
+
>(
|
|
2852
|
+
client: Client<Transport, chain, account>,
|
|
2853
|
+
parameters: withdrawExact.Parameters<chain, account>,
|
|
2854
|
+
): Promise<bigint> {
|
|
2855
|
+
return estimateContractGas(client, {
|
|
2856
|
+
...pickWriteParameters(parameters as never),
|
|
2857
|
+
...withdrawExact.call(
|
|
2858
|
+
await toWithdrawExactArgs(client, parameters as never),
|
|
2859
|
+
),
|
|
2860
|
+
} as never)
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
/**
|
|
2864
|
+
* Simulates an exact withdrawal, assuming enough vault share allowance.
|
|
2865
|
+
*
|
|
2866
|
+
* @param client - Client.
|
|
2867
|
+
* @param parameters - Parameters.
|
|
2868
|
+
* @returns The simulation result and write request.
|
|
2869
|
+
*/
|
|
2870
|
+
export async function simulate<
|
|
2871
|
+
chain extends Chain | undefined,
|
|
2872
|
+
account extends Account | undefined,
|
|
2873
|
+
>(
|
|
2874
|
+
client: Client<Transport, chain, account>,
|
|
2875
|
+
parameters: withdrawExact.Parameters<chain, account>,
|
|
2876
|
+
): Promise<
|
|
2877
|
+
SimulateContractReturnType<typeof Abis.vaultAdapter, 'withdrawExact'>
|
|
2878
|
+
> {
|
|
2879
|
+
return simulateContract(client, {
|
|
2880
|
+
...pickWriteParameters(parameters as never),
|
|
2881
|
+
...withdrawExact.call(
|
|
2882
|
+
await toWithdrawExactArgs(client, parameters as never),
|
|
2883
|
+
),
|
|
2884
|
+
} as never) as never
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
/**
|
|
2889
|
+
* Withdraws an exact asset amount and returns the confirmed receipt and event data.
|
|
2890
|
+
*
|
|
2891
|
+
* @example
|
|
2892
|
+
* ```ts
|
|
2893
|
+
* import { createClient, http } from 'viem'
|
|
2894
|
+
* import { privateKeyToAccount } from 'viem/accounts'
|
|
2895
|
+
* import { tempoModerato } from 'viem/chains'
|
|
2896
|
+
* import { Actions } from 'viem/tempo'
|
|
2897
|
+
*
|
|
2898
|
+
* const client = createClient({
|
|
2899
|
+
* account: privateKeyToAccount('0x...'),
|
|
2900
|
+
* chain: tempoModerato,
|
|
2901
|
+
* transport: http(),
|
|
2902
|
+
* })
|
|
2903
|
+
*
|
|
2904
|
+
* const { shareAmount } = await Actions.earn.withdrawExactSync(client, {
|
|
2905
|
+
* assetAmount: 40_000_000n,
|
|
2906
|
+
* shareAmountMax: 40_200_000n,
|
|
2907
|
+
* vault: '0x...',
|
|
2908
|
+
* })
|
|
2909
|
+
* ```
|
|
2910
|
+
*
|
|
2911
|
+
* @param client - Client.
|
|
2912
|
+
* @param parameters - Parameters.
|
|
2913
|
+
* @returns The transaction receipt and event data.
|
|
2914
|
+
*/
|
|
2915
|
+
export async function withdrawExactSync<
|
|
2916
|
+
chain extends Chain | undefined,
|
|
2917
|
+
account extends Account | undefined,
|
|
2918
|
+
>(
|
|
2919
|
+
client: Client<Transport, chain, account>,
|
|
2920
|
+
parameters: withdrawExactSync.Parameters<chain, account>,
|
|
2921
|
+
): Promise<withdrawExactSync.ReturnValue> {
|
|
2922
|
+
const { throwOnReceiptRevert = true, vault } = parameters
|
|
2923
|
+
const receipt = await withdrawExact.inner(sendTransactionSync, client, {
|
|
2924
|
+
...parameters,
|
|
2925
|
+
throwOnReceiptRevert,
|
|
2926
|
+
} as never)
|
|
2927
|
+
const { args } = withdrawExact.extractEvent(receipt.logs, { vault })
|
|
2928
|
+
return {
|
|
2929
|
+
assetAmount: args.assets,
|
|
2930
|
+
caller: args.caller,
|
|
2931
|
+
receipt,
|
|
2932
|
+
recipient: args.receiver,
|
|
2933
|
+
shareAmount: args.sharesBurned,
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
export namespace withdrawExactSync {
|
|
2938
|
+
export type Args = withdrawExact.Args
|
|
2939
|
+
export type Parameters<
|
|
2940
|
+
chain extends Chain | undefined = Chain | undefined,
|
|
2941
|
+
account extends Account | undefined = Account | undefined,
|
|
2942
|
+
> = withdrawExact.Parameters<chain, account> &
|
|
2943
|
+
WriteSyncParameters<chain, account>
|
|
2944
|
+
export type ReturnValue = Compute<{
|
|
2945
|
+
/** Exact assets received. */
|
|
2946
|
+
assetAmount: bigint
|
|
2947
|
+
/** Withdrawing caller. */
|
|
2948
|
+
caller: Address
|
|
2949
|
+
/** Transaction receipt. */
|
|
2950
|
+
receipt: TransactionReceipt
|
|
2951
|
+
/** Asset recipient. */
|
|
2952
|
+
recipient: Address
|
|
2953
|
+
/** Vault shares burned. */
|
|
2954
|
+
shareAmount: bigint
|
|
2955
|
+
}>
|
|
2956
|
+
// TODO: exhaustive error type
|
|
2957
|
+
export type ErrorType = BaseErrorType
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
type MinimumAssetAmountParameters = OneOf<
|
|
2961
|
+
| {
|
|
2962
|
+
/** Minimum assets returned to the Zone. */
|
|
2963
|
+
assetAmountMin: bigint
|
|
2964
|
+
}
|
|
2965
|
+
| {
|
|
2966
|
+
/** Quoted assets returned to the Zone. */
|
|
2967
|
+
assetAmount: bigint
|
|
2968
|
+
/** Slippage tolerance under `assetAmount` (50 = 0.5%). */
|
|
2969
|
+
slippageBps: number
|
|
2970
|
+
}
|
|
2971
|
+
>
|
|
2972
|
+
|
|
2973
|
+
type MinimumShareAmountParameters = OneOf<
|
|
2974
|
+
| {
|
|
2975
|
+
/** Minimum vault shares returned to the Zone. */
|
|
2976
|
+
shareAmountMin: bigint
|
|
2977
|
+
}
|
|
2978
|
+
| {
|
|
2979
|
+
/** Quoted vault shares returned to the Zone. */
|
|
2980
|
+
shareAmount: bigint
|
|
2981
|
+
/** Slippage tolerance under `shareAmount` (50 = 0.5%). */
|
|
2982
|
+
slippageBps: number
|
|
2983
|
+
}
|
|
2984
|
+
>
|
|
2985
|
+
|
|
2986
|
+
type PrivatePreparationParameters = {
|
|
2987
|
+
/** Optional caller-supplied correlation id. @default Random bytes32 */
|
|
2988
|
+
actionId?: Hex.Hex | undefined
|
|
2989
|
+
/** Gas reserved for the parent-chain callback. @default `10_000_000n` */
|
|
2990
|
+
callbackGas?: bigint | undefined
|
|
2991
|
+
/** Public recipient if the parent-chain callback fails. @default `recoveryRecipient` */
|
|
2992
|
+
fallbackRecipient?: Address | undefined
|
|
2993
|
+
/** Gateway identifying the vault and Zone configuration. */
|
|
2994
|
+
gateway: Address
|
|
2995
|
+
/** Encrypted recipient for the returned tokens. */
|
|
2996
|
+
recipient: Address
|
|
2997
|
+
/** Public recipient if the encrypted return fails. */
|
|
2998
|
+
recoveryRecipient: Address
|
|
2999
|
+
/** Optional memo encrypted with the returned Zone deposit. */
|
|
3000
|
+
returnMemo?: Hex.Hex | undefined
|
|
3001
|
+
/** Optional memo attached to the Zone withdrawal. */
|
|
3002
|
+
withdrawalMemo?: Hex.Hex | undefined
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
type PreparedZoneRequest = {
|
|
3006
|
+
/** Correlation id for the matching wait action. */
|
|
3007
|
+
actionId: Hex.Hex
|
|
3008
|
+
/** Withdrawal amount, passed through to the Zone action. */
|
|
3009
|
+
amount: bigint
|
|
3010
|
+
/** Gas reserved for the parent-chain callback. */
|
|
3011
|
+
callbackGas: bigint
|
|
3012
|
+
/** Parent chain containing the gateway. */
|
|
3013
|
+
chainId: number
|
|
3014
|
+
/** Encoded gateway callback. */
|
|
3015
|
+
data: Hex.Hex
|
|
3016
|
+
/** Public recipient if the parent-chain callback fails. */
|
|
3017
|
+
fallbackRecipient: Address
|
|
3018
|
+
/** Parent-chain block before the withdrawal is submitted. */
|
|
3019
|
+
fromBlock: bigint
|
|
3020
|
+
/** Optional memo attached to the Zone withdrawal. */
|
|
3021
|
+
memo?: Hex.Hex | undefined
|
|
3022
|
+
/** Zone gateway receiving the withdrawal. */
|
|
3023
|
+
to: Address
|
|
3024
|
+
/** Token withdrawn from the Zone. */
|
|
3025
|
+
token: Address
|
|
3026
|
+
/** Zone containing the withdrawn tokens. */
|
|
3027
|
+
zoneId: number
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
const zoneGatewayCallbackGas = 10_000_000n
|
|
3031
|
+
|
|
3032
|
+
function resolveMinimumShareAmount(parameters: MinimumShareAmountParameters) {
|
|
3033
|
+
if (parameters.shareAmountMin !== undefined)
|
|
3034
|
+
return EarnShares.minimumOutput(parameters.shareAmountMin, 0)
|
|
3035
|
+
return EarnShares.minimumOutput(
|
|
3036
|
+
parameters.shareAmount,
|
|
3037
|
+
parameters.slippageBps,
|
|
3038
|
+
)
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
async function assertPreparedZoneRequestChain(
|
|
3042
|
+
client: Client<Transport, Chain | undefined>,
|
|
3043
|
+
parameters: PreparedZoneRequest,
|
|
3044
|
+
) {
|
|
3045
|
+
const chain = client.chain
|
|
3046
|
+
if (!chain) throw new Error('`chain` is required.')
|
|
3047
|
+
if (chain.sourceId !== parameters.chainId)
|
|
3048
|
+
throw new Error(
|
|
3049
|
+
'Prepared Zone request parent chain ID does not match client chain.',
|
|
3050
|
+
)
|
|
3051
|
+
const { zoneId } = await zoneActions.getZoneInfo(client)
|
|
3052
|
+
if (zoneId !== parameters.zoneId)
|
|
3053
|
+
throw new Error(
|
|
3054
|
+
'Prepared Zone request Zone ID does not match client chain.',
|
|
3055
|
+
)
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function pickReadParameters(parameters: Omit<ReadParameters, 'account'>) {
|
|
3059
|
+
const { blockOverrides, stateOverride } = parameters
|
|
3060
|
+
if (parameters.blockNumber !== undefined)
|
|
3061
|
+
return {
|
|
3062
|
+
blockNumber: parameters.blockNumber,
|
|
3063
|
+
blockOverrides,
|
|
3064
|
+
stateOverride,
|
|
3065
|
+
}
|
|
3066
|
+
return { blockOverrides, blockTag: parameters.blockTag, stateOverride }
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
async function getZoneGatewayConfig<chain extends Chain | undefined>(
|
|
3070
|
+
client: Client<Transport, chain>,
|
|
3071
|
+
parameters: Omit<ReadParameters, 'account'> & { gateway: Address },
|
|
3072
|
+
) {
|
|
3073
|
+
const { gateway, ...rest } = parameters
|
|
3074
|
+
const [
|
|
3075
|
+
vaultAdapter,
|
|
3076
|
+
vaultAsset,
|
|
3077
|
+
shareToken,
|
|
3078
|
+
zoneId,
|
|
3079
|
+
zonePortal,
|
|
3080
|
+
supportsAsyncFlow,
|
|
3081
|
+
] = await multicall(client, {
|
|
3082
|
+
...rest,
|
|
3083
|
+
allowFailure: false,
|
|
3084
|
+
contracts: [
|
|
3085
|
+
{
|
|
3086
|
+
abi: Abis.zoneGateway,
|
|
3087
|
+
address: gateway,
|
|
3088
|
+
functionName: 'vaultAdapter',
|
|
3089
|
+
},
|
|
3090
|
+
{
|
|
3091
|
+
abi: Abis.zoneGateway,
|
|
3092
|
+
address: gateway,
|
|
3093
|
+
functionName: 'vaultAsset',
|
|
3094
|
+
},
|
|
3095
|
+
{
|
|
3096
|
+
abi: Abis.zoneGateway,
|
|
3097
|
+
address: gateway,
|
|
3098
|
+
functionName: 'shareToken',
|
|
3099
|
+
},
|
|
3100
|
+
{
|
|
3101
|
+
abi: Abis.zoneGateway,
|
|
3102
|
+
address: gateway,
|
|
3103
|
+
functionName: 'zoneId',
|
|
3104
|
+
},
|
|
3105
|
+
{
|
|
3106
|
+
abi: Abis.zoneGateway,
|
|
3107
|
+
address: gateway,
|
|
3108
|
+
functionName: 'zonePortal',
|
|
3109
|
+
},
|
|
3110
|
+
{
|
|
3111
|
+
abi: Abis.zoneGateway,
|
|
3112
|
+
address: gateway,
|
|
3113
|
+
args: [2],
|
|
3114
|
+
functionName: 'supportsFlow',
|
|
3115
|
+
},
|
|
3116
|
+
],
|
|
3117
|
+
deployless: true,
|
|
3118
|
+
})
|
|
3119
|
+
if (supportsAsyncFlow)
|
|
3120
|
+
throw new Error('Async Zone gateways are not supported.')
|
|
3121
|
+
return { shareToken, vaultAdapter, vaultAsset, zoneId, zonePortal }
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
// ERC-165 ids of the optional engine capability interfaces (XOR of each
|
|
3125
|
+
// interface's function selectors).
|
|
3126
|
+
const interfaceIds = {
|
|
3127
|
+
/** `IVaultEngineAsync`. */
|
|
3128
|
+
asyncRedeem: '0xa1a6a1d7',
|
|
3129
|
+
/** `IVaultEngineExactWithdraw`. */
|
|
3130
|
+
exactWithdraw: '0x0adfb0b9',
|
|
3131
|
+
/** `IVaultEngineShares`. */
|
|
3132
|
+
inKindDeposit: '0x7d28a2f2',
|
|
3133
|
+
/** `IVaultEngineSync`. */
|
|
3134
|
+
syncRedeem: '0x370457f4',
|
|
3135
|
+
} as const
|
|
3136
|
+
|
|
3137
|
+
/** Trims the decoded `IVaultFees.FeeConfig` to its active fixed-fee count. */
|
|
3138
|
+
function toFeeConfig(
|
|
3139
|
+
config: ReadContractReturnType<typeof Abis.vaultAdapter, 'feeConfig'>,
|
|
3140
|
+
): FeeConfig {
|
|
3141
|
+
return {
|
|
3142
|
+
excess: config.excess,
|
|
3143
|
+
fixedFees: config.fixedFees.slice(0, config.fixedFeeCount),
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
/** Trims the decoded `IVaultFees.FeePreview` to its active allocation count. */
|
|
3148
|
+
function toFeePreview(
|
|
3149
|
+
preview: ReadContractReturnType<
|
|
3150
|
+
typeof Abis.vaultAdapter,
|
|
3151
|
+
'previewAccruedFees'
|
|
3152
|
+
>,
|
|
3153
|
+
): FeePreview {
|
|
3154
|
+
const { allocationCount, allocations, ...rest } = preview
|
|
3155
|
+
return { ...rest, allocations: allocations.slice(0, allocationCount) }
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
/** Resolves `deposit` parameters into the adapter call args. @internal */
|
|
3159
|
+
async function toDepositArgs(
|
|
3160
|
+
client: Client<Transport, Chain | undefined, Account | undefined>,
|
|
3161
|
+
parameters: deposit.Parameters,
|
|
3162
|
+
): Promise<deposit.call.Args> {
|
|
3163
|
+
const { vault } = parameters
|
|
3164
|
+
const assetAmount = await toBaseUnitsLive(client, {
|
|
3165
|
+
amount: parameters.assetAmount,
|
|
3166
|
+
token: 'asset',
|
|
3167
|
+
vault,
|
|
3168
|
+
})
|
|
3169
|
+
const args = {
|
|
3170
|
+
assetAmount,
|
|
3171
|
+
recipient: resolveRecipient(client, parameters),
|
|
3172
|
+
vault,
|
|
3173
|
+
}
|
|
3174
|
+
if (parameters.shareAmountMin !== undefined)
|
|
3175
|
+
return { ...args, shareAmountMin: parameters.shareAmountMin }
|
|
3176
|
+
return {
|
|
3177
|
+
...args,
|
|
3178
|
+
shareAmount: parameters.shareAmount,
|
|
3179
|
+
slippageBps: parameters.slippageBps,
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
/** Resolves `depositShares` parameters into the adapter call args. @internal */
|
|
3184
|
+
function toDepositSharesArgs(
|
|
3185
|
+
client: Client<Transport, Chain | undefined, Account | undefined>,
|
|
3186
|
+
parameters: depositShares.Parameters,
|
|
3187
|
+
): depositShares.call.Args {
|
|
3188
|
+
const { vault, venueShareAmount } = parameters
|
|
3189
|
+
const args = {
|
|
3190
|
+
recipient: resolveRecipient(client, parameters),
|
|
3191
|
+
vault,
|
|
3192
|
+
venueShareAmount,
|
|
3193
|
+
}
|
|
3194
|
+
if (parameters.earnShareAmountMin !== undefined)
|
|
3195
|
+
return { ...args, earnShareAmountMin: parameters.earnShareAmountMin }
|
|
3196
|
+
return {
|
|
3197
|
+
...args,
|
|
3198
|
+
earnShareAmount: parameters.earnShareAmount,
|
|
3199
|
+
slippageBps: parameters.slippageBps,
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
/** Resolves `redeem` parameters into the adapter call args. @internal */
|
|
3204
|
+
async function toRedeemArgs(
|
|
3205
|
+
client: Client<Transport, Chain | undefined, Account | undefined>,
|
|
3206
|
+
parameters: redeem.Parameters,
|
|
3207
|
+
): Promise<redeem.call.Args> {
|
|
3208
|
+
const { vault } = parameters
|
|
3209
|
+
const shareAmount = await toBaseUnitsLive(client, {
|
|
3210
|
+
amount: parameters.shareAmount,
|
|
3211
|
+
token: 'shareToken',
|
|
3212
|
+
vault,
|
|
3213
|
+
})
|
|
3214
|
+
const args = {
|
|
3215
|
+
recipient: resolveRecipient(client, parameters),
|
|
3216
|
+
shareAmount,
|
|
3217
|
+
vault,
|
|
3218
|
+
}
|
|
3219
|
+
if (parameters.assetAmountMin !== undefined)
|
|
3220
|
+
return { ...args, assetAmountMin: parameters.assetAmountMin }
|
|
3221
|
+
const assetAmount = await (async () => {
|
|
3222
|
+
if (parameters.assetAmount !== undefined) return parameters.assetAmount
|
|
3223
|
+
return getRedeemQuote(client, { shareAmount, vault })
|
|
3224
|
+
})()
|
|
3225
|
+
return {
|
|
3226
|
+
...args,
|
|
3227
|
+
assetAmount,
|
|
3228
|
+
slippageBps: parameters.slippageBps,
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
/** Resolves `withdrawExact` parameters into the adapter call args. @internal */
|
|
3233
|
+
async function toWithdrawExactArgs(
|
|
3234
|
+
client: Client<Transport, Chain | undefined, Account | undefined>,
|
|
3235
|
+
parameters: withdrawExact.Parameters,
|
|
3236
|
+
): Promise<withdrawExact.call.Args> {
|
|
3237
|
+
const { vault } = parameters
|
|
3238
|
+
const assetAmount = await toBaseUnitsLive(client, {
|
|
3239
|
+
amount: parameters.assetAmount,
|
|
3240
|
+
token: 'asset',
|
|
3241
|
+
vault,
|
|
3242
|
+
})
|
|
3243
|
+
const args = {
|
|
3244
|
+
assetAmount,
|
|
3245
|
+
recipient: resolveRecipient(client, parameters),
|
|
3246
|
+
vault,
|
|
3247
|
+
}
|
|
3248
|
+
if (parameters.shareAmountMax !== undefined)
|
|
3249
|
+
return { ...args, shareAmountMax: parameters.shareAmountMax }
|
|
3250
|
+
const shareAmount = await (async () => {
|
|
3251
|
+
if (parameters.shareAmount !== undefined) return parameters.shareAmount
|
|
3252
|
+
return getWithdrawQuote(client, { assetAmount, vault })
|
|
3253
|
+
})()
|
|
3254
|
+
return {
|
|
3255
|
+
...args,
|
|
3256
|
+
shareAmount,
|
|
3257
|
+
slippageBps: parameters.slippageBps,
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
/** Raises a quoted input by basis points with ceiling rounding. @internal */
|
|
3262
|
+
function maximumInput(shareAmount: bigint, slippageBps: number): bigint {
|
|
3263
|
+
if (shareAmount <= 0n)
|
|
3264
|
+
throw new EarnShares.InvalidExpectedOutputError({
|
|
3265
|
+
expectedAmount: shareAmount,
|
|
3266
|
+
})
|
|
3267
|
+
if (
|
|
3268
|
+
!Number.isInteger(slippageBps) ||
|
|
3269
|
+
slippageBps < 0 ||
|
|
3270
|
+
slippageBps >= EarnShares.basisPointScale
|
|
3271
|
+
)
|
|
3272
|
+
throw new EarnShares.InvalidSlippageError({ slippageBps })
|
|
3273
|
+
const scale = BigInt(EarnShares.basisPointScale)
|
|
3274
|
+
const numerator = shareAmount * (scale + BigInt(slippageBps))
|
|
3275
|
+
// Adding the denominator minus one converts floor division to ceiling.
|
|
3276
|
+
return (numerator + scale - 1n) / scale
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
/**
|
|
3280
|
+
* Converts an amount to base units, resolving missing decimals with live
|
|
3281
|
+
* reads of the vault's asset or share token. Vault share tokens are not
|
|
3282
|
+
* genesis-declared, so nothing is cached. @internal
|
|
3283
|
+
*/
|
|
3284
|
+
async function toBaseUnitsLive(
|
|
3285
|
+
client: Client<Transport, Chain | undefined>,
|
|
3286
|
+
options: {
|
|
3287
|
+
amount: internal_Token.AmountInput
|
|
3288
|
+
token: 'asset' | 'shareToken'
|
|
3289
|
+
vault: Address
|
|
3290
|
+
},
|
|
3291
|
+
): Promise<bigint> {
|
|
3292
|
+
const { amount, token, vault } = options
|
|
3293
|
+
if (typeof amount === 'bigint') return amount
|
|
3294
|
+
if (amount.decimals !== undefined)
|
|
3295
|
+
return internal_Token.toBaseUnits(amount, amount.decimals)
|
|
3296
|
+
const address = await readContract(client, {
|
|
3297
|
+
abi: Abis.vaultAdapter,
|
|
3298
|
+
address: vault,
|
|
3299
|
+
functionName: token,
|
|
3300
|
+
})
|
|
3301
|
+
const { decimals } = await resolveTokenWithDecimals(client, {
|
|
3302
|
+
token: address,
|
|
3303
|
+
})
|
|
3304
|
+
return internal_Token.toBaseUnits(amount, decimals)
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
/** Defaults a write's `recipient` to the sending account's address. @internal */
|
|
3308
|
+
function resolveRecipient(
|
|
3309
|
+
client: Client<Transport, Chain | undefined, Account | undefined>,
|
|
3310
|
+
parameters: {
|
|
3311
|
+
account?: Account | Address | null | undefined
|
|
3312
|
+
recipient?: Address | undefined
|
|
3313
|
+
},
|
|
3314
|
+
): Address {
|
|
3315
|
+
if (parameters.recipient) return parameters.recipient
|
|
3316
|
+
const account = parameters.account ?? client.account
|
|
3317
|
+
if (!account) throw new AccountNotFoundError()
|
|
3318
|
+
return parseAccount(account).address
|
|
3319
|
+
}
|