@wireio/stake 0.2.3 → 0.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wireio/stake",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "LIQ Staking Module for Wire Network",
5
5
  "homepage": "https://gitea.gitgo.app/Wire/sdk-stake",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -0,0 +1,82 @@
1
+ {
2
+ "_format": "hh-sol-artifact-1",
3
+ "contractName": "Aggregator",
4
+ "sourceName": "contracts/outpost/Aggregator.sol",
5
+ "abi": [
6
+ {
7
+ "inputs": [
8
+ { "internalType": "address", "name": "_authority", "type": "address" },
9
+ { "internalType": "int256", "name": "initialPrice", "type": "int256" },
10
+ { "internalType": "uint8", "name": "__decimals", "type": "uint8" }
11
+ ],
12
+ "name": "initialize",
13
+ "outputs": [],
14
+ "stateMutability": "nonpayable",
15
+ "type": "function"
16
+ },
17
+ {
18
+ "inputs": [],
19
+ "name": "decimals",
20
+ "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
21
+ "stateMutability": "view",
22
+ "type": "function"
23
+ },
24
+ {
25
+ "inputs": [],
26
+ "name": "description",
27
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
28
+ "stateMutability": "pure",
29
+ "type": "function"
30
+ },
31
+ {
32
+ "inputs": [],
33
+ "name": "version",
34
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
35
+ "stateMutability": "pure",
36
+ "type": "function"
37
+ },
38
+ {
39
+ "inputs": [{ "internalType": "uint80", "name": "", "type": "uint80" }],
40
+ "name": "getRoundData",
41
+ "outputs": [
42
+ { "internalType": "uint80", "name": "roundId", "type": "uint80" },
43
+ { "internalType": "int256", "name": "answer", "type": "int256" },
44
+ { "internalType": "uint256", "name": "startedAt", "type": "uint256" },
45
+ { "internalType": "uint256", "name": "updatedAt", "type": "uint256" },
46
+ { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }
47
+ ],
48
+ "stateMutability": "view",
49
+ "type": "function"
50
+ },
51
+ {
52
+ "inputs": [],
53
+ "name": "latestRoundData",
54
+ "outputs": [
55
+ { "internalType": "uint80", "name": "roundId", "type": "uint80" },
56
+ { "internalType": "int256", "name": "answer", "type": "int256" },
57
+ { "internalType": "uint256", "name": "startedAt", "type": "uint256" },
58
+ { "internalType": "uint256", "name": "updatedAt", "type": "uint256" },
59
+ { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }
60
+ ],
61
+ "stateMutability": "view",
62
+ "type": "function"
63
+ },
64
+ {
65
+ "inputs": [{ "internalType": "int256", "name": "newPrice", "type": "int256" }],
66
+ "name": "updateAnswer",
67
+ "outputs": [],
68
+ "stateMutability": "nonpayable",
69
+ "type": "function"
70
+ },
71
+ {
72
+ "inputs": [
73
+ { "internalType": "int256", "name": "newPrice", "type": "int256" },
74
+ { "internalType": "uint256", "name": "oldTimestamp", "type": "uint256" }
75
+ ],
76
+ "name": "updateStale",
77
+ "outputs": [],
78
+ "stateMutability": "nonpayable",
79
+ "type": "function"
80
+ }
81
+ ]
82
+ }
@@ -1,6 +1,7 @@
1
- import { BigNumber, ethers } from "ethers";
2
- import { DepositEvent, DepositResult } from "../types";
1
+ import { BigNumber, ethers, Signer } from "ethers";
2
+ import { DepositEvent, DepositResult, WithdrawRequestedEvent, WithdrawResult } from "../types";
3
3
  import { EthereumContractService } from "../contract";
4
+ import { ChainID } from "@wireio/core";
4
5
 
