Skip to content

refactor: fix arweave payment verification #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default () => ({
},
arweave: {
address: process.env.ARWEAVE_ADDRESS,
pk: process.env.ARWEAVE_PK,
},
solana: {
address: process.env.SOLANA_ADDRESS,
Expand All @@ -34,6 +35,7 @@ export default () => ({
addresses: {
evm: process.env.FEE_ADDRESS_EVM,
solana: process.env.FEE_ADDRESS_SOLANA,
arweave: process.env.FEE_ADDRESS_ARWEAVE,
},
feePercentage: process.env.FEE_PERCENTAGE,
},
Expand Down
28 changes: 26 additions & 2 deletions src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,33 @@ export class AuthService {
throw new BadRequestException('Invalid Arweave signature: nonce missing or mismatch');
}

const verified = this.getArweave().crypto.verify(publicKey, Uint8Array.from(Buffer.from(signedMessage)), Uint8Array.from(Buffer.from(signature)));
const publicJWK = {
e: "AQAB",
ext: true,
kty: "RSA",
n: publicKey
};

if (!verified) {
const verificationKey = await crypto.subtle.importKey(
"jwk",
publicJWK,
{
name: "RSA-PSS",
hash: "SHA-256"
},
false,
["verify"]
);

const hash = await crypto.subtle.digest("SHA-256", Buffer.from(signedMessage));
const isValidSignature = await crypto.subtle.verify(
{ name: "RSA-PSS", saltLength: 32 },
verificationKey,
Buffer.from(signature, 'base64'),
hash
);

if (!isValidSignature) {
throw new BadRequestException('Invalid Arweave signature: verification failed');
}
}
Expand Down
28 changes: 19 additions & 9 deletions src/modules/upload/upload.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,25 @@ export class UploadController {
throw new BadRequestException('Completed upload already exists with receipt id: ' + receipt.id);
}

const verified = await this.uploadService.verifyPayment({
paymentTx: txnHash,
chainType: token.chainType,
network: token.network,
chainId: +token.chainId,
senderAddress: (req as any).user.walletAddress,
amount: paymentTransaction.amountInSubUnits,
tokenAddress: token.address
});
let verified = false;
let retries = 30;
while (!verified && retries > 0) {
verified = await this.uploadService.verifyPayment({
paymentTx: txnHash,
chainType: token.chainType,
network: token.network,
chainId: +token.chainId,
senderAddress: (req as any).user.walletAddress,
amount: paymentTransaction.amountInSubUnits,
tokenAddress: token.address
});
if (!verified) {
retries--;
if (retries > 0) {
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 second between retries
}
}
}
if (!verified) {
throw new BadRequestException('Payment verification failed. Invalid transaction hash or amount');
}
Expand Down
53 changes: 49 additions & 4 deletions src/modules/upload/upload.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { BadRequestException, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ChainType, Network, ReceiptStatus, TokenTicker, TransactionStatus, UploadStatus, } from '@prisma/client';
import { Connection, ParsedInstruction } from '@solana/web3.js';
import Arweave from 'arweave/node/common';
import axios from 'axios';
import BigNumber from 'bignumber.js';
import { Contract, getAddress, Interface, Provider } from 'ethers';
import * as fs from 'fs';
Expand Down Expand Up @@ -110,8 +112,8 @@ export class UploadService {
data: {
userWalletAddress: user.walletAddress,
tokenId: validToken.id,
amount: costInToken.amount,
amountInSubUnits: costInToken.amountInSubUnits,
amount: typeof costInToken.amount === 'string' ? costInToken.amount : BigNumber(costInToken.amount).toFixed(),
amountInSubUnits: typeof costInToken.amountInSubUnits === 'string' ? costInToken.amountInSubUnits : BigNumber(costInToken.amountInSubUnits).toString(),
}
})

Expand All @@ -122,7 +124,7 @@ export class UploadService {
fileName: fileName,
size: size,
uploadType: uploadType,
uploadEstimate: costInToken.amount,
uploadEstimate: typeof costInToken.amount === 'string' ? costInToken.amount : BigNumber(costInToken.amount).toFixed(),
uploadEstimateUSD: BigNumber(costEstimate.usd).toFixed(),
mimeType: mimeType,
totalChunks: totalChunks,
Expand Down Expand Up @@ -375,6 +377,28 @@ export class UploadService {
}
}

if (chainType === ChainType.arweave) {
//
console.log({ paymentTx })
const tx = await axios.post('https://arweave.net/graphql', {
query: `query{ transaction(id:"${paymentTx}"){ id, quantity{ winston ar }, owner{ address }, recipient } }`,
});
const data = tx.data;
const transaction = data.data.transaction;
if (!transaction) {
return false;
}
console.log({ transaction })

const { quantity, owner, recipient } = transaction;
const quantityInWinston = quantity.winston;
const ownerAddress = owner.address;
const recipientAddress = recipient;

const valid = BigInt(quantityInWinston) === BigInt(amount) && recipientAddress === systemAddress && ownerAddress === senderAddress;
return valid;
}

return false;
}

Expand Down Expand Up @@ -548,8 +572,29 @@ export class UploadService {
throw new BadRequestException("Solana fee debit not implemented");
}

if (ChainType.arweave === token.chainType) {
const jwk = JSON.parse(systemPrivateKey)

const arweave = Arweave.init({
host: 'arweave.net',
port: 443,
protocol: 'https',
})
console.log({ feeAmount })
const txn = await arweave.createTransaction({ quantity: feeAmount, target: feeAddress })
await arweave.transactions.sign(txn, jwk)
const res = await arweave.transactions.post(txn)
console.log({ res })

if (res.status !== 200) {
throw new BadRequestException("Fee transaction failed");
}


await this.databaseService.feeTransaction.update({
where: { id: feeTransactionId },
data: { status: TransactionStatus.SUCCEEDED, transactionHash: txn.id }
});
}
}

private async validateToken(chainType: ChainType, tokenTicker: TokenTicker, chainId: number, network: Network) {
Expand Down
13 changes: 10 additions & 3 deletions src/modules/upload/utils/Web3Provider.util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BadRequestException } from '@nestjs/common';
import { ChainType, Network } from '@prisma/client';
import { Connection } from '@solana/web3.js';
import Arweave from "arweave/node";
import { ethers, Provider, Signer } from 'ethers';


Expand Down Expand Up @@ -57,11 +58,17 @@ export class Web3Provider {
* @param chainType - The type of blockchain (EVM or SOLANA).
* @param network - The network to connect to (MAINNET or TESTNET).
*/
public static getProvider(chainType: ChainType, network: Network, chainId: number): Provider | Connection {
public static getProvider(chainType: ChainType, network: Network, chainId: number): Provider | Connection | Arweave {
if (chainType === ChainType.evm) {
return this.getEvmProvider(chainId);
} else if (chainType === ChainType.solana) {
return this.getSolanaConnection(network);
} else if (chainType === ChainType.arweave) {
return Arweave.init({
host: 'arweave.net',
port: 443,
protocol: 'https',
});
} else {
throw new Error('Unsupported chain type');
}
Expand All @@ -71,8 +78,8 @@ export class Web3Provider {
if (chainType === ChainType.evm) {
return new ethers.Wallet(privateKey, this.getEvmProvider(chainId));
} else if (chainType === ChainType.solana) {
// TODO: Implement solana signer
throw new BadRequestException("Solana signer not implemented");
// TODO: Implement solana signer
throw new BadRequestException("Solana signer not implemented");
} else {
throw new Error('Unsupported chain type');
}
Expand Down
Loading