Skip to content

Commit

Permalink
Merge testnet > mainnet (#266)
Browse files Browse the repository at this point in the history
  • Loading branch information
MightOfOaks authored Oct 25, 2022
1 parent f021aee commit 5ad67be
Show file tree
Hide file tree
Showing 8 changed files with 503 additions and 120 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* @orkunkl @MightOfOaks

60 changes: 58 additions & 2 deletions components/AirdropsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { AnchorButton } from 'components/AnchorButton'
import { Tooltip } from 'components/Tooltip'
import { useWallet } from 'contexts/wallet'
import type { ComponentProps } from 'react'
import { FaCopy } from 'react-icons/fa'
import { useEffect, useState } from 'react'
import { FaCopy, FaWrench } from 'react-icons/fa'
import { getAirdropDate } from 'utils/airdrop'
import { copy } from 'utils/clipboard'
import { truncateMiddle } from 'utils/text'

import { useContracts } from '../contexts/contracts'

export interface AirdropData {
name: string
contractAddress: string
Expand All @@ -29,6 +32,51 @@ export interface AirdropsTableProps extends ComponentProps<'table'> {
export const AirdropsTable = (props: AirdropsTableProps) => {
const { data, className, ...rest } = props
const wallet = useWallet()
const merkleAirdropContract = useContracts().cw20MerkleAirdrop
const [isOwner, setIsOwner] = useState<boolean[]>([])
const [isPaused, setIsPaused] = useState<boolean[]>([])
const getAirdropOwner = async (contractAddress: string) => {
const airdropConfig = await merkleAirdropContract?.use(contractAddress)?.getConfig()
return airdropConfig?.owner
}
const getAirdropPauseStatus = async (contractAddress: string) => {
const airdropPauseStatus = await merkleAirdropContract?.use(contractAddress)?.isPaused(1)
return airdropPauseStatus
}

useEffect(() => {
const tempArray: boolean[] = []
setIsOwner([])
data.map(async (airdrop, index) => {
await getAirdropOwner(airdrop.contractAddress)
.then((owner) => {
tempArray[index] = owner === wallet.address
})
.catch(() => {
tempArray[index] = false
})
})
setTimeout(() => {
setIsOwner(tempArray)
}, 500)
}, [data, wallet.address])

useEffect(() => {
const tempArray: boolean[] = []
setIsPaused([])
data.map(async (airdrop, index) => {
await getAirdropPauseStatus(airdrop.contractAddress)
.then((pauseStatus) => {
tempArray[index] = pauseStatus || false
})
.catch(() => {
tempArray[index] = false
})
})
setTimeout(() => {
setIsPaused(tempArray)
}, 500)
}, [data, wallet.address])

return (
<table className={clsx('min-w-full', className)} {...rest}>
Expand Down Expand Up @@ -76,7 +124,9 @@ export const AirdropsTable = (props: AirdropsTableProps) => {
<td className="p-4 text-right">{airdrop.claimed.toLocaleString('en')}</td>
<td className="p-4 text-right">{airdrop.allocation ? airdrop.allocation.toLocaleString('en') : '-'}</td>
<td className="p-4">{getAirdropDate(airdrop.start, airdrop.startType)}</td>
<td className="p-4">{getAirdropDate(airdrop.expiration, airdrop.expirationType)}</td>
<td className="p-4">
{isPaused[i] ? 'Paused' : getAirdropDate(airdrop.expiration, airdrop.expirationType)}
</td>
<td className="p-4">
<div className="flex">
<AnchorButton
Expand All @@ -88,6 +138,12 @@ export const AirdropsTable = (props: AirdropsTableProps) => {
>
CLAIM
</AnchorButton>
<AnchorButton
className={clsx('ml-2 border-none', { invisible: !isOwner[i] })}
href={`/airdrops/manage/?contractAddress=${airdrop.contractAddress}`}
leftIcon={<FaWrench className="ml-2" />}
variant="outline"
/>
</div>
</td>
</tr>
Expand Down
5 changes: 0 additions & 5 deletions config/keplr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ export const keplrConfig = (config: AppConfig): ChainInfo => ({
coinMinimalDenom: config.feeToken,
coinDecimals: config.coinMap[config.feeToken].fractionalDigits,
},
{
coinDenom: config.coinMap[config.stakingToken].denom,
coinMinimalDenom: config.stakingToken,
coinDecimals: config.coinMap[config.stakingToken].fractionalDigits,
},
],
feeCurrencies: [
{
Expand Down
2 changes: 1 addition & 1 deletion config/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const mainnetConfig: AppConfig = {
}

export const uniTestnetConfig: AppConfig = {
chainId: 'uni-3',
chainId: 'uni-5',
chainName: 'Uni',
addressPrefix: 'juno',
rpcUrl: 'https://rpc.uni.juno.deuslabs.fi',
Expand Down
81 changes: 81 additions & 0 deletions contracts/cw20/merkleAirdrop/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface CW20MerkleAirdropInstance {
getLatestStage: () => Promise<number>
isClaimed: (address: string, stage: number) => Promise<boolean>
totalClaimed: (stage: number) => Promise<string>
isPaused: (stage: number) => Promise<boolean>

// Execute
updateConfig: (txSigner: string, newOwner: string) => Promise<string>
Expand All @@ -67,6 +68,8 @@ export interface CW20MerkleAirdropInstance {
claim: (stage: number, amount: string, proof: string[], signedMessage?: SignedMessage) => Promise<string>
burn: (stage: number) => Promise<string>
withdraw: (stage: number, address: string) => Promise<string>
pause: (stage: number) => Promise<string>
resume: (stage: number, newExpiration?: Expiration) => Promise<string>
registerAndReleaseEscrow: (
merkleRoot: string,
start: Expiration,
Expand Down Expand Up @@ -101,6 +104,8 @@ export interface CW20MerkleAirdropMessages {
fundWithSend: (recipient: string, amount: string) => FundWithSendMessage
burn: (airdropAddress: string, stage: number) => BurnMessage
withdraw: (airdropAddress: string, stage: number, address: string) => WithdrawMessage
pause: (airdropAddress: string, stage: number) => PauseMessage
resume: (airdropAddress: string, stage: number, new_expiration?: Expiration) => ResumeMessage
}

export interface InstantiateMessage {
Expand Down Expand Up @@ -193,6 +198,28 @@ export interface WithdrawMessage {
funds: Coin[]
}

export interface PauseMessage {
sender: string
contract: string
msg: {
pause: {
stage: number
}
}
funds: Coin[]
}
export interface ResumeMessage {
sender: string
contract: string
msg: {
resume: {
stage: number
new_expiration?: Expiration
}
}
funds: Coin[]
}

export interface CW20MerkleAirdropContract {
instantiate: (
senderAddress: string,
Expand Down Expand Up @@ -237,6 +264,13 @@ export const CW20MerkleAirdrop = (client: SigningCosmWasmClient, txSigner: strin
return data.is_claimed
}

const isPaused = async (stage: number): Promise<boolean> => {
const data = await client.queryContractSmart(contractAddress, {
is_paused: { stage },
})
return data.is_paused
}

const totalClaimed = async (stage: number): Promise<string> => {
const data = await client.queryContractSmart(contractAddress, {
total_claimed: { stage },
Expand Down Expand Up @@ -302,6 +336,21 @@ export const CW20MerkleAirdrop = (client: SigningCosmWasmClient, txSigner: strin
return result.transactionHash
}

const pause = async (stage: number): Promise<string> => {
const result = await client.execute(txSigner, contractAddress, { pause: { stage } }, fee)
return result.transactionHash
}

const resume = async (stage: number, newExpiration?: Expiration): Promise<string> => {
const result = await client.execute(
txSigner,
contractAddress,
{ resume: { stage, new_expiration: newExpiration } },
fee,
)
return result.transactionHash
}

const registerAndReleaseEscrow = async (
merkleRoot: string,
start: Expiration,
Expand Down Expand Up @@ -444,12 +493,15 @@ export const CW20MerkleAirdrop = (client: SigningCosmWasmClient, txSigner: strin
getMerkleRoot,
getLatestStage,
isClaimed,
isPaused,
totalClaimed,
updateConfig,
registerMerkleRoot,
claim,
burn,
withdraw,
pause,
resume,
registerAndReleaseEscrow,
depositEscrow,
fundWithSend,
Expand Down Expand Up @@ -593,6 +645,33 @@ export const CW20MerkleAirdrop = (client: SigningCosmWasmClient, txSigner: strin
}
}

const pause = (airdropAddress: string, stage: number): PauseMessage => {
return {
sender: txSigner,
contract: airdropAddress,
msg: {
pause: {
stage,
},
},
funds: [],
}
}

const resume = (airdropAddress: string, stage: number, newExpiration?: Expiration): ResumeMessage => {
return {
sender: txSigner,
contract: airdropAddress,
msg: {
resume: {
stage,
new_expiration: newExpiration,
},
},
funds: [],
}
}

return {
instantiate,
registerAndReleaseEscrow,
Expand All @@ -601,6 +680,8 @@ export const CW20MerkleAirdrop = (client: SigningCosmWasmClient, txSigner: strin
fundWithSend,
burn,
withdraw,
pause,
resume,
}
}

Expand Down
57 changes: 9 additions & 48 deletions pages/airdrops/[address]/claim.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CreateTxOptions, Msg, SignDoc } from '@terra-money/terra.js'
import { LCDClient, MsgSend } from '@terra-money/terra.js'
import { prepareSignBytes } from '@terra-money/terra.js/dist/util/json'
import type { SignBytesResult, SignResult } from '@terra-money/wallet-provider'
import type { SignResult } from '@terra-money/wallet-provider'
import {
ConnectType,
useConnectedWallet,
Expand Down Expand Up @@ -94,36 +94,10 @@ const ClaimAirdropPage: NextPage = () => {
}, [wallets[0]?.terraAddress])

useEffect(() => {
if (contractAddress === 'juno143rmxg4khjkxzk56pd3tru6wapenwls20y3shahlc5p9zgddyk8q27n0k4')
setSignedMessage({ claim_msg: { addr: wallet.address }, signature })
else setSignedMessage({ claim_msg: claimMsg, signature })
setSignedMessage({ claim_msg: claimMsg, signature })
}, [signature, claimMsg])

// TODO: Think about moving this to a service
const legacySignTerraClaimSignature = async (): Promise<string> => {
return new Promise((resolve, reject) => {
if (!connectedWallet) {
toast.error('Terra Station Wallet not connected!')
return
}

const junoAddressMsgByteArray = Buffer.from(JSON.stringify({ addr: wallet.address }))

connectedWallet
.signBytes(junoAddressMsgByteArray)
.then((nextSignBytesResult: SignBytesResult) => {
const signedJunoAddress = Buffer.from(nextSignBytesResult.result.signature).toString('base64')
const publickey = nextSignBytesResult.result.public_key?.toAmino().value
const sig = Buffer.from(JSON.stringify({ pub_key: publickey, signature: signedJunoAddress })).toString(
'base64',
)
setSignature(sig)
resolve(sig)
})
.catch(reject)
})
}

const signTerraClaimSignature = async (): Promise<Record<string, string>> => {
return new Promise((resolve, reject) => {
if (!connectedWallet) {
Expand Down Expand Up @@ -201,9 +175,7 @@ const ClaimAirdropPage: NextPage = () => {
setIsTerraAirdrop(airdrop.isTerraAirdrop)

if (airdrop.isTerraAirdrop) {
if (contractAddress === 'juno143rmxg4khjkxzk56pd3tru6wapenwls20y3shahlc5p9zgddyk8q27n0k4')
setSignedMessage({ claim_msg: { addr: wallet.address }, signature })
else setSignedMessage({ claim_msg: '', signature })
setSignedMessage({ claim_msg: '', signature })
}

if (isClaimed) setAirdropState('claimed')
Expand Down Expand Up @@ -269,21 +241,12 @@ const ClaimAirdropPage: NextPage = () => {

let signedMsg
if (isTerraAirdrop) {
if (contractAddress === 'juno143rmxg4khjkxzk56pd3tru6wapenwls20y3shahlc5p9zgddyk8q27n0k4') {
const data = await legacySignTerraClaimSignature()
signedMsg = {
claim_msg: { addr: wallet.address },
signature: data,
}
setSignedMessage(signedMessage)
} else {
const data = (await signTerraClaimSignature()) as { sig: string; claimMsg: string }
signedMsg = {
claim_msg: data.claimMsg,
signature: data.sig,
}
setSignedMessage(signedMessage)
const data = (await signTerraClaimSignature()) as { sig: string; claimMsg: string }
signedMsg = {
claim_msg: data.claimMsg,
signature: data.sig,
}
setSignedMessage(signedMessage)
}

await contractMessages?.claim(stage, amount, proofs, signedMsg)
Expand Down Expand Up @@ -384,9 +347,7 @@ const ClaimAirdropPage: NextPage = () => {
<JsonPreview content={transactionMessage} copyable isVisible={false} title="Show Transaction Message" />
</Conditional>

<Conditional
test={isTerraAirdrop && contractAddress !== 'juno143rmxg4khjkxzk56pd3tru6wapenwls20y3shahlc5p9zgddyk8q27n0k4'}
>
<Conditional test={isTerraAirdrop}>
<Alert type="warning">
A send message of 0,000001 LUNA will be signed for making sure the terra wallet signature is valid on the
airdrop contract.
Expand Down
Loading

0 comments on commit 5ad67be

Please sign in to comment.