5
6
  export class DepositClient {
6
7
 
@@ -79,4 +80,77 @@ export class DepositClient {
79
80
  }
80
81
 
81
82
 
83
+
84
+
85
+ async requestWithdraw(amountWei: BigNumber, signer: Signer, chainId: ChainID): Promise<WithdrawResult> {
86
+ // deadline is a period of time in the future that the signature is valid for
87
+ const deadline = Math.floor(Date.now() / 1000) + 3600;
88
+
89
+ const liqEth = this.contract.LiqEth;
90
+ const owner = await signer.getAddress();
91
+
92
+ const nonce: BigNumber = await liqEth.nonces(owner);
93
+ const domain = {
94
+ name: await liqEth.name(),
95
+ version: '1',
96
+ chainId,
97
+ verifyingContract: this.contract.LiqEth.address,
98
+ } as any;
99
+
100
+ const types = {
101
+ Permit: [
102
+ { name: 'owner', type: 'address' },
103
+ { name: 'spender', type: 'address' },
104
+ { name: 'value', type: 'uint256' },
105
+ { name: 'nonce', type: 'uint256' },
106
+ { name: 'deadline', type: 'uint256' },
107
+ ],
108
+ } as any;
109
+
110
+ const values = {
111
+ owner,
112
+ spender: this.contractService.getAddress('DepositManager'),
113
+ value: amountWei,
114
+ nonce: nonce,
115
+ deadline,
116
+ } as any;
117
+
118
+ const signature = await (signer as any)._signTypedData(domain, types, values);
119
+ const split = ethers.utils.splitSignature(signature);
120
+
121
+ const tx = await this.contract.DepositManager.requestWithdrawal(
122
+ amountWei,
123
+ deadline,
124
+ split.v,
125
+ split.r,
126
+ split.s
127
+ );
128
+
129
+ // wait for 1 confirmation
130
+ const receipt = await tx.wait(1);
131
+
132
+ // if WithdrawRequested event exists, parse it and get arguments
133
+ let withdrawRequested: WithdrawRequestedEvent | undefined;
134
+ const ev = receipt.events?.find((e) => e.event === 'WithdrawRequested');
135
+
136
+ if (ev && ev.args) {
137
+ const { user, ethAmount, nftId, readyAt } = ev.args;
138
+ withdrawRequested = {
139
+ user,
140
+ ethAmount: BigNumber.from(ethAmount),
141
+ nftId: BigNumber.from(nftId),
142
+ readyAt: readyAt,
143
+ };
144
+ }
145
+
146
+ return {
147
+ txHash: tx.hash,
148
+ receipt,
149
+ withdrawRequested,
150
+ } as WithdrawResult;
151
+ }
152
+
153
+
154
+
155
+
82
156
  }
