diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index eea316ee..59d7240a 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -1,14 +1,21 @@ name: Frontend Tests on: pull_request: - paths: [frontend/**] + paths: [frontend/**, token-dispenser/**] push: branches: [main] - paths: [frontend/**] + paths: [frontend/**, token-dispenser/**] jobs: test: runs-on: ubuntu-latest + container: + image: jetprotocol/builder:rust-1.68.0-node-18.15.0-solana-1.14.17-anchor-0.27.0-1 + env: + ENDPOINT: http://localhost:8899 + + env: + PROGRAM_ID: Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS defaults: run: diff --git a/backend/package.json b/backend/package.json index f6ac92ae..26f84d63 100644 --- a/backend/package.json +++ b/backend/package.json @@ -16,6 +16,7 @@ "@aws-sdk/client-secrets-manager": "^3.535.0", "@coral-xyz/anchor": "^0.29.0", "@solana/web3.js": "^1.91.1", + "bs58": "^5.0.0", "tweetnacl": "^1.0.3" }, "devDependencies": { diff --git a/backend/src/handlers/discord-signed-digest.ts b/backend/src/handlers/discord-signed-digest.ts index 7d6d89d0..596ce561 100644 --- a/backend/src/handlers/discord-signed-digest.ts +++ b/backend/src/handlers/discord-signed-digest.ts @@ -3,11 +3,14 @@ import { getDispenserKey } from '../utils/secrets' import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda' import { getDiscordUser, signDiscordDigest } from '../utils/discord' import { HandlerError } from '../utils/errors' +import { asJsonResponse } from '../utils/response' export interface DiscordSignedDigestParams { publicKey: string } +let guardKeyPair: Keypair + export const signDiscordMessage = async ( event: APIGatewayProxyEvent ): Promise => { @@ -15,7 +18,10 @@ export const signDiscordMessage = async ( const publicKey = (event.queryStringParameters ?? {})['publicKey'] validatePublicKey(publicKey) - const accessToken = event.headers['x-auth-token'] + const accessToken = + event.headers['Authorization'] ?? + event.headers['authorization'] ?? + event.headers['x-auth-token'] const discordId = await getDiscordId(accessToken) const claimant = new PublicKey(publicKey!) @@ -23,31 +29,26 @@ export const signDiscordMessage = async ( const signedDigest = signDiscordDigest(discordId, claimant, dispenserGuard) - return { - statusCode: 200, - body: JSON.stringify({ - signature: Buffer.from(signedDigest.signature).toString('hex'), - publicKey: Buffer.from(signedDigest.publicKey).toString('hex'), // The dispenser guard's public key - fullMessage: Buffer.from(signedDigest.fullMessage).toString('hex') - }) - } + return asJsonResponse(200, { + signature: Buffer.from(signedDigest.signature).toString('hex'), + publicKey: Buffer.from(signedDigest.publicKey).toString('hex'), // The dispenser guard's public key + fullMessage: Buffer.from(signedDigest.fullMessage).toString('hex') + }) } catch (err: HandlerError | unknown) { console.error('Error generating signed discord digest', err) if (err instanceof HandlerError) { - return { - statusCode: err.statusCode, - body: JSON.stringify(err.body) - } + return asJsonResponse(err.statusCode, err.body) } - return { - statusCode: 500, - body: JSON.stringify({ error: 'Internal server error' }) - } + return asJsonResponse(500, { error: 'Internal server error' }) } } async function loadDispenserGuard() { + if (guardKeyPair) { + return guardKeyPair + } + const secretData = await getDispenserKey() const dispenserGuardKey = secretData.key @@ -55,7 +56,9 @@ async function loadDispenserGuard() { Uint8Array.from(dispenserGuardKey) ) - return dispenserGuard + guardKeyPair = dispenserGuard + console.log('Loaded dispenser guard key') + return guardKeyPair } function validatePublicKey(publicKey?: string) { @@ -80,13 +83,18 @@ function validatePublicKey(publicKey?: string) { } } -async function getDiscordId(accessToken?: string) { - if (!accessToken) { - throw new HandlerError(400, { error: 'Must provide discord auth token' }) +async function getDiscordId(tokenHeaderValue?: string) { + if (!tokenHeaderValue) { + throw new HandlerError(403, { error: 'Must provide discord auth token' }) + } + + const tokenParts = tokenHeaderValue.split(' ') + if (tokenParts.length !== 2 || tokenParts[0] !== 'Bearer') { + throw new HandlerError(403, { error: 'Invalid authorization header' }) } try { - const user = await getDiscordUser(accessToken) + const user = await getDiscordUser(tokenParts[1]) return user.id } catch (err) { throw new HandlerError(403, { error: 'Invalid discord access token' }) diff --git a/backend/src/handlers/fund-transactions.ts b/backend/src/handlers/fund-transactions.ts index c2ddbd16..fcfe5ecd 100644 --- a/backend/src/handlers/fund-transactions.ts +++ b/backend/src/handlers/fund-transactions.ts @@ -5,11 +5,15 @@ import { checkTransactions, deserializeTransactions } from '../utils/fund-transactions' -import { Keypair } from '@solana/web3.js' +import { Keypair, VersionedTransaction } from '@solana/web3.js' +import bs58 from 'bs58' import { HandlerError } from '../utils/errors' +import { asJsonResponse } from '../utils/response' export type FundTransactionRequest = Uint8Array[] +let funderWallet: NodeWallet + export const fundTransactions = async ( event: APIGatewayProxyEvent ): Promise => { @@ -20,35 +24,25 @@ export const fundTransactions = async ( const isTransactionsValid = await checkTransactions(transactions) if (!isTransactionsValid) { - return { - statusCode: 403, - body: JSON.stringify({ error: 'Unauthorized transactions' }) - } + return asJsonResponse(403, { error: 'Unauthorized transactions' }) } const wallet = await loadFunderWallet() const signedTransactions = await wallet.signAllTransactions(transactions) - return { - statusCode: 200, - body: JSON.stringify( - signedTransactions.map((tx) => Buffer.from(tx.serialize())) - ) - } + logSignatures(signedTransactions) + return asJsonResponse( + 200, + signedTransactions.map((tx) => Buffer.from(tx.serialize())) + ) } catch (err: HandlerError | unknown) { console.error('Error signing transactions', err) if (err instanceof HandlerError) { - return { - statusCode: err.statusCode, - body: JSON.stringify(err.body) - } + return asJsonResponse(err.statusCode, err.body) } - return { - statusCode: 500, - body: JSON.stringify({ error: 'Internal server error' }) - } + return asJsonResponse(500, { error: 'Internal server error' }) } } @@ -63,10 +57,32 @@ function validateFundTransactions(transactions: unknown) { } async function loadFunderWallet(): Promise { + if (funderWallet) { + return funderWallet + } + const secretData = await getFundingKey() const funderWalletKey = secretData.key - const keypair = Keypair.fromSecretKey(new Uint8Array(funderWalletKey)) + const keypair = Keypair.fromSecretKey(Uint8Array.from(funderWalletKey)) + + funderWallet = new NodeWallet(keypair) + console.log('Loaded funder wallet') + return funderWallet +} + +function getSignature(tx: VersionedTransaction): string { + if (tx.signatures.length > 0) { + return bs58.encode(tx.signatures[0]) + } + + return 'unkown signature' +} - return new NodeWallet(keypair) +function logSignatures(signedTransactions: VersionedTransaction[]) { + const sigs: string[] = [] + signedTransactions.forEach((tx) => { + sigs.push(getSignature(tx)) + }) + console.log(`Signed transactions: ${sigs}`) } diff --git a/backend/src/utils/response.ts b/backend/src/utils/response.ts new file mode 100644 index 00000000..3935179f --- /dev/null +++ b/backend/src/utils/response.ts @@ -0,0 +1,12 @@ +import { APIGatewayProxyResult } from 'aws-lambda' + +export const asJsonResponse = ( + statusCode: number, + body: unknown +): APIGatewayProxyResult => { + return { + statusCode, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + } +} diff --git a/backend/src/utils/secrets.ts b/backend/src/utils/secrets.ts index 7a925d85..c384ea3f 100644 --- a/backend/src/utils/secrets.ts +++ b/backend/src/utils/secrets.ts @@ -8,19 +8,30 @@ import config from '../config' const client = new SecretsManagerClient({ region: config.aws.region }) export async function getDispenserKey() { + let key: string if (config.keys.dispenserGuard.key) { - return { key: JSON.parse(config.keys.dispenserGuard.key) } + console.log('Using dispenser guard key from config') + key = config.keys.dispenserGuard.key } - return getSecret(config.keys.dispenserGuard.secretName) + key = await getSecretKey(config.keys.dispenserGuard.secretName, 'key') + return { key: JSON.parse(key) } } -export async function getFundingKey() { +export async function getFundingKey(): Promise<{ key: Uint8Array }> { + let key: string if (config.keys.funding.key) { - return { key: JSON.parse(config.keys.funding.key) } + console.log('Using funding key from config') + key = config.keys.funding.key } - return getSecret(config.keys.funding.secretName) + key = await getSecretKey(config.keys.funding.secretName, 'key') + return { key: JSON.parse(key) } +} + +export async function getSecretKey(secretName: string, keyName: string) { + const secret = await getSecret(secretName) + return secret[keyName] } export async function getSecret(secretName: string) { @@ -28,8 +39,14 @@ export async function getSecret(secretName: string) { const command = new GetSecretValueCommand({ SecretId: secretName }) const response = await client.send(command) if (response.SecretString) { + console.log( + `Retrieved secret: ${secretName}. ${response.SecretString.length} characters long` + ) return JSON.parse(response.SecretString) } else if (response.SecretBinary) { + console.log( + `Retrieved binary secret: ${secretName}. ${response.SecretBinary.length} characters long` + ) // For binary secrets, use Buffer to decode const buff = Buffer.from(response.SecretBinary.toString(), 'base64') return JSON.parse(buff.toString('ascii')) diff --git a/backend/test/handlers/discord-signed-digest.test.ts b/backend/test/handlers/discord-signed-digest.test.ts index f795b5f2..bbbd1c08 100644 --- a/backend/test/handlers/discord-signed-digest.test.ts +++ b/backend/test/handlers/discord-signed-digest.test.ts @@ -64,7 +64,7 @@ const givenDownstreamServicesWork = () => { }), http.all('https://secretsmanager.us-east-2.amazonaws.com', () => { return HttpResponse.json({ - SecretString: JSON.stringify({ key: [...new Keypair().secretKey] }) + SecretString: JSON.stringify({ key: `[${new Keypair().secretKey}]` }) }) }) ) @@ -83,7 +83,7 @@ const whenSignDiscordMessageCalled = async ( ) => { response = await signDiscordMessage({ queryStringParameters: queryParams, - headers: { 'x-auth-token': 'token' } + headers: { Authorization: 'Bearer token' } } as unknown as APIGatewayProxyEvent) } diff --git a/backend/test/handlers/fund-transactions.test.ts b/backend/test/handlers/fund-transactions.test.ts index 074bec1a..e2c0e53a 100644 --- a/backend/test/handlers/fund-transactions.test.ts +++ b/backend/test/handlers/fund-transactions.test.ts @@ -229,7 +229,7 @@ const givenDownstreamServicesWork = () => { server.use( http.all('https://secretsmanager.us-east-2.amazonaws.com', () => { return HttpResponse.json({ - SecretString: JSON.stringify({ key: [...FUNDER_KEY.secretKey] }) + SecretString: JSON.stringify({ key: `[${[...FUNDER_KEY.secretKey]}]` }) }) }) ) diff --git a/backend/yarn.lock b/backend/yarn.lock index 990958c4..b00f14d5 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2320,6 +2320,11 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -2440,6 +2445,13 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" diff --git a/frontend/components/buttons/ProceedButton.tsx b/frontend/components/buttons/ProceedButton.tsx index 56f4ecb5..a9d4a7cd 100644 --- a/frontend/components/buttons/ProceedButton.tsx +++ b/frontend/components/buttons/ProceedButton.tsx @@ -18,7 +18,7 @@ export function ProceedButton({ return ( ) diff --git a/frontend/hooks/useDiscordAuth.tsx b/frontend/hooks/useDiscordAuth.tsx index deb95ace..8d09c439 100644 --- a/frontend/hooks/useDiscordAuth.tsx +++ b/frontend/hooks/useDiscordAuth.tsx @@ -142,5 +142,5 @@ export default function useDiscordAuth() { // Fetch user info on getDiscordUserInfo change, that should mutate on token or isAuthenticated change useEffect(() => getDiscordUserInfo(), [getDiscordUserInfo]) - return { authenticate, clear, isConnecting, isAuthenticated, profile } + return { authenticate, clear, isConnecting, isAuthenticated, profile, token } } diff --git a/frontend/hooks/useSignMessage.tsx b/frontend/hooks/useSignMessage.tsx index 0af91f8d..d7034ded 100644 --- a/frontend/hooks/useSignMessage.tsx +++ b/frontend/hooks/useSignMessage.tsx @@ -17,6 +17,7 @@ import { fetchDiscordSignedMessage } from 'utils/api' import { useTokenDispenserProvider } from './useTokenDispenserProvider' import { ChainName } from '@components/wallets/Cosmos' import { useWallet as useAlgorandWallet } from '@components/Ecosystem/AlgorandProvider' +import useDiscordAuth from './useDiscordAuth' // SignMessageFn signs the message and returns it. // It will return undefined: @@ -177,11 +178,11 @@ export function useSuiSignMessage(): SignMessageFn { export function useDiscordSignMessage(): SignMessageFn { const tokenDispenser = useTokenDispenserProvider() - + const { token } = useDiscordAuth() return useCallback(async () => { - if (tokenDispenser?.claimant === undefined) return - return await fetchDiscordSignedMessage(tokenDispenser.claimant) - }, [tokenDispenser?.claimant]) + if (tokenDispenser?.claimant === undefined || token === null) return + return await fetchDiscordSignedMessage(tokenDispenser.claimant, token) + }, [tokenDispenser?.claimant, token]) } // This hook returns a function to sign message for the Algorand wallet. diff --git a/frontend/integration/api.ts b/frontend/integration/api.ts index 7e99deb6..19dcb9c6 100644 --- a/frontend/integration/api.ts +++ b/frontend/integration/api.ts @@ -72,7 +72,7 @@ export async function handlerAmountAndProof( } else { res.status(200).json({ amount: result.amount, - proof: result.proof, + hashes: result.proof_of_inclusion, address: identity, }) } diff --git a/frontend/integration/integrationTest.test.ts b/frontend/integration/integrationTest.test.ts index da12621e..3fff9b06 100644 --- a/frontend/integration/integrationTest.test.ts +++ b/frontend/integration/integrationTest.test.ts @@ -23,23 +23,8 @@ import { ethers } from 'ethers' import { addTestWalletsToDatabase as addTestWalletsToInMemoryDb, clearInMemoryDb, - getInMemoryDb, } from './utils' -function getDatabasePool() { - return { - end: async () => {}, - } -} - -async function clearDatabase(..._: any[]) {} - -async function addTestWalletsToDatabase(..._: any[]) { - return [] -} - -const pool = getDatabasePool() - describe('integration test', () => { let root: Buffer let maxAmount: anchor.BN @@ -241,7 +226,7 @@ describe('integration test', () => { ).toBeTruthy() }, 40000) - it('submits a cosmwasm claim', async () => { + it('submits a terra claim', async () => { const { claimInfo, proofOfInclusion } = (await mockFetchAmountAndProof( 'terra', testWallets.terra[0].address() @@ -274,7 +259,7 @@ describe('integration test', () => { const claimantFund = await mint.getAccountInfo(claimantFundPubkey) expect( - claimantFund.amount.eq(new anchor.BN(3000000 + 6000000)) + claimantFund.amount.eq(new anchor.BN(3000000 + 7000000)) ).toBeTruthy() const { txnEvents } = @@ -301,7 +286,7 @@ describe('integration test', () => { it('submits multiple claims at once', async () => { const wallets = { - terra: testWallets.terra[0], + injective: testWallets.injective[0], osmosis: testWallets.osmosis[0], } @@ -360,50 +345,11 @@ describe('integration test', () => { expect( claimantFund.amount.eq( - new anchor.BN(3000000 + 6000000 + 6100000 + 6200000) + new anchor.BN(3000000 + 7000000 + 8000000 + 9000000) ) ).toBeTruthy() }) - it('submits an injective claim', async () => { - const wallet = testWallets.injective[0] - const { claimInfo, proofOfInclusion } = (await mockFetchAmountAndProof( - 'injective', - wallet.address() - ))! - const signedMessage = await wallet.signMessage( - tokenDispenserProvider.generateAuthorizationPayload() - ) - - await Promise.all( - await tokenDispenserProvider.submitClaims( - [ - { - claimInfo, - proofOfInclusion, - signedMessage, - }, - ], - mockfetchFundTransaction - ) - ) - - expect( - await tokenDispenserProvider.isClaimAlreadySubmitted(claimInfo) - ).toBeTruthy() - - const claimantFundPubkey = - await tokenDispenserProvider.getClaimantFundAddress() - - const claimantFund = await mint.getAccountInfo(claimantFundPubkey) - - expect( - claimantFund.amount.eq( - new anchor.BN(3000000 + 6000000 + 6100000 + 6200000 + 7000000) - ) - ).toBeTruthy() - }, 40000) - it('submits an aptos claim', async () => { const wallet = testWallets.aptos[0] const { claimInfo, proofOfInclusion } = (await mockFetchAmountAndProof( @@ -438,9 +384,7 @@ describe('integration test', () => { expect( claimantFund.amount.eq( - new anchor.BN( - 3000000 + 6000000 + 6100000 + 6200000 + 7000000 + 5000000 - ) + new anchor.BN(3000000 + 7000000 + 8000000 + 9000000 + 6000000) ) ).toBeTruthy() }) @@ -484,13 +428,7 @@ describe('integration test', () => { expect( claimantFund.amount.eq( new anchor.BN( - 3000000 + - 6000000 + - 6100000 + - 6200000 + - 7000000 + - 5000000 + - 1000000 + 3000000 + 7000000 + 8000000 + 9000000 + 6000000 + 1000000 ) ) ).toBeTruthy() @@ -530,14 +468,7 @@ describe('integration test', () => { expect( claimantFund.amount.eq( new anchor.BN( - 3000000 + - 6000000 + - 6100000 + - 6200000 + - 7000000 + - 5000000 + - 1000000 + - 2000000 + 3000000 + 7000000 + 8000000 + 9000000 + 6000000 + 1000000 + 2000000 ) ) ).toBeTruthy() @@ -579,11 +510,10 @@ describe('integration test', () => { claimantFund.amount.eq( new anchor.BN( 3000000 + - 6000000 + - 6100000 + - 6200000 + 7000000 + - 5000000 + + 8000000 + + 9000000 + + 6000000 + 1000000 + 2000000 + 4000000 @@ -591,6 +521,7 @@ describe('integration test', () => { ) ).toBeTruthy() }, 40000) + it('fails to submit a duplicate claim', async () => { const wallet = testWallets.sui[0] const { claimInfo, proofOfInclusion } = (await mockFetchAmountAndProof( @@ -615,6 +546,7 @@ describe('integration test', () => { ) expect(JSON.stringify(res[0]).includes('InstructionError')).toBeTruthy() }) + it('eventSubscriber parses error transaction logs', async () => { const { txnEvents, failedTxnInfos } = await tokenDispenserEventSubscriber.parseTransactionLogs() diff --git a/frontend/integration/utils.ts b/frontend/integration/utils.ts index fc3e5b9e..0d1856ed 100644 --- a/frontend/integration/utils.ts +++ b/frontend/integration/utils.ts @@ -1,17 +1,12 @@ import 'dotenv/config' // Load environment variables from .env file import * as anchor from '@coral-xyz/anchor' -import { - TestEvmWallet, - TestSolanaWallet, - TestWallet, -} from '../claim_sdk/testWallets' +import { TestWallet } from '../claim_sdk/testWallets' import { ClaimInfo, Ecosystem, Ecosystems } from '../claim_sdk/claim' import { getMaxAmount } from '../claim_sdk/claim' import { MerkleTree } from '../claim_sdk/merkleTree' const CHUNK_SIZE = 1000 -const SOLANA_ECOSYSTEM_INDEX = 2 -const EVM_ECOSYSTEM_INDEX = 3 + export const EVM_CHAINS = [ 'optimism-mainnet', 'arbitrum-mainnet', diff --git a/frontend/pages/login-solana.tsx b/frontend/pages/login-solana.tsx index e264271a..5a4dca7c 100644 --- a/frontend/pages/login-solana.tsx +++ b/frontend/pages/login-solana.tsx @@ -8,7 +8,7 @@ import { LoggedInSolana } from '@sections/LoggedInSolana' export const LOGIN_SOLANA_METADATA = { url: '/login-solana', - title: 'Log in with Solana', + title: 'Connect to Solana', } export default function LogInWithSolanaPage() { diff --git a/frontend/pages/review-eligibility.tsx b/frontend/pages/review-eligibility.tsx index 2b9d43fc..618a79be 100644 --- a/frontend/pages/review-eligibility.tsx +++ b/frontend/pages/review-eligibility.tsx @@ -5,7 +5,7 @@ import { VERIFY_ELIGIBILITY_METADATA } from './verify-eligibility' export const REVIEW_ELIGIBILITY_METADATA = { url: '/review-eligibility', - title: 'Review Airdrop Eligibility', + title: 'Review Eligibility', } export default function ReviewEligibilitPage() { diff --git a/frontend/pages/verify-eligibility/index.tsx b/frontend/pages/verify-eligibility/index.tsx index fce7200d..056c3b06 100644 --- a/frontend/pages/verify-eligibility/index.tsx +++ b/frontend/pages/verify-eligibility/index.tsx @@ -17,14 +17,12 @@ export default function VerifyEligibilityPage() { Verify Eligibility

- Please connect your wallets and Discord account according to the boxes - you checked in Step 2. You can go back and change any - of your selections. + Please connect all wallets based on the networks you chose in the + previous step. Feel free to go back and adjust any selections if + necessary.

- You will not be able to proceed to Step 4 to claim - your PYTH tokens if you do not successfully connect all of your - wallets or Discord account. + {`Note that you won't be able to move on to the next step and claim your W unless all your wallets or Discord account are successfully connected.`}

diff --git a/frontend/scripts/setup.sh b/frontend/scripts/setup.sh index 33900137..fe894306 100755 --- a/frontend/scripts/setup.sh +++ b/frontend/scripts/setup.sh @@ -85,7 +85,6 @@ function stop_anchor_localnet() { if [ -n "$solana_pid" ]; then echo "killing solana-test-validator with pid: $solana_pid" kill -9 "$solana_pid" - pgrep -f 'solana-test-validator' | xargs kill -9 else echo "No solana-test-validator process found to stop" fi diff --git a/frontend/sections/ClaimStatus.tsx b/frontend/sections/ClaimStatus.tsx index e2bb6a96..10048c8e 100644 --- a/frontend/sections/ClaimStatus.tsx +++ b/frontend/sections/ClaimStatus.tsx @@ -50,9 +50,7 @@ export const ClaimStatus = ({
-

- Sign Your Wallets and Claim -

+

Sign and Claim

{
- Log in with Solana + Connect to Solana

- PYTH tokens are native to Solana. You need a Solana wallet to proceed - and receive your PYTH tokens. Your claimed PYTH tokens will go to the - Solana wallet you connect in this step. -

-

- You can find a list of popular wallets that support Solana (SPL) - tokens below. + W is native to the Solana network. To receive your W, a Solana wallet + is required. The W you claim will be sent to the Solana wallet you + link during this process.

+

Below, you'll find a list of popular Solana wallets.

{wallet === null ? ( ) : ( diff --git a/frontend/sections/LoggedInSolana.tsx b/frontend/sections/LoggedInSolana.tsx index bfe21b1f..bc3ee76c 100644 --- a/frontend/sections/LoggedInSolana.tsx +++ b/frontend/sections/LoggedInSolana.tsx @@ -16,15 +16,15 @@ export const LoggedInSolana = ({ onBack, onProceed }: StepProps) => {
- Log in with Solana + Connect to Solana

- PYTH tokens are native to Solana. You need a Solana wallet to receive - your tokens. Your claimed PYTH tokens will go to the Solana wallet you - have connected in the previous step. + W is native to the Solana network. To receive your W, a Solana wallet + is required. The W you claim will be sent to the Solana wallet you + link during this process.

To change the connected wallet please go to the previous step. diff --git a/frontend/sections/PastActivity.tsx b/frontend/sections/PastActivity.tsx index 5f181d13..461d2eec 100644 --- a/frontend/sections/PastActivity.tsx +++ b/frontend/sections/PastActivity.tsx @@ -33,14 +33,14 @@ export const PastActivity = ({ onBack, onProceed }: StepProps) => { return ( <> - {`Let's Review Your Activity`} + {`Activity Review`}

- Please check the following boxes below corresponding to your wallet - and social activity in the Pyth ecosystem. + Please tick the appropriate boxes below that reflect your wallet + usage and participation in the Wormhole ecosystem.

-

I am active on…

+

Active on:

{Object.values(Ecosystem).map((ecosystem) => { if (ecosystem === Ecosystem.DISCORD) { @@ -57,7 +57,7 @@ export const PastActivity = ({ onBack, onProceed }: StepProps) => { } })}
-

I am an active member of…

+

Wormhole Discord member:

{ - Sign Your Wallets and Claim + Sign and Claim

- {`Please sign your connected wallets. To sign, click the - corresponding “sign” button for each wallet. Your wallet will ask - if you wish to sign the transaction. Confirm by clicking “sign” in - your wallet's pop-up window.`} + {`Please proceed to sign your connected wallets. Press the “Sign” button next to each wallet and confirm in the pop-up window.`}

- Note: You will sign with your Solana wallet at a later stage. No - action is required for your Discord account. + Signing with your Solana wallet will be done at a later step. + Discord requires no further action.

-

Your claimed PYTH tokens will go to this Solana wallet:

+

Your W will be claimed to the following Solana wallet:

setScreen(2)} /> @@ -254,12 +251,12 @@ function ClaimAirdropModal({ return (

- Claim Airdrop + Claim W

- Please ensure that you have connected all the necessary wallets and the - Discord account with your claim. Additionally, you can repeat the - Airdrop Claim process using a different set of wallets. + Please make sure you’ve connected all required wallets and Discord + accounts. You have the option to go through the claim process again + using different wallets.

diff --git a/frontend/sections/SignForEligibleWallets.tsx b/frontend/sections/SignForEligibleWallets.tsx index 07d3e5c9..cd358c67 100644 --- a/frontend/sections/SignForEligibleWallets.tsx +++ b/frontend/sections/SignForEligibleWallets.tsx @@ -86,9 +86,7 @@ export const SignForEligibleWallets = ({
- - Sign Your Wallets and Claim - + Sign and Claim
, ] } else { diff --git a/frontend/sections/Welcome.tsx b/frontend/sections/Welcome.tsx index 02b087ed..7ce09160 100644 --- a/frontend/sections/Welcome.tsx +++ b/frontend/sections/Welcome.tsx @@ -9,37 +9,18 @@ export const Welcome = ({ onProceed }: { onProceed: () => void }) => { Welcome to the Wormhole Airdrop

- As part of the Wormhole’s recent governance initiative, W tokens - have been allocated to the community. + The Wormhole platform is on a road of further decentralization. This + airdrop is constructed to support that vision and meaningfully + decentralize stakeholders from the start.

- You may be eligible for the Wormhole Airdrop if you: + Eligibility for this W airdrop may apply to you if you've interacted + with ecosystem chains, applications, or the community within the + Wormhole ecosystem in the past. The snapshot for this airdrop has + already been taken as of February 6, 2024, 23:59 UTC.

-
    -
  • - Interacted with apps that use Wormhole data on any supported - blockchain, including Solana, Aptos, Sui, Cosmos, and the EVM - ecosystem. -
  • - -
  • - Received special community roles in the official Wormhole Discord - server. -
  • -
-

- {' '} - - {' '} - Note: The eligibility window for the airdrop has closed. No - further participants can become eligible. - -

-

- This website will check your wallet activity and Discord account to - calculate how many W tokens you are eligible to claim. Your progress - is automatically saved. You will not lose your progress if you - leave. +

+ Please proceed to check your eligibility and claim your W.

diff --git a/frontend/utils/api.ts b/frontend/utils/api.ts index 9e90f301..633ae4d7 100644 --- a/frontend/utils/api.ts +++ b/frontend/utils/api.ts @@ -77,7 +77,7 @@ export async function fetchAmountAndProof( // The best case will be to have only one file per identity for (const file of files) { const response = await fetch(file) - if (response.headers.get('content-type') === 'application/json') { + if (response.headers.get('content-type')?.includes('application/json')) { const data = await response.json() if ( response.status === 200 && @@ -111,9 +111,14 @@ export function handleDiscordSignedMessageResponse( } export async function fetchDiscordSignedMessage( - claimant: PublicKey + claimant: PublicKey, + accessToken: string ): Promise { - const response = await fetch(getDiscordSignedMessageRoute(claimant)) + const response = await fetch(getDiscordSignedMessageRoute(claimant), { + headers: { + authorization: `Bearer ${accessToken}`, + }, + }) return handleDiscordSignedMessageResponse( response.status, await response.json()