From db737144ff40af2a5ef0849e4fe78ec9a8d71988 Mon Sep 17 00:00:00 2001 From: Urban Osvald Date: Tue, 26 Mar 2024 18:03:03 +0100 Subject: [PATCH 1/5] UI copy updates (#40) * Updates to copy in the UI * try fix build --------- Co-authored-by: Adrian Timpau --- frontend/components/buttons/ProceedButton.tsx | 2 +- frontend/pages/login-solana.tsx | 2 +- frontend/pages/review-eligibility.tsx | 2 +- frontend/pages/verify-eligibility/index.tsx | 8 ++--- frontend/sections/ClaimStatus.tsx | 2 +- frontend/sections/LogInWithSolana.tsx | 9 ++---- frontend/sections/LoggedInSolana.tsx | 6 ++-- frontend/sections/PastActivity.tsx | 9 +++--- frontend/sections/SignAndClaim.tsx | 18 ++++------- frontend/sections/SignForEligibleWallets.tsx | 2 +- frontend/sections/WalletsEligibility.tsx | 4 +-- frontend/sections/Welcome.tsx | 32 +++---------------- 12 files changed, 28 insertions(+), 68 deletions(-) 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/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..1ed14bf3 100644 --- a/frontend/pages/verify-eligibility/index.tsx +++ b/frontend/pages/verify-eligibility/index.tsx @@ -17,14 +17,10 @@ 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/sections/ClaimStatus.tsx b/frontend/sections/ClaimStatus.tsx index e2bb6a96..83857d61 100644 --- a/frontend/sections/ClaimStatus.tsx +++ b/frontend/sections/ClaimStatus.tsx @@ -51,7 +51,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. + 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.

- You can find a list of popular wallets that support Solana (SPL) - tokens below. + 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..f13dcf15 100644 --- a/frontend/sections/LoggedInSolana.tsx +++ b/frontend/sections/LoggedInSolana.tsx @@ -16,15 +16,13 @@ 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..262cc743 100644 --- a/frontend/sections/PastActivity.tsx +++ b/frontend/sections/PastActivity.tsx @@ -33,14 +33,13 @@ 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 +56,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 +250,10 @@ 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..e2f04f1f 100644 --- a/frontend/sections/SignForEligibleWallets.tsx +++ b/frontend/sections/SignForEligibleWallets.tsx @@ -87,7 +87,7 @@ export const SignForEligibleWallets = ({
- Sign Your Wallets and Claim + Sign and Claim
diff --git a/frontend/sections/WalletsEligibility.tsx b/frontend/sections/WalletsEligibility.tsx index bf1cb6c2..dfd7553d 100644 --- a/frontend/sections/WalletsEligibility.tsx +++ b/frontend/sections/WalletsEligibility.tsx @@ -65,7 +65,7 @@ const Eligibility = ({ return } else if (areAllTokensClaimed) { setIsProceedDisabled(true) - setProceedTooltipContent('There are no tokens to claim.') + setProceedTooltipContent('There are no W to claim') return } else { setIsProceedDisabled(false) @@ -159,7 +159,7 @@ function TableRow({ ecosystem }: TableRowProps) { } else { if (eligibility?.claimInfo === undefined) { return [ - 'This wallet is unfortunately not eligible for an allocation. You can click on the wallet address to disconnect and connect to another wallet.', + 'The connected wallet is unfortunately not eligible for any W.', , ] } else { diff --git a/frontend/sections/Welcome.tsx b/frontend/sections/Welcome.tsx index 02b087ed..13d4deb6 100644 --- a/frontend/sections/Welcome.tsx +++ b/frontend/sections/Welcome.tsx @@ -9,37 +9,13 @@ 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.

From 6c49738f04bd18b6205f15f8bec8a6eca4a88d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Mart=C3=ADnez?= <131624652+mat1asm@users.noreply.github.com> Date: Wed, 27 Mar 2024 09:24:04 -0300 Subject: [PATCH 2/5] [be] fix secrets parsing (#45) * some debugging logs * treat secret as string when reading from manager * using authorization instead of x-auth-token * lower case header --- backend/src/handlers/discord-signed-digest.ts | 28 +++++++++++++++---- backend/src/handlers/fund-transactions.ts | 12 ++++++-- backend/src/utils/secrets.ts | 27 ++++++++++++++---- .../handlers/discord-signed-digest.test.ts | 4 +-- .../test/handlers/fund-transactions.test.ts | 2 +- 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/backend/src/handlers/discord-signed-digest.ts b/backend/src/handlers/discord-signed-digest.ts index 7d6d89d0..a1ae43d1 100644 --- a/backend/src/handlers/discord-signed-digest.ts +++ b/backend/src/handlers/discord-signed-digest.ts @@ -8,6 +8,8 @@ export interface DiscordSignedDigestParams { publicKey: string } +let guardKeyPair: Keypair + export const signDiscordMessage = async ( event: APIGatewayProxyEvent ): Promise => { @@ -15,7 +17,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!) @@ -48,6 +53,10 @@ export const signDiscordMessage = async ( } async function loadDispenserGuard() { + if (guardKeyPair) { + return guardKeyPair + } + const secretData = await getDispenserKey() const dispenserGuardKey = secretData.key @@ -55,7 +64,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 +91,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..33062856 100644 --- a/backend/src/handlers/fund-transactions.ts +++ b/backend/src/handlers/fund-transactions.ts @@ -10,6 +10,8 @@ import { HandlerError } from '../utils/errors' export type FundTransactionRequest = Uint8Array[] +let funderWallet: NodeWallet + export const fundTransactions = async ( event: APIGatewayProxyEvent ): Promise => { @@ -63,10 +65,16 @@ 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)) - return new NodeWallet(keypair) + funderWallet = new NodeWallet(keypair) + console.log('Loaded funder wallet') + return funderWallet } 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 1bcdc469..1de20193 100644 --- a/backend/test/handlers/fund-transactions.test.ts +++ b/backend/test/handlers/fund-transactions.test.ts @@ -45,7 +45,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]}]` }) }) }) ) From 6488d0e0af505c75e8a42470e5f58f1a3f52f819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Mart=C3=ADnez?= <131624652+mat1asm@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:47:45 -0300 Subject: [PATCH 3/5] Updating integration tests to assert on latest ecosystems changes (#42) * updating tests based on ecosystems changes * running fe tests when program changes * temporarilly remove install jq * restoring env in fe action * kill validator on stop test * restore paths setting on FE workflow * use hashes instead of prrog * prettier --- .github/workflows/frontend.yml | 11 ++- frontend/integration/api.ts | 2 +- frontend/integration/integrationTest.test.ts | 92 +++----------------- frontend/integration/utils.ts | 9 +- frontend/pages/verify-eligibility/index.tsx | 4 +- frontend/scripts/setup.sh | 1 - frontend/sections/ClaimStatus.tsx | 4 +- frontend/sections/LogInWithSolana.tsx | 8 +- frontend/sections/LoggedInSolana.tsx | 4 +- frontend/sections/PastActivity.tsx | 3 +- frontend/sections/SignAndClaim.tsx | 7 +- frontend/sections/SignForEligibleWallets.tsx | 4 +- frontend/sections/Welcome.tsx | 11 ++- 13 files changed, 51 insertions(+), 109 deletions(-) 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/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/verify-eligibility/index.tsx b/frontend/pages/verify-eligibility/index.tsx index 1ed14bf3..056c3b06 100644 --- a/frontend/pages/verify-eligibility/index.tsx +++ b/frontend/pages/verify-eligibility/index.tsx @@ -17,7 +17,9 @@ export default function VerifyEligibilityPage() { Verify Eligibility

- 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. + 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.

{`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 83857d61..10048c8e 100644 --- a/frontend/sections/ClaimStatus.tsx +++ b/frontend/sections/ClaimStatus.tsx @@ -50,9 +50,7 @@ export const ClaimStatus = ({

-

- Sign and Claim -

+

Sign and Claim

{

- 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. + 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 f13dcf15..bc3ee76c 100644 --- a/frontend/sections/LoggedInSolana.tsx +++ b/frontend/sections/LoggedInSolana.tsx @@ -22,7 +22,9 @@ export const LoggedInSolana = ({ onBack, onProceed }: StepProps) => {

- 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. + 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 262cc743..461d2eec 100644 --- a/frontend/sections/PastActivity.tsx +++ b/frontend/sections/PastActivity.tsx @@ -36,7 +36,8 @@ export const PastActivity = ({ onBack, onProceed }: StepProps) => { {`Activity Review`}

- Please tick the appropriate boxes below that reflect your wallet usage and participation in the Wormhole ecosystem. + Please tick the appropriate boxes below that reflect your wallet + usage and participation in the Wormhole ecosystem.

Active on:

diff --git a/frontend/sections/SignAndClaim.tsx b/frontend/sections/SignAndClaim.tsx index 5e5dcdea..6d54112b 100644 --- a/frontend/sections/SignAndClaim.tsx +++ b/frontend/sections/SignAndClaim.tsx @@ -207,7 +207,8 @@ export const SignAndClaim = ({ onBack, onProceed }: SignAndClaimProps) => { {`Please proceed to sign your connected wallets. Press the “Sign” button next to each wallet and confirm in the pop-up window.`}

- Signing with your Solana wallet will be done at a later step. Discord requires no further action. + Signing with your Solana wallet will be done at a later step. + Discord requires no further action.

Your W will be claimed to the following Solana wallet:

@@ -253,7 +254,9 @@ function ClaimAirdropModal({ Claim W

- 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. + 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 e2f04f1f..cd358c67 100644 --- a/frontend/sections/SignForEligibleWallets.tsx +++ b/frontend/sections/SignForEligibleWallets.tsx @@ -86,9 +86,7 @@ export const SignForEligibleWallets = ({
- - Sign and Claim - + Sign and Claim
void }) => { Welcome to the Wormhole Airdrop

- 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. + 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.

- 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. + 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.

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

From 50fb337e98b8355f994f1f79cd2b489beb82d499 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Mar 2024 11:47:29 -0300 Subject: [PATCH 4/5] forward bearer token to the backend (#46) * fordward bearer token to the backend * prefix Bearer and change content type check * run prettier --- frontend/hooks/useDiscordAuth.tsx | 2 +- frontend/hooks/useSignMessage.tsx | 9 +++++---- frontend/utils/api.ts | 11 ++++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) 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/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() From 7cbc1eca91bf221fe3119fc972d0264a74ecd010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Mart=C3=ADnez?= <131624652+mat1asm@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:04:23 -0300 Subject: [PATCH 5/5] [be] log sigs + application/json header (#48) * some debugging logs * treat secret as string when reading from manager * using authorization instead of x-auth-token * lower case header * set json header * add signatures log --- backend/package.json | 1 + backend/src/handlers/discord-signed-digest.ts | 24 ++++------ backend/src/handlers/fund-transactions.ts | 46 +++++++++++-------- backend/src/utils/response.ts | 12 +++++ backend/yarn.lock | 12 +++++ 5 files changed, 60 insertions(+), 35 deletions(-) create mode 100644 backend/src/utils/response.ts 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 a1ae43d1..596ce561 100644 --- a/backend/src/handlers/discord-signed-digest.ts +++ b/backend/src/handlers/discord-signed-digest.ts @@ -3,6 +3,7 @@ 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 @@ -28,27 +29,18 @@ 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' }) } } diff --git a/backend/src/handlers/fund-transactions.ts b/backend/src/handlers/fund-transactions.ts index 33062856..fcfe5ecd 100644 --- a/backend/src/handlers/fund-transactions.ts +++ b/backend/src/handlers/fund-transactions.ts @@ -5,8 +5,10 @@ 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[] @@ -22,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' }) } } @@ -78,3 +70,19 @@ async function loadFunderWallet(): Promise { 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' +} + +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/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"