@vetro-protocol/gateway 1.0.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/_esm/abi/gatewayAbi.js +185 -0
- package/_esm/abi/gatewayAbi.js.map +1 -0
- package/_esm/actions/index.js +18 -0
- package/_esm/actions/index.js.map +1 -0
- package/_esm/actions/public/getMaxWithdraw.js +28 -0
- package/_esm/actions/public/getMaxWithdraw.js.map +1 -0
- package/_esm/actions/public/getMintFee.js +28 -0
- package/_esm/actions/public/getMintFee.js.map +1 -0
- package/_esm/actions/public/getPeggedToken.js +23 -0
- package/_esm/actions/public/getPeggedToken.js.map +1 -0
- package/_esm/actions/public/getRedeemFee.js +28 -0
- package/_esm/actions/public/getRedeemFee.js.map +1 -0
- package/_esm/actions/public/getRedeemRequest.js +28 -0
- package/_esm/actions/public/getRedeemRequest.js.map +1 -0
- package/_esm/actions/public/getTreasury.js +23 -0
- package/_esm/actions/public/getTreasury.js.map +1 -0
- package/_esm/actions/public/getWithdrawalDelay.js +23 -0
- package/_esm/actions/public/getWithdrawalDelay.js.map +1 -0
- package/_esm/actions/public/getWithdrawalDelayEnabled.js +23 -0
- package/_esm/actions/public/getWithdrawalDelayEnabled.js.map +1 -0
- package/_esm/actions/public/index.js +12 -0
- package/_esm/actions/public/index.js.map +1 -0
- package/_esm/actions/public/isInstantRedeemWhitelisted.js +28 -0
- package/_esm/actions/public/isInstantRedeemWhitelisted.js.map +1 -0
- package/_esm/actions/public/previewDeposit.js +35 -0
- package/_esm/actions/public/previewDeposit.js.map +1 -0
- package/_esm/actions/public/previewRedeem.js +35 -0
- package/_esm/actions/public/previewRedeem.js.map +1 -0
- package/_esm/actions/wallet/cancelRedeemRequest.js +89 -0
- package/_esm/actions/wallet/cancelRedeemRequest.js.map +1 -0
- package/_esm/actions/wallet/deposit.js +193 -0
- package/_esm/actions/wallet/deposit.js.map +1 -0
- package/_esm/actions/wallet/redeem.js +226 -0
- package/_esm/actions/wallet/redeem.js.map +1 -0
- package/_esm/actions/wallet/requestRedeem.js +153 -0
- package/_esm/actions/wallet/requestRedeem.js.map +1 -0
- package/_esm/gatewayAddresses.js +18 -0
- package/_esm/gatewayAddresses.js.map +1 -0
- package/_esm/index.js +30 -0
- package/_esm/index.js.map +1 -0
- package/_esm/types.js +2 -0
- package/_esm/types.js.map +1 -0
- package/_esm/utils/isAddressValid.js +3 -0
- package/_esm/utils/isAddressValid.js.map +1 -0
- package/_types/abi/gatewayAbi.d.ts +252 -0
- package/_types/abi/gatewayAbi.d.ts.map +1 -0
- package/_types/actions/index.d.ts +16 -0
- package/_types/actions/index.d.ts.map +1 -0
- package/_types/actions/public/getMaxWithdraw.d.ts +6 -0
- package/_types/actions/public/getMaxWithdraw.d.ts.map +1 -0
- package/_types/actions/public/getMintFee.d.ts +6 -0
- package/_types/actions/public/getMintFee.d.ts.map +1 -0
- package/_types/actions/public/getPeggedToken.d.ts +5 -0
- package/_types/actions/public/getPeggedToken.d.ts.map +1 -0
- package/_types/actions/public/getRedeemFee.d.ts +6 -0
- package/_types/actions/public/getRedeemFee.d.ts.map +1 -0
- package/_types/actions/public/getRedeemRequest.d.ts +6 -0
- package/_types/actions/public/getRedeemRequest.d.ts.map +1 -0
- package/_types/actions/public/getTreasury.d.ts +5 -0
- package/_types/actions/public/getTreasury.d.ts.map +1 -0
- package/_types/actions/public/getWithdrawalDelay.d.ts +5 -0
- package/_types/actions/public/getWithdrawalDelay.d.ts.map +1 -0
- package/_types/actions/public/getWithdrawalDelayEnabled.d.ts +5 -0
- package/_types/actions/public/getWithdrawalDelayEnabled.d.ts.map +1 -0
- package/_types/actions/public/index.d.ts +12 -0
- package/_types/actions/public/index.d.ts.map +1 -0
- package/_types/actions/public/isInstantRedeemWhitelisted.d.ts +6 -0
- package/_types/actions/public/isInstantRedeemWhitelisted.d.ts.map +1 -0
- package/_types/actions/public/previewDeposit.d.ts +7 -0
- package/_types/actions/public/previewDeposit.d.ts.map +1 -0
- package/_types/actions/public/previewRedeem.d.ts +7 -0
- package/_types/actions/public/previewRedeem.d.ts.map +1 -0
- package/_types/actions/wallet/cancelRedeemRequest.d.ts +14 -0
- package/_types/actions/wallet/cancelRedeemRequest.d.ts.map +1 -0
- package/_types/actions/wallet/deposit.d.ts +19 -0
- package/_types/actions/wallet/deposit.d.ts.map +1 -0
- package/_types/actions/wallet/redeem.d.ts +24 -0
- package/_types/actions/wallet/redeem.d.ts.map +1 -0
- package/_types/actions/wallet/requestRedeem.d.ts +18 -0
- package/_types/actions/wallet/requestRedeem.d.ts.map +1 -0
- package/_types/gatewayAddresses.d.ts +8 -0
- package/_types/gatewayAddresses.d.ts.map +1 -0
- package/_types/index.d.ts +42 -0
- package/_types/index.d.ts.map +1 -0
- package/_types/types.d.ts +53 -0
- package/_types/types.d.ts.map +1 -0
- package/_types/utils/isAddressValid.d.ts +3 -0
- package/_types/utils/isAddressValid.d.ts.map +1 -0
- package/package.json +59 -0
- package/src/abi/gatewayAbi.ts +184 -0
- package/src/actions/index.ts +18 -0
- package/src/actions/public/getMaxWithdraw.ts +40 -0
- package/src/actions/public/getMintFee.ts +40 -0
- package/src/actions/public/getPeggedToken.ts +33 -0
- package/src/actions/public/getRedeemFee.ts +40 -0
- package/src/actions/public/getRedeemRequest.ts +40 -0
- package/src/actions/public/getTreasury.ts +33 -0
- package/src/actions/public/getWithdrawalDelay.ts +33 -0
- package/src/actions/public/getWithdrawalDelayEnabled.ts +33 -0
- package/src/actions/public/index.ts +11 -0
- package/src/actions/public/isInstantRedeemWhitelisted.ts +40 -0
- package/src/actions/public/previewDeposit.ts +49 -0
- package/src/actions/public/previewRedeem.ts +49 -0
- package/src/actions/wallet/cancelRedeemRequest.ts +135 -0
- package/src/actions/wallet/deposit.ts +277 -0
- package/src/actions/wallet/redeem.ts +321 -0
- package/src/actions/wallet/requestRedeem.ts +226 -0
- package/src/gatewayAddresses.ts +27 -0
- package/src/index.ts +79 -0
- package/src/types.ts +60 -0
- package/src/utils/isAddressValid.ts +4 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { toPromiseEvent } from "to-promise-event";
|
|
3
|
+
import {
|
|
4
|
+
type Address,
|
|
5
|
+
type TransactionReceipt,
|
|
6
|
+
type WalletClient,
|
|
7
|
+
encodeFunctionData,
|
|
8
|
+
isAddress,
|
|
9
|
+
isAddressEqual,
|
|
10
|
+
zeroAddress,
|
|
11
|
+
} from "viem";
|
|
12
|
+
import { waitForTransactionReceipt, writeContract } from "viem/actions";
|
|
13
|
+
import { allowance, approve } from "viem-erc20/actions";
|
|
14
|
+
|
|
15
|
+
import { gatewayAbi } from "../../abi/gatewayAbi.js";
|
|
16
|
+
import type { RedeemEvents } from "../../types.js";
|
|
17
|
+
import { getMaxWithdraw } from "../public/getMaxWithdraw.js";
|
|
18
|
+
import { getPeggedToken } from "../public/getPeggedToken.js";
|
|
19
|
+
import { getWithdrawalDelayEnabled } from "../public/getWithdrawalDelayEnabled.js";
|
|
20
|
+
import { isInstantRedeemWhitelisted } from "../public/isInstantRedeemWhitelisted.js";
|
|
21
|
+
|
|
22
|
+
export type RedeemParams = {
|
|
23
|
+
approveAmount?: bigint;
|
|
24
|
+
gatewayAddress: Address;
|
|
25
|
+
minAmountOut: bigint;
|
|
26
|
+
peggedTokenIn: bigint;
|
|
27
|
+
receiver: Address;
|
|
28
|
+
tokenOut: Address;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const canRedeem = async function ({
|
|
32
|
+
approveAmount,
|
|
33
|
+
client,
|
|
34
|
+
gatewayAddress,
|
|
35
|
+
minAmountOut,
|
|
36
|
+
peggedTokenIn,
|
|
37
|
+
receiver,
|
|
38
|
+
tokenOut,
|
|
39
|
+
}: {
|
|
40
|
+
approveAmount: bigint;
|
|
41
|
+
client: WalletClient;
|
|
42
|
+
gatewayAddress: Address;
|
|
43
|
+
minAmountOut: bigint;
|
|
44
|
+
peggedTokenIn: bigint;
|
|
45
|
+
receiver: Address;
|
|
46
|
+
tokenOut: Address;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
canRedeem: boolean;
|
|
49
|
+
reason?: string;
|
|
50
|
+
}> {
|
|
51
|
+
// Validate client
|
|
52
|
+
if (!client) {
|
|
53
|
+
return {
|
|
54
|
+
canRedeem: false,
|
|
55
|
+
reason: "Client is not defined",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!client.chain) {
|
|
59
|
+
return {
|
|
60
|
+
canRedeem: false,
|
|
61
|
+
reason: "Chain is not defined on wallet client",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Validate client has required properties
|
|
65
|
+
if (!client.account) {
|
|
66
|
+
return {
|
|
67
|
+
canRedeem: false,
|
|
68
|
+
reason: "Client must have an account",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Validate gateway address
|
|
72
|
+
if (!gatewayAddress || !isAddress(gatewayAddress)) {
|
|
73
|
+
return {
|
|
74
|
+
canRedeem: false,
|
|
75
|
+
reason: "Invalid gateway address",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (isAddressEqual(gatewayAddress, zeroAddress)) {
|
|
79
|
+
return {
|
|
80
|
+
canRedeem: false,
|
|
81
|
+
reason: "Gateway address cannot be zero address",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Validate tokenOut address
|
|
85
|
+
if (!tokenOut || !isAddress(tokenOut)) {
|
|
86
|
+
return {
|
|
87
|
+
canRedeem: false,
|
|
88
|
+
reason: "Invalid token address",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (isAddressEqual(tokenOut, zeroAddress)) {
|
|
92
|
+
return {
|
|
93
|
+
canRedeem: false,
|
|
94
|
+
reason: "Token address cannot be zero address",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Validate receiver address
|
|
99
|
+
if (!receiver || !isAddress(receiver)) {
|
|
100
|
+
return {
|
|
101
|
+
canRedeem: false,
|
|
102
|
+
reason: "Invalid receiver address",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (isAddressEqual(receiver, zeroAddress)) {
|
|
106
|
+
return {
|
|
107
|
+
canRedeem: false,
|
|
108
|
+
reason: "Receiver address cannot be zero address",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Validate peggedTokenIn
|
|
113
|
+
if (typeof peggedTokenIn !== "bigint") {
|
|
114
|
+
return {
|
|
115
|
+
canRedeem: false,
|
|
116
|
+
reason: "Amount must be a bigint",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (peggedTokenIn <= 0n) {
|
|
120
|
+
return {
|
|
121
|
+
canRedeem: false,
|
|
122
|
+
reason: "Amount must be greater than 0",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate minAmountOut
|
|
127
|
+
if (typeof minAmountOut !== "bigint") {
|
|
128
|
+
return {
|
|
129
|
+
canRedeem: false,
|
|
130
|
+
reason: "Minimum output must be a bigint",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (minAmountOut < 0n) {
|
|
134
|
+
return {
|
|
135
|
+
canRedeem: false,
|
|
136
|
+
reason: "Minimum output cannot be negative",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Validate approveAmount
|
|
140
|
+
if (approveAmount < peggedTokenIn) {
|
|
141
|
+
return {
|
|
142
|
+
canRedeem: false,
|
|
143
|
+
reason: "Approve amount must be greater than or equal to amount",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check treasury has enough balance for the requested token
|
|
148
|
+
const maxWithdrawable = await getMaxWithdraw(client, {
|
|
149
|
+
address: gatewayAddress,
|
|
150
|
+
tokenOut,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (minAmountOut > maxWithdrawable) {
|
|
154
|
+
return {
|
|
155
|
+
canRedeem: false,
|
|
156
|
+
reason: "Not enough balance in treasury",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { canRedeem: true };
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const runRedeem = (
|
|
164
|
+
walletClient: WalletClient,
|
|
165
|
+
{
|
|
166
|
+
approveAmount,
|
|
167
|
+
gatewayAddress,
|
|
168
|
+
minAmountOut,
|
|
169
|
+
peggedTokenIn,
|
|
170
|
+
receiver,
|
|
171
|
+
tokenOut,
|
|
172
|
+
}: RedeemParams,
|
|
173
|
+
) =>
|
|
174
|
+
async function (emitter: EventEmitter<RedeemEvents>) {
|
|
175
|
+
const resolvedApproveAmount = approveAmount ?? peggedTokenIn;
|
|
176
|
+
try {
|
|
177
|
+
const { canRedeem: canRedeemFlag, reason } = await canRedeem({
|
|
178
|
+
approveAmount: resolvedApproveAmount,
|
|
179
|
+
client: walletClient,
|
|
180
|
+
gatewayAddress,
|
|
181
|
+
minAmountOut,
|
|
182
|
+
peggedTokenIn,
|
|
183
|
+
receiver,
|
|
184
|
+
tokenOut,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!canRedeemFlag) {
|
|
188
|
+
emitter.emit("redeem-failed-validation", reason!);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check if user is whitelisted for instant redeem
|
|
193
|
+
const [isWhitelisted, delayEnabled] = await Promise.all([
|
|
194
|
+
isInstantRedeemWhitelisted(walletClient, {
|
|
195
|
+
account: walletClient.account!.address,
|
|
196
|
+
address: gatewayAddress,
|
|
197
|
+
}),
|
|
198
|
+
getWithdrawalDelayEnabled(walletClient, {
|
|
199
|
+
address: gatewayAddress,
|
|
200
|
+
}),
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
// Approval is needed when the user is whitelisted (instant redeem burns pegged tokens)
|
|
204
|
+
// or when delay is disabled (redeem completes immediately)
|
|
205
|
+
if (isWhitelisted || !delayEnabled) {
|
|
206
|
+
// Get the pegged token address
|
|
207
|
+
const peggedTokenAddress = await getPeggedToken(walletClient, {
|
|
208
|
+
address: gatewayAddress,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Check current allowance for pegged token
|
|
212
|
+
const currentAllowance = await allowance(walletClient, {
|
|
213
|
+
address: peggedTokenAddress,
|
|
214
|
+
owner: walletClient.account!.address,
|
|
215
|
+
spender: gatewayAddress,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// If allowance is insufficient, approve first
|
|
219
|
+
if (currentAllowance < peggedTokenIn) {
|
|
220
|
+
emitter.emit("pre-approve");
|
|
221
|
+
|
|
222
|
+
const approvalHash = await approve(walletClient, {
|
|
223
|
+
address: peggedTokenAddress,
|
|
224
|
+
amount: resolvedApproveAmount,
|
|
225
|
+
spender: gatewayAddress,
|
|
226
|
+
}).catch(function (error: Error) {
|
|
227
|
+
emitter.emit("user-signing-approval-error", error);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (!approvalHash) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
emitter.emit("user-signed-approval", approvalHash);
|
|
235
|
+
|
|
236
|
+
const approvalReceipt = await waitForTransactionReceipt(
|
|
237
|
+
walletClient,
|
|
238
|
+
{
|
|
239
|
+
hash: approvalHash,
|
|
240
|
+
},
|
|
241
|
+
).catch(function (error: Error) {
|
|
242
|
+
emitter.emit("redeem-failed", error);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!approvalReceipt) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (approvalReceipt.status === "reverted") {
|
|
250
|
+
emitter.emit("approve-transaction-reverted", approvalReceipt);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
emitter.emit("approve-transaction-succeeded", approvalReceipt);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
emitter.emit("pre-redeem");
|
|
259
|
+
|
|
260
|
+
const redeemHash = await writeContract(walletClient, {
|
|
261
|
+
abi: gatewayAbi,
|
|
262
|
+
account: walletClient.account!,
|
|
263
|
+
address: gatewayAddress,
|
|
264
|
+
args: [tokenOut, peggedTokenIn, minAmountOut, receiver],
|
|
265
|
+
chain: walletClient.chain,
|
|
266
|
+
functionName: "redeem",
|
|
267
|
+
}).catch(function (error: Error) {
|
|
268
|
+
emitter.emit("user-signing-redeem-error", error);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (!redeemHash) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
emitter.emit("user-signed-redeem", redeemHash);
|
|
276
|
+
|
|
277
|
+
const redeemReceipt = await waitForTransactionReceipt(walletClient, {
|
|
278
|
+
hash: redeemHash,
|
|
279
|
+
}).catch(function (error: Error) {
|
|
280
|
+
emitter.emit("redeem-failed", error);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!redeemReceipt) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const redeemEventMap: Record<
|
|
288
|
+
TransactionReceipt["status"],
|
|
289
|
+
keyof RedeemEvents
|
|
290
|
+
> = {
|
|
291
|
+
reverted: "redeem-transaction-reverted",
|
|
292
|
+
success: "redeem-transaction-succeeded",
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
emitter.emit(redeemEventMap[redeemReceipt.status], redeemReceipt);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
emitter.emit("unexpected-error", error as Error);
|
|
298
|
+
} finally {
|
|
299
|
+
emitter.emit("redeem-settled");
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export const redeem = (...args: Parameters<typeof runRedeem>) =>
|
|
304
|
+
toPromiseEvent<RedeemEvents>(runRedeem(...args));
|
|
305
|
+
|
|
306
|
+
export const encodeRedeem = ({
|
|
307
|
+
minAmountOut,
|
|
308
|
+
peggedTokenIn,
|
|
309
|
+
receiver,
|
|
310
|
+
tokenOut,
|
|
311
|
+
}: {
|
|
312
|
+
minAmountOut: bigint;
|
|
313
|
+
peggedTokenIn: bigint;
|
|
314
|
+
receiver: Address;
|
|
315
|
+
tokenOut: Address;
|
|
316
|
+
}) =>
|
|
317
|
+
encodeFunctionData({
|
|
318
|
+
abi: gatewayAbi,
|
|
319
|
+
args: [tokenOut, peggedTokenIn, minAmountOut, receiver],
|
|
320
|
+
functionName: "redeem",
|
|
321
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { toPromiseEvent } from "to-promise-event";
|
|
3
|
+
import {
|
|
4
|
+
type Address,
|
|
5
|
+
type TransactionReceipt,
|
|
6
|
+
type WalletClient,
|
|
7
|
+
encodeFunctionData,
|
|
8
|
+
isAddress,
|
|
9
|
+
isAddressEqual,
|
|
10
|
+
zeroAddress,
|
|
11
|
+
} from "viem";
|
|
12
|
+
import { waitForTransactionReceipt, writeContract } from "viem/actions";
|
|
13
|
+
import { allowance, approve } from "viem-erc20/actions";
|
|
14
|
+
|
|
15
|
+
import { gatewayAbi } from "../../abi/gatewayAbi.js";
|
|
16
|
+
import type { RequestRedeemEvents } from "../../types.js";
|
|
17
|
+
import { getPeggedToken } from "../public/getPeggedToken.js";
|
|
18
|
+
|
|
19
|
+
export type RequestRedeemParams = {
|
|
20
|
+
approveAmount?: bigint;
|
|
21
|
+
gatewayAddress: Address;
|
|
22
|
+
peggedTokenAmount: bigint;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const canRequestRedeem = function ({
|
|
26
|
+
approveAmount,
|
|
27
|
+
client,
|
|
28
|
+
gatewayAddress,
|
|
29
|
+
peggedTokenAmount,
|
|
30
|
+
}: {
|
|
31
|
+
approveAmount: bigint;
|
|
32
|
+
client: WalletClient;
|
|
33
|
+
gatewayAddress: Address;
|
|
34
|
+
peggedTokenAmount: bigint;
|
|
35
|
+
}): {
|
|
36
|
+
canRequestRedeem: boolean;
|
|
37
|
+
reason?: string;
|
|
38
|
+
} {
|
|
39
|
+
if (!client) {
|
|
40
|
+
return {
|
|
41
|
+
canRequestRedeem: false,
|
|
42
|
+
reason: "Client is not defined",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!client.chain) {
|
|
46
|
+
return {
|
|
47
|
+
canRequestRedeem: false,
|
|
48
|
+
reason: "Chain is not defined on wallet client",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (!client.account) {
|
|
52
|
+
return {
|
|
53
|
+
canRequestRedeem: false,
|
|
54
|
+
reason: "Client must have an account",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Validate gateway address
|
|
58
|
+
if (!gatewayAddress || !isAddress(gatewayAddress)) {
|
|
59
|
+
return {
|
|
60
|
+
canRequestRedeem: false,
|
|
61
|
+
reason: "Invalid gateway address",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (isAddressEqual(gatewayAddress, zeroAddress)) {
|
|
65
|
+
return {
|
|
66
|
+
canRequestRedeem: false,
|
|
67
|
+
reason: "Gateway address cannot be zero address",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof peggedTokenAmount !== "bigint") {
|
|
72
|
+
return {
|
|
73
|
+
canRequestRedeem: false,
|
|
74
|
+
reason: "Amount must be a bigint",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (peggedTokenAmount <= 0n) {
|
|
78
|
+
return {
|
|
79
|
+
canRequestRedeem: false,
|
|
80
|
+
reason: "Amount must be greater than 0",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate approveAmount
|
|
85
|
+
if (approveAmount < peggedTokenAmount) {
|
|
86
|
+
return {
|
|
87
|
+
canRequestRedeem: false,
|
|
88
|
+
reason: "Approve amount must be greater than or equal to amount",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { canRequestRedeem: true };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const runRequestRedeem = (
|
|
96
|
+
walletClient: WalletClient,
|
|
97
|
+
{ approveAmount, gatewayAddress, peggedTokenAmount }: RequestRedeemParams,
|
|
98
|
+
) =>
|
|
99
|
+
async function (emitter: EventEmitter<RequestRedeemEvents>) {
|
|
100
|
+
const resolvedApproveAmount = approveAmount ?? peggedTokenAmount;
|
|
101
|
+
try {
|
|
102
|
+
const { canRequestRedeem: canRequestRedeemFlag, reason } =
|
|
103
|
+
canRequestRedeem({
|
|
104
|
+
approveAmount: resolvedApproveAmount,
|
|
105
|
+
client: walletClient,
|
|
106
|
+
gatewayAddress,
|
|
107
|
+
peggedTokenAmount,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!canRequestRedeemFlag) {
|
|
111
|
+
emitter.emit("request-redeem-failed-validation", reason!);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Get the pegged token address
|
|
116
|
+
const peggedTokenAddress = await getPeggedToken(walletClient, {
|
|
117
|
+
address: gatewayAddress,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Check current allowance for pegged token
|
|
121
|
+
const currentAllowance = await allowance(walletClient, {
|
|
122
|
+
address: peggedTokenAddress,
|
|
123
|
+
owner: walletClient.account!.address,
|
|
124
|
+
spender: gatewayAddress,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// If allowance is insufficient, approve first
|
|
128
|
+
if (currentAllowance < peggedTokenAmount) {
|
|
129
|
+
emitter.emit("pre-approve");
|
|
130
|
+
|
|
131
|
+
const approvalHash = await approve(walletClient, {
|
|
132
|
+
address: peggedTokenAddress,
|
|
133
|
+
amount: resolvedApproveAmount,
|
|
134
|
+
spender: gatewayAddress,
|
|
135
|
+
}).catch(function (error: Error) {
|
|
136
|
+
emitter.emit("user-signing-approval-error", error);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!approvalHash) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
emitter.emit("user-signed-approval", approvalHash);
|
|
144
|
+
|
|
145
|
+
const approvalReceipt = await waitForTransactionReceipt(walletClient, {
|
|
146
|
+
hash: approvalHash,
|
|
147
|
+
}).catch(function (error: Error) {
|
|
148
|
+
emitter.emit("request-redeem-failed", error);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!approvalReceipt) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (approvalReceipt.status === "reverted") {
|
|
156
|
+
emitter.emit("approve-transaction-reverted", approvalReceipt);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
emitter.emit("approve-transaction-succeeded", approvalReceipt);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
emitter.emit("pre-request-redeem");
|
|
164
|
+
|
|
165
|
+
const requestRedeemHash = await writeContract(walletClient, {
|
|
166
|
+
abi: gatewayAbi,
|
|
167
|
+
account: walletClient.account!,
|
|
168
|
+
address: gatewayAddress,
|
|
169
|
+
args: [peggedTokenAmount],
|
|
170
|
+
chain: walletClient.chain,
|
|
171
|
+
functionName: "requestRedeem",
|
|
172
|
+
}).catch(function (error: Error) {
|
|
173
|
+
emitter.emit("user-signing-request-redeem-error", error);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (!requestRedeemHash) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
emitter.emit("user-signed-request-redeem", requestRedeemHash);
|
|
181
|
+
|
|
182
|
+
const requestRedeemReceipt = await waitForTransactionReceipt(
|
|
183
|
+
walletClient,
|
|
184
|
+
{
|
|
185
|
+
hash: requestRedeemHash,
|
|
186
|
+
},
|
|
187
|
+
).catch(function (error: Error) {
|
|
188
|
+
emitter.emit("request-redeem-failed", error);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (!requestRedeemReceipt) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const requestRedeemEventMap: Record<
|
|
196
|
+
TransactionReceipt["status"],
|
|
197
|
+
keyof RequestRedeemEvents
|
|
198
|
+
> = {
|
|
199
|
+
reverted: "request-redeem-transaction-reverted",
|
|
200
|
+
success: "request-redeem-transaction-succeeded",
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
emitter.emit(
|
|
204
|
+
requestRedeemEventMap[requestRedeemReceipt.status],
|
|
205
|
+
requestRedeemReceipt,
|
|
206
|
+
);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
emitter.emit("unexpected-error", error as Error);
|
|
209
|
+
} finally {
|
|
210
|
+
emitter.emit("request-redeem-settled");
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export const requestRedeem = (...args: Parameters<typeof runRequestRedeem>) =>
|
|
215
|
+
toPromiseEvent<RequestRedeemEvents>(runRequestRedeem(...args));
|
|
216
|
+
|
|
217
|
+
export const encodeRequestRedeem = ({
|
|
218
|
+
peggedTokenAmount,
|
|
219
|
+
}: {
|
|
220
|
+
peggedTokenAmount: bigint;
|
|
221
|
+
}) =>
|
|
222
|
+
encodeFunctionData({
|
|
223
|
+
abi: gatewayAbi,
|
|
224
|
+
args: [peggedTokenAmount],
|
|
225
|
+
functionName: "requestRedeem",
|
|
226
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Address } from "viem";
|
|
2
|
+
|
|
3
|
+
export type Gateway = {
|
|
4
|
+
address: Address;
|
|
5
|
+
// Portal-API symbol used to convert this gateway's peg unit into USD.
|
|
6
|
+
// "USD" is treated as identity (no conversion needed).
|
|
7
|
+
pegBaseSymbol: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Whitelisted-token oracles within a gateway are denominated in the gateway's
|
|
11
|
+
// peg unit (e.g. WBTC/BTC for the vetBTC gateway, USDT/USD for the VUSD gateway),
|
|
12
|
+
// so the USD price for any whitelisted token is `oracle × portal[pegBaseSymbol]`.
|
|
13
|
+
// See `web/src/fetchers/fetchPrices.ts` for the merge.
|
|
14
|
+
export const gateways: Gateway[] = [
|
|
15
|
+
// VUSD
|
|
16
|
+
{
|
|
17
|
+
address: "0xDaD503f8B9d42bb7af3AfC588358D30163e4416F",
|
|
18
|
+
pegBaseSymbol: "USD",
|
|
19
|
+
},
|
|
20
|
+
// vetBTC
|
|
21
|
+
{
|
|
22
|
+
address: "0xCBA2Ffa0AC52d7871a4221a871793Eb788013faB",
|
|
23
|
+
pegBaseSymbol: "BTC",
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const gatewayAddresses: Address[] = gateways.map((g) => g.address);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Client, WalletClient } from "viem";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getMaxWithdraw,
|
|
5
|
+
getMintFee,
|
|
6
|
+
getPeggedToken,
|
|
7
|
+
getRedeemFee,
|
|
8
|
+
getRedeemRequest,
|
|
9
|
+
getTreasury,
|
|
10
|
+
getWithdrawalDelay,
|
|
11
|
+
getWithdrawalDelayEnabled,
|
|
12
|
+
isInstantRedeemWhitelisted,
|
|
13
|
+
previewDeposit,
|
|
14
|
+
previewRedeem,
|
|
15
|
+
} from "./actions/public/index.js";
|
|
16
|
+
import {
|
|
17
|
+
type CancelRedeemRequestParams,
|
|
18
|
+
cancelRedeemRequest,
|
|
19
|
+
} from "./actions/wallet/cancelRedeemRequest.js";
|
|
20
|
+
import { type DepositParams, deposit } from "./actions/wallet/deposit.js";
|
|
21
|
+
import { type RedeemParams, redeem } from "./actions/wallet/redeem.js";
|
|
22
|
+
import {
|
|
23
|
+
type RequestRedeemParams,
|
|
24
|
+
requestRedeem,
|
|
25
|
+
} from "./actions/wallet/requestRedeem.js";
|
|
26
|
+
|
|
27
|
+
// Export ABI
|
|
28
|
+
export { gatewayAbi } from "./abi/gatewayAbi.js";
|
|
29
|
+
// Export gateway addresses and per-gateway peg-base metadata
|
|
30
|
+
export {
|
|
31
|
+
type Gateway,
|
|
32
|
+
gatewayAddresses,
|
|
33
|
+
gateways,
|
|
34
|
+
} from "./gatewayAddresses.js";
|
|
35
|
+
|
|
36
|
+
export type { CancelRedeemRequestParams };
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
type CancelRedeemRequestEvents,
|
|
40
|
+
type DepositEvents,
|
|
41
|
+
type RedeemEvents,
|
|
42
|
+
type RequestRedeemEvents,
|
|
43
|
+
} from "./types.js";
|
|
44
|
+
|
|
45
|
+
// Export factory functions for .extend() pattern
|
|
46
|
+
export const gatewayPublicActions = () => (client: Client) => ({
|
|
47
|
+
getMaxWithdraw: (params: Parameters<typeof getMaxWithdraw>[1]) =>
|
|
48
|
+
getMaxWithdraw(client, params),
|
|
49
|
+
getMintFee: (params: Parameters<typeof getMintFee>[1]) =>
|
|
50
|
+
getMintFee(client, params),
|
|
51
|
+
getPeggedToken: (params: Parameters<typeof getPeggedToken>[1]) =>
|
|
52
|
+
getPeggedToken(client, params),
|
|
53
|
+
getRedeemFee: (params: Parameters<typeof getRedeemFee>[1]) =>
|
|
54
|
+
getRedeemFee(client, params),
|
|
55
|
+
getRedeemRequest: (params: Parameters<typeof getRedeemRequest>[1]) =>
|
|
56
|
+
getRedeemRequest(client, params),
|
|
57
|
+
getTreasury: (params: Parameters<typeof getTreasury>[1]) =>
|
|
58
|
+
getTreasury(client, params),
|
|
59
|
+
getWithdrawalDelay: (params: Parameters<typeof getWithdrawalDelay>[1]) =>
|
|
60
|
+
getWithdrawalDelay(client, params),
|
|
61
|
+
getWithdrawalDelayEnabled: (
|
|
62
|
+
params: Parameters<typeof getWithdrawalDelayEnabled>[1],
|
|
63
|
+
) => getWithdrawalDelayEnabled(client, params),
|
|
64
|
+
isInstantRedeemWhitelisted: (
|
|
65
|
+
params: Parameters<typeof isInstantRedeemWhitelisted>[1],
|
|
66
|
+
) => isInstantRedeemWhitelisted(client, params),
|
|
67
|
+
previewDeposit: (params: Parameters<typeof previewDeposit>[1]) =>
|
|
68
|
+
previewDeposit(client, params),
|
|
69
|
+
previewRedeem: (params: Parameters<typeof previewRedeem>[1]) =>
|
|
70
|
+
previewRedeem(client, params),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const gatewayWalletActions = () => (client: WalletClient) => ({
|
|
74
|
+
cancelRedeemRequest: (params: CancelRedeemRequestParams) =>
|
|
75
|
+
cancelRedeemRequest(client, params),
|
|
76
|
+
deposit: (params: DepositParams) => deposit(client, params),
|
|
77
|
+
redeem: (params: RedeemParams) => redeem(client, params),
|
|
78
|
+
requestRedeem: (params: RequestRedeemParams) => requestRedeem(client, params),
|
|
79
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Hash, TransactionReceipt } from "viem";
|
|
2
|
+
|
|
3
|
+
type CommonEvents = {
|
|
4
|
+
"unexpected-error": [Error];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type ApprovalEvents = {
|
|
8
|
+
"approve-transaction-reverted": [TransactionReceipt];
|
|
9
|
+
"approve-transaction-succeeded": [TransactionReceipt];
|
|
10
|
+
"pre-approve": [];
|
|
11
|
+
"user-signed-approval": [Hash];
|
|
12
|
+
"user-signing-approval-error": [Error];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type DepositEvents = ApprovalEvents &
|
|
16
|
+
CommonEvents & {
|
|
17
|
+
"deposit-failed": [Error];
|
|
18
|
+
"deposit-failed-validation": [string];
|
|
19
|
+
"deposit-settled": [];
|
|
20
|
+
"deposit-transaction-reverted": [TransactionReceipt];
|
|
21
|
+
"deposit-transaction-succeeded": [TransactionReceipt];
|
|
22
|
+
"pre-deposit": [];
|
|
23
|
+
"user-signed-deposit": [Hash];
|
|
24
|
+
"user-signing-deposit-error": [Error];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type CancelRedeemRequestEvents = CommonEvents & {
|
|
28
|
+
"cancel-redeem-request-failed": [Error];
|
|
29
|
+
"cancel-redeem-request-failed-validation": [string];
|
|
30
|
+
"cancel-redeem-request-settled": [];
|
|
31
|
+
"cancel-redeem-request-transaction-reverted": [TransactionReceipt];
|
|
32
|
+
"cancel-redeem-request-transaction-succeeded": [TransactionReceipt];
|
|
33
|
+
"pre-cancel-redeem-request": [];
|
|
34
|
+
"user-signed-cancel-redeem-request": [Hash];
|
|
35
|
+
"user-signing-cancel-redeem-request-error": [Error];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type RedeemEvents = ApprovalEvents &
|
|
39
|
+
CommonEvents & {
|
|
40
|
+
"pre-redeem": [];
|
|
41
|
+
"redeem-failed": [Error];
|
|
42
|
+
"redeem-failed-validation": [string];
|
|
43
|
+
"redeem-settled": [];
|
|
44
|
+
"redeem-transaction-reverted": [TransactionReceipt];
|
|
45
|
+
"redeem-transaction-succeeded": [TransactionReceipt];
|
|
46
|
+
"user-signed-redeem": [Hash];
|
|
47
|
+
"user-signing-redeem-error": [Error];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type RequestRedeemEvents = ApprovalEvents &
|
|
51
|
+
CommonEvents & {
|
|
52
|
+
"pre-request-redeem": [];
|
|
53
|
+
"request-redeem-failed": [Error];
|
|
54
|
+
"request-redeem-failed-validation": [string];
|
|
55
|
+
"request-redeem-settled": [];
|
|
56
|
+
"request-redeem-transaction-reverted": [TransactionReceipt];
|
|
57
|
+
"request-redeem-transaction-succeeded": [TransactionReceipt];
|
|
58
|
+
"user-signed-request-redeem": [Hash];
|
|
59
|
+
"user-signing-request-redeem-error": [Error];
|
|
60
|
+
};
|