@chorus-one/polygon 1.0.0
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/.mocharc.json +6 -0
- package/LICENSE +13 -0
- package/README.md +233 -0
- package/dist/cjs/constants.d.ts +187 -0
- package/dist/cjs/constants.js +141 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/referrer.d.ts +2 -0
- package/dist/cjs/referrer.js +15 -0
- package/dist/cjs/staker.d.ts +335 -0
- package/dist/cjs/staker.js +716 -0
- package/dist/cjs/types.d.ts +40 -0
- package/dist/cjs/types.js +2 -0
- package/dist/mjs/constants.d.ts +187 -0
- package/dist/mjs/constants.js +138 -0
- package/dist/mjs/index.d.ts +4 -0
- package/dist/mjs/index.js +2 -0
- package/dist/mjs/package.json +3 -0
- package/dist/mjs/referrer.d.ts +2 -0
- package/dist/mjs/referrer.js +11 -0
- package/dist/mjs/staker.d.ts +335 -0
- package/dist/mjs/staker.js +712 -0
- package/dist/mjs/types.d.ts +40 -0
- package/dist/mjs/types.js +1 -0
- package/hardhat.config.ts +27 -0
- package/package.json +50 -0
- package/src/constants.ts +151 -0
- package/src/index.ts +14 -0
- package/src/referrer.ts +15 -0
- package/src/staker.ts +878 -0
- package/src/types.ts +45 -0
- package/test/fixtures/expected-data.ts +17 -0
- package/test/integration/localSigner.spec.ts +128 -0
- package/test/integration/setup.ts +41 -0
- package/test/integration/staker.spec.ts +587 -0
- package/test/integration/testStaker.ts +130 -0
- package/test/integration/utils.ts +263 -0
- package/test/lib/networks.json +14 -0
- package/test/staker.spec.ts +154 -0
- package/tsconfig.cjs.json +9 -0
- package/tsconfig.json +13 -0
- package/tsconfig.mjs.json +9 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Hex, TransactionReceipt } from 'viem'
|
|
2
|
+
import type { PolygonNetworks } from './constants'
|
|
3
|
+
|
|
4
|
+
export interface PolygonNetworkConfig {
|
|
5
|
+
/** Network to use: 'mainnet' (Ethereum L1) or 'testnet' (Sepolia L1) */
|
|
6
|
+
network: PolygonNetworks
|
|
7
|
+
/** Optional RPC endpoint URL override. If not provided, uses viem's default for the network. */
|
|
8
|
+
rpcUrl?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Transaction {
|
|
12
|
+
/** The recipient (contract) address in hexadecimal format */
|
|
13
|
+
to: Hex
|
|
14
|
+
/** The data to be included in the transaction in hexadecimal format (includes referrer tracking suffix) */
|
|
15
|
+
data: Hex
|
|
16
|
+
/** The amount of ETH (in wei) to be sent with the transaction */
|
|
17
|
+
value?: bigint
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PolygonTxStatus {
|
|
21
|
+
/** Status of the transaction */
|
|
22
|
+
status: 'success' | 'failure' | 'unknown'
|
|
23
|
+
/** Transaction receipt (null if unknown) */
|
|
24
|
+
receipt: TransactionReceipt | null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface StakeInfo {
|
|
28
|
+
/** Total staked amount formatted in POL */
|
|
29
|
+
balance: string
|
|
30
|
+
/** Total shares held by the delegator */
|
|
31
|
+
shares: bigint
|
|
32
|
+
/** Current exchange rate between shares and POL (with high precision) */
|
|
33
|
+
exchangeRate: bigint
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface UnbondInfo {
|
|
37
|
+
/** Amount pending unbonding in POL */
|
|
38
|
+
amount: string
|
|
39
|
+
/** Whether the unbond can be withdrawn now */
|
|
40
|
+
isWithdrawable: boolean
|
|
41
|
+
/** Shares amount pending unbonding */
|
|
42
|
+
shares: bigint
|
|
43
|
+
/** Epoch number when the unbond becomes claimable */
|
|
44
|
+
withdrawEpoch: bigint
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Address, Hex } from 'viem'
|
|
2
|
+
import { NETWORK_CONTRACTS, CHORUS_ONE_POLYGON_VALIDATORS } from '../../src/constants'
|
|
3
|
+
|
|
4
|
+
const { stakingTokenAddress } = NETWORK_CONTRACTS.mainnet
|
|
5
|
+
|
|
6
|
+
export const TEST_ADDRESS: Address = '0x1234567890123456789012345678901234567890'
|
|
7
|
+
export const TEST_VALIDATOR_SHARE: Address = CHORUS_ONE_POLYGON_VALIDATORS.mainnet
|
|
8
|
+
|
|
9
|
+
export const EXPECTED_APPROVE_TX = {
|
|
10
|
+
amount: '100',
|
|
11
|
+
expected: {
|
|
12
|
+
to: stakingTokenAddress as Address,
|
|
13
|
+
// approve(address spender, uint256 amount) with spender = stakeManagerAddress (0x5e3e...), amount = 100e18
|
|
14
|
+
data: '0x095ea7b30000000000000000000000005e3ef299fddf15eaa0432e6e66473ace8c13d9080000000000000000000000000000000000000000000000056bc75e2d63100000' as Hex,
|
|
15
|
+
value: 0n
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { type PublicClient, parseEther } from 'viem'
|
|
2
|
+
import { hardhat } from 'viem/chains'
|
|
3
|
+
import { assert } from 'chai'
|
|
4
|
+
import { fundWithStakingToken, impersonate, getStakingTokenBalance, getWithdrawalDelay, advanceEpoch } from './utils'
|
|
5
|
+
import { restoreToInitialState } from './setup'
|
|
6
|
+
import { PolygonTestStaker } from './testStaker'
|
|
7
|
+
|
|
8
|
+
const TEST_MNEMONIC = 'test test test test test test test test test test test junk'
|
|
9
|
+
const AMOUNT = '100'
|
|
10
|
+
|
|
11
|
+
describe('PolygonStaker with LocalSigner', () => {
|
|
12
|
+
let testStaker: PolygonTestStaker
|
|
13
|
+
let publicClient: PublicClient
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await restoreToInitialState()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
testStaker = new PolygonTestStaker({
|
|
21
|
+
mnemonic: TEST_MNEMONIC,
|
|
22
|
+
rpcUrl: hardhat.rpcUrls.default.http[0]
|
|
23
|
+
})
|
|
24
|
+
await testStaker.init()
|
|
25
|
+
|
|
26
|
+
publicClient = testStaker.publicClient
|
|
27
|
+
|
|
28
|
+
await fundWithStakingToken({
|
|
29
|
+
publicClient,
|
|
30
|
+
recipientAddress: testStaker.delegatorAddress,
|
|
31
|
+
amount: parseEther('10000')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
await impersonate({ publicClient, address: testStaker.delegatorAddress })
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('approves and stakes using LocalSigner', async () => {
|
|
38
|
+
await testStaker.approve(AMOUNT)
|
|
39
|
+
|
|
40
|
+
const allowance = await testStaker.staker.getAllowance(testStaker.delegatorAddress)
|
|
41
|
+
assert.equal(allowance, AMOUNT)
|
|
42
|
+
|
|
43
|
+
await testStaker.stake(AMOUNT, 0n)
|
|
44
|
+
|
|
45
|
+
const stakeInfo = await testStaker.staker.getStake({
|
|
46
|
+
delegatorAddress: testStaker.delegatorAddress,
|
|
47
|
+
validatorShareAddress: testStaker.validatorShareAddress
|
|
48
|
+
})
|
|
49
|
+
assert.equal(stakeInfo.balance, AMOUNT)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('unstakes using LocalSigner', async () => {
|
|
53
|
+
await testStaker.approve(AMOUNT)
|
|
54
|
+
await testStaker.stake(AMOUNT, 0n)
|
|
55
|
+
|
|
56
|
+
const nonceBefore = await testStaker.staker.getUnbondNonce({
|
|
57
|
+
delegatorAddress: testStaker.delegatorAddress,
|
|
58
|
+
validatorShareAddress: testStaker.validatorShareAddress
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const stakeBefore = await testStaker.staker.getStake({
|
|
62
|
+
delegatorAddress: testStaker.delegatorAddress,
|
|
63
|
+
validatorShareAddress: testStaker.validatorShareAddress
|
|
64
|
+
})
|
|
65
|
+
await testStaker.unstake(AMOUNT, stakeBefore.shares)
|
|
66
|
+
|
|
67
|
+
const nonceAfter = await testStaker.staker.getUnbondNonce({
|
|
68
|
+
delegatorAddress: testStaker.delegatorAddress,
|
|
69
|
+
validatorShareAddress: testStaker.validatorShareAddress
|
|
70
|
+
})
|
|
71
|
+
assert.equal(nonceAfter, nonceBefore + 1n)
|
|
72
|
+
|
|
73
|
+
const stakeAfter = await testStaker.staker.getStake({
|
|
74
|
+
delegatorAddress: testStaker.delegatorAddress,
|
|
75
|
+
validatorShareAddress: testStaker.validatorShareAddress
|
|
76
|
+
})
|
|
77
|
+
assert.equal(stakeAfter.balance, '0')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('withdraws using LocalSigner after unbonding period', async () => {
|
|
81
|
+
await testStaker.approve(AMOUNT)
|
|
82
|
+
await testStaker.stake(AMOUNT, 0n)
|
|
83
|
+
|
|
84
|
+
const stakeBefore = await testStaker.staker.getStake({
|
|
85
|
+
delegatorAddress: testStaker.delegatorAddress,
|
|
86
|
+
validatorShareAddress: testStaker.validatorShareAddress
|
|
87
|
+
})
|
|
88
|
+
await testStaker.unstake(AMOUNT, stakeBefore.shares)
|
|
89
|
+
|
|
90
|
+
const nonce = await testStaker.staker.getUnbondNonce({
|
|
91
|
+
delegatorAddress: testStaker.delegatorAddress,
|
|
92
|
+
validatorShareAddress: testStaker.validatorShareAddress
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const unbond = await testStaker.staker.getUnbond({
|
|
96
|
+
delegatorAddress: testStaker.delegatorAddress,
|
|
97
|
+
validatorShareAddress: testStaker.validatorShareAddress,
|
|
98
|
+
unbondNonce: nonce
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const withdrawalDelay = await getWithdrawalDelay({ publicClient })
|
|
102
|
+
await advanceEpoch({
|
|
103
|
+
publicClient,
|
|
104
|
+
staker: testStaker.staker,
|
|
105
|
+
targetEpoch: unbond.withdrawEpoch + withdrawalDelay
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const balanceBefore = await getStakingTokenBalance({
|
|
109
|
+
publicClient,
|
|
110
|
+
address: testStaker.delegatorAddress
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
await testStaker.withdraw(nonce)
|
|
114
|
+
|
|
115
|
+
const balanceAfter = await getStakingTokenBalance({
|
|
116
|
+
publicClient,
|
|
117
|
+
address: testStaker.delegatorAddress
|
|
118
|
+
})
|
|
119
|
+
assert.equal(balanceAfter - balanceBefore, parseEther(AMOUNT))
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('reports transaction status correctly', async () => {
|
|
123
|
+
const txHash = await testStaker.approve(AMOUNT)
|
|
124
|
+
|
|
125
|
+
const { status } = await testStaker.staker.getTxStatus({ txHash: txHash as `0x${string}` })
|
|
126
|
+
assert.equal(status, 'success')
|
|
127
|
+
})
|
|
128
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createPublicClient, http } from 'viem'
|
|
2
|
+
import { hardhat } from 'viem/chains'
|
|
3
|
+
|
|
4
|
+
let currentSnapshotId: string | null = null
|
|
5
|
+
|
|
6
|
+
const publicClient = createPublicClient({
|
|
7
|
+
chain: hardhat,
|
|
8
|
+
transport: http(hardhat.rpcUrls.default.http[0])
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
before(async () => {
|
|
12
|
+
currentSnapshotId = (await publicClient.request({
|
|
13
|
+
method: 'evm_snapshot',
|
|
14
|
+
params: []
|
|
15
|
+
} as any)) as string
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const restoreToInitialState = async () => {
|
|
19
|
+
if (!currentSnapshotId) {
|
|
20
|
+
throw new Error('No snapshot available to restore to')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const success = await publicClient.request({
|
|
24
|
+
method: 'evm_revert',
|
|
25
|
+
params: [currentSnapshotId]
|
|
26
|
+
} as any)
|
|
27
|
+
|
|
28
|
+
if (!success) {
|
|
29
|
+
throw new Error('Failed to restore snapshot')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const newSnapshotId = (await publicClient.request({
|
|
33
|
+
method: 'evm_snapshot',
|
|
34
|
+
params: []
|
|
35
|
+
} as any)) as string
|
|
36
|
+
|
|
37
|
+
if (!newSnapshotId) {
|
|
38
|
+
throw new Error('Failed to take new snapshot after restore')
|
|
39
|
+
}
|
|
40
|
+
currentSnapshotId = newSnapshotId
|
|
41
|
+
}
|