@@ -0,0 +1,130 @@
1
+ import { BigNumber, ethers } from "ethers";
2
+ import { EthereumContractService } from "../contract";
3
+ import { formatContractErrors, sendOPPFinalize } from "../utils";
4
+ import { PurchaseAsset } from "../../../types";
5
+
6
+
7
+
8
+ export class PretokenClient {
9
+ private readonly contractService: EthereumContractService;
10
+
11
+ get contract() { return this.contractService.contract; }
12
+
13
+ constructor(contract: EthereumContractService) {
14
+ this.contractService = contract;
15
+ }
16
+
17
+
18
+
19
+ /**
20
+ * Purchase warrants by sending ETH to the Depositor.purchaseWarrantsWithETH(buyer) payable function.
21
+ * Returns txHash, receipt and parsed PurchasedWithETH event (if present).
22
+ */
23
+ async purchaseWarrantsWithETH(amountWei: BigNumber, buyer: string): Promise<any> {
24
+ // attempt a simulation of the purchase call
25
+ try {
26
+ await this.contract.Depositor.callStatic.purchaseWarrantsWithETH(buyer, { value: amountWei });
27
+ } catch (err: any) {
28
+ let errorObj = formatContractErrors(err);
29
+ throw new Error(errorObj.name ?? errorObj.raw)
30
+ }
31
+
32
+ // attempt the real purchase call
33
+ let tx, receipt;
34
+ try {
35
+ tx = await this.contract.Depositor.purchaseWarrantsWithETH(buyer, { value: amountWei });
36
+ receipt = await tx.wait(1);
37
+ } catch (err: any) {
38
+ let errorObj = formatContractErrors(err);
39
+ throw new Error(errorObj.name ?? errorObj.raw)
40
+ }
41
+
42
+
43
+ // Attempt to parse PurchasedWithETH event
44
+ let purchased: any | undefined;
45
+ const ev = receipt.events?.find((e) => e.event === 'PurchasedWithETH' || e.event === 'PurchasedWithETH(address,uint256,uint256,uint256)');
46
+
47
+ if (ev && ev.args) {
48
+ // event signature: PurchasedWithETH(address indexed user, uint256 ethIn, uint256 shares, uint256 tokenId)
49
+ const { user, ethIn, shares, tokenId } = ev.args as any;
50
+
51
+ purchased = {
52
+ buyer: user,
53
+ amount: BigNumber.from(ethIn),
54
+ shares: BigNumber.from(shares),
55
+ tokenId: BigNumber.from(tokenId),
56
+ };
57
+ }
58
+
59
+ return {
60
+ txHash: tx.hash,
61
+ receipt,
62
+ purchased,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Purchase warrants by transferring liqETH into the pool (ERC-20) and calling Depositor.purchaseWarrantsWithLiqETH(amountLiq, buyer).
68
+ * Returns txHash, receipt and parsed PurchasedWithLiqETH event (if present).
69
+ */
70
+ async purchaseWarrantsWithLiqETH(amountLiq: BigNumber, buyer: string): Promise<any> {
71
+ // attempt a simulation of the purchase call
72
+ try {
73
+ await this.contract.Depositor.callStatic.purchaseWarrantsWithLiqETH(amountLiq, buyer);
74
+ } catch (err: any) {
75
+ let errorObj = formatContractErrors(err);
76
+
77
+ // ! TEMP for testing
78
+ if(errorObj.name == "OPP_PreviousEpochUnsent") {
79
+ console.error("OPP Previous Epoch is unsent - trigger OPP finalizeEpoch()")
80
+ await sendOPPFinalize(this.contract.OPP);
81
+ throw new Error(errorObj.name+` - triggering OPP finalizeEpoch() please try again`);
82
+ }
83
+
84
+ throw new Error(errorObj.name ?? errorObj.raw)
85
+ }
86
+
87
+ // attempt the real purchase call
88
+ let tx, receipt;
89
+ try {
90
+ tx = await this.contract.Depositor.purchaseWarrantsWithLiqETH(amountLiq, buyer);
91
+ receipt = await tx.wait(1);
92
+ } catch (err: any) {
93
+ let errorObj = formatContractErrors(err);
94
+ throw new Error(errorObj.name ?? errorObj.raw)
95
+ }
96
+
97
+
98
+ // Attempt to parse PurchasedWithLiqETH event
99
+ let purchased: any | undefined;
100
+ const ev = receipt.events?.find((e) => e.event === 'PurchasedWithLiqETH' || (e.event && e.event.toString().startsWith('PurchasedWithLiqETH')));
101
+
102
+ if (ev && ev.args) {
103
+ // Event shape: PurchasedWithLiqETH(buyer, amountLiq, ethEqWei, shares, tokenId)
104
+ const args: any = ev.args;
105
+ // try named fields first, fallback to positional
106
+ const evtBuyer = args.buyer ?? args[0];
107
+ const evtAmountLiq = (args.amountLiq ?? args[1]) ? BigNumber.from(args.amountLiq ?? args[1]) : undefined;
108
+ const evtEthEqWei = (args.ethEqWei ?? args[2]) ? BigNumber.from(args.ethEqWei ?? args[2]) : undefined;
109
+ const evtShares = (args.shares ?? args[3]) ? BigNumber.from(args.shares ?? args[3]) : undefined;
110
+ const evtTokenId = (args.tokenId ?? args[4]) ? BigNumber.from(args.tokenId ?? args[4]) : undefined;
111
+
112
+ purchased = {
113
+ buyer: evtBuyer,
114
+ amountLiq: evtAmountLiq,
115
+ ethEqWei: evtEthEqWei,
116
+ shares: evtShares,
117
+ tokenId: evtTokenId,
118
+ };
119
+ }
120
+
121
+ return {
122
+ txHash: tx.hash,
123
+ receipt,
124
+ purchased,
125
+ };
126
+ }
127
+
128
+
129
+
130
+ }
@@ -1,6 +1,7 @@
1
1
  import { BigNumber, ethers } from "ethers";
2
- import { preLaunchReceipt, StakedEvent, StakedResult, WithdrawnStakeEvent, WithdrawnStakeResult } from "../types";
2
+ import { preLaunchReceipt, StakedEvent, WithdrawnStakeEvent, WithdrawnStakeResult } from "../types";
3
3
  import { EthereumContractService } from "../contract";
4
+ import { formatContractErrors } from "../utils";
4
5
 
5
6
  export class StakeClient {
6
7
 
@@ -15,19 +16,72 @@ export class StakeClient {
15
16
 
16
17
 
17
18
  /**
18
- * Simulate a stake via callStatic.
19
- *
20
- * Useful for pre-flight checks; will throw with the same revert
21
- * reason as a real tx if it would fail.
19
+ * Simulate a stake via callStatic
22
20
  */
23
- async simulateStake(amount: number | string | bigint | BigNumber): Promise<void> {
24
- const amountWei = BigNumber.isBigNumber(amount)
25
- ? amount
26
- : BigNumber.from(amount);
27
-
28
- // callStatic executes the function locally without sending a tx.
29
- // stakeLiqEth() doesn't return anything, so we only care if it reverts.
30
- await this.contract.Depositor.callStatic.stakeLiqETH(amountWei);
21
+ async simulateStake(amountWei: BigNumber): Promise<void> {
22
+ try {
23
+ await this.contract.Depositor.callStatic.stakeLiqETH(amountWei);
24
+ } catch (err: any) {
25
+ let errorObj = formatContractErrors(err);
26
+ throw new Error(errorObj.name ?? errorObj.raw)
27
+ }
28
+ }
29
+
30
+
31
+ /**
32
+ * Read OPP / Outpost state used by the Depositor to decide whether staking is allowed.
33
+ * Returns various data
34
+ */
35
+ async getOppStatus(): Promise<any> {
36
+ const depositor = this.contract.Depositor;
37
+ const opp = this.contract.OPP;
38
+
39
+
40
+ const oppAddress: string = await depositor.oppAddress();
41
+ const oppInboundAddress: string = await depositor.oppInboundAddress();
42
+ const prevEpochSent = await opp.prevEpochSent();
43
+
44
+
45
+ const inbound = this.contractService.getReadOnly('OPPInbound');
46
+
47
+ // Query useful getters
48
+ const nextEpochBN: any = await inbound.nextEpochNum();
49
+ const pendingEpochRaw: any = await inbound.pendingEpoch();
50
+ const pendingMessageCount: any = await inbound.pendingMessageCount();
51
+ const previousEpochHash: string = await inbound.previousEpochHash();
52
+ const nextEpochNum = (nextEpochBN && typeof nextEpochBN.toNumber === 'function') ? nextEpochBN.toNumber() : Number(nextEpochBN || 0);
53
+
54
+ const pendingEpoch = (pendingEpochRaw && pendingEpochRaw.epochNumber !== undefined)
55
+ ? {
56
+ epochNumber: (pendingEpochRaw.epochNumber && typeof pendingEpochRaw.epochNumber.toNumber === 'function') ? pendingEpochRaw.epochNumber.toNumber() : Number(pendingEpochRaw.epochNumber || 0),
57
+ timestamp: (pendingEpochRaw.timestamp && typeof pendingEpochRaw.timestamp.toNumber === 'function') ? pendingEpochRaw.timestamp.toNumber() : Number(pendingEpochRaw.timestamp || 0),
58
+ prevEpochHash: pendingEpochRaw.prevEpochHash,
59
+ merkleRoot: pendingEpochRaw.merkleRoot,
60
+ firstMessageID: pendingEpochRaw.firstMessageID,
61
+ lastMessageID: pendingEpochRaw.lastMessageID,
62
+ }
63
+ : null;
64
+
65
+ const pendingMessagesBN = pendingMessageCount;
66
+ const pendingMessages = (pendingMessagesBN && typeof pendingMessagesBN.toString === 'function') ? pendingMessagesBN.toString() : String(pendingMessagesBN || '0');
67
+
68
+ const hasPendingMessages = (pendingMessagesBN && typeof pendingMessagesBN.gt === 'function') ? pendingMessagesBN.gt(0) : (Number(pendingMessages) > 0);
69
+
70
+ return {
71
+ oppAddress,
72
+ prevEpochSent,
73
+ oppInboundAddress,
74
+ nextEpochNum,
75
+ pendingEpoch,
76
+ pendingMessageCount: pendingMessages,
77
+ previousEpochHash,
78
+ hasPendingMessages,
79
+ raw: {
80
+ nextEpochBN,
81
+ pendingEpochRaw,
82
+ pendingMessageCount: pendingMessagesBN,
83
+ },
84
+ };
31
85
  }
32
86
 
33
87
 
@@ -36,12 +90,17 @@ export class StakeClient {
36
90
  * @param amountWei an amount of liqETH (in WEI) to stake to the Outpost
37
91
  * @returns txHash (hash of the transaction), receipt, Staked event
38
92
  */
39
- async performStake(amountWei: BigNumber, signerAddress: string): Promise<StakedResult> {
40
- const liq = this.contract.LiqEth;
41
- const depositorAddress = this.contract.Depositor.address;
42
-
43
- // check allowance
44
- const allowance = await liq.allowance(signerAddress, depositorAddress);
93
+ async performStake(amountWei: BigNumber, signerAddress: string): Promise<any> {
94
+ const depositor = this.contract.Depositor.address;
95
+ const liqRead = this.contract.LiqEth;
96
+ const bal = await liqRead.balanceOf(signerAddress);
97
+ const allowance = await liqRead.allowance(signerAddress, depositor);
98
+
99
+ const paused = await this.contract.Depositor.paused();
100
+ if(paused) {
101
+ throw new Error("Error - Depositor is in a paused state");
102
+ }
103
+
45
104
 
46
105
  // if allowance is less than the requested stake amount, request permission to spend LiqEth
47
106
  if (allowance.lt(amountWei)) {
@@ -50,17 +109,20 @@ export class StakeClient {
50
109
  // currently requested unlimited amount - potentially change to only request up to the current amount?
51
110
  const approveAmount = ethers.constants.MaxUint256;
52
111
 
53
- console.warn(`allowance insufficient (${allowance.toString()} < ${amountWei.toString()}); sending approve(${depositorAddress}, ${approveAmount.toString()})`);
54
- const approveTx = await liqWrite.approve(depositorAddress, approveAmount);
112
+ console.warn(`allowance insufficient (${allowance.toString()} < ${amountWei.toString()}); sending approve(${depositor}, ${approveAmount.toString()})`);
113
+ const approveTx = await liqWrite.approve(depositor, approveAmount);
55
114
  await approveTx.wait(1);
56
115
 
57
116
  // re-read allowance to ensure approval succeeded
58
- const newAllowance = await liq.allowance(signerAddress, depositorAddress);
117
+ const newAllowance = await liqRead.allowance(signerAddress, depositor);
59
118
  if (newAllowance.lt(amountWei)) {
60
119
  throw new Error('Approval failed or allowance still insufficient after approve');
61
120
  }
62
121
  }
63
122
 
123
+
124
+ await this.simulateStake(amountWei);
125
+
64
126
 
65
127
  // send the tx to stake liqeth
66
128
  const tx = await this.contract.Depositor.stakeLiqETH(amountWei);
@@ -150,7 +212,6 @@ export class StakeClient {
150
212
  for (const idBN of tokenIds) {
151
213
  try {
152
214
  const receiptData = await receiptContract.getReceipt(idBN);
153
-
154
215
  const formattedReceipt = {
155
216
  account: receiptData.account,
156
217
  currency: receiptData.currency,
@@ -169,7 +230,8 @@ export class StakeClient {
169
230
  timestamp: new Date(Number(receiptData.timestamp.toString()) * 1000).toLocaleString(),
170
231
  }
171
232
 
172
- results.push({ tokenId: idBN.toBigInt(), receipt: formattedReceipt } as any);
233
+ // Only fetch staking receipts (kind 1 is a wire pretoken receipt)
234
+ if(receiptData.kind == 0) results.push({ tokenId: idBN.toBigInt(), receipt: formattedReceipt } as any);
173
235
  } catch (err) {
174
236
  // in case of any mismatch or race, just skip this id
175
237
  console.warn(`Failed to load receipt for tokenId=${idBN.toString()}`, err);
@@ -177,6 +239,7 @@ export class StakeClient {
177
239
  }
178
240
  }
179
241
 
242
+ console.log('results of prelaunch receipts', results)
180
243
  return results;
181
244
  }
182
245
 
@@ -21,6 +21,8 @@ import OPPArtifact from '../../assets/ethereum/ABI/outpost/OPP.sol/OPP.json';
21
21
  import OPPCommonArtifact from '../../assets/ethereum/ABI/outpost/OPPCommon.sol/OPPCommon.json';
22
22
  import OPPInboundArtifact from '../../assets/ethereum/ABI/outpost/OPPInbound.sol/OPPInbound.json';
23
23
  import WarrantArtifact from '../../assets/ethereum/ABI/outpost/Warrant.sol/Warrant.json';
24
+ import AggregatorArtifact from '../../assets/ethereum/ABI/outpost/Aggregator.sol/Aggregator.json';
25
+ import EthUsdPriceConsumerArtifact from '../../assets/ethereum/ABI/outpost/EthUsdPriceConsumer.sol/EthUsdPriceConsumer.json';
24
26
 
25
27
  // Currently Unused Artifacts
26
28
  // import LiqEthCommonArtifact from '../../assets/ethereum/ABI/liqEth/liqEthCommon.sol/liqEthCommon.json';
@@ -64,6 +66,9 @@ export const ADDRESSES: AddressBook = {
64
66
  OPPCommon: "0x52C1d7F02B35176F79b03F6eF7E5b74b27d1dB8c",
65
67
  OPPInbound: "0x39feC7536BaEd4E376b1B5cf2f2e8182ab203418",
66
68
  Warrant: "0x9190bBcaB5cfeb4b7b6DE5Ae21105F3114753F10",
69
+
70
+ Aggregator: "0xd89F85Ce867E523Dc3E58578B06Ee72cdaBc39ec",
71
+ EthUsdPriceConsumer: "0xd02F512eAcCA4c713CA954DfD907DC6E8cFACf8e",
67
72
  };
68
73
 
69
74
  export type Contracts<T extends string = ContractName> = Record<T, ContractConfig>;
@@ -156,6 +161,14 @@ export const CONTRACTS: Contracts<ContractName> = {
156
161
  Warrant: {
157
162
  address: ADDRESSES.Warrant,
158
163
  abi: WarrantArtifact.abi as JsonFragment[],
164
+ },
165
+ Aggregator: {
166
+ address: ADDRESSES.Aggregator,
167
+ abi: AggregatorArtifact.abi as JsonFragment[],
168
+ },
169
+ EthUsdPriceConsumer: {
170
+ address: ADDRESSES.EthUsdPriceConsumer,
171
+ abi: EthUsdPriceConsumerArtifact.abi as JsonFragment[],
159
172
  }
160
173
  };
161
174