Skip to content

Commit 3a7c811

Browse files
authored
Merge pull request #11 from labscommunity/kranthi/arweave-support
refactor: fix arweave payment verification
2 parents c2e232d + ad3b1ba commit 3a7c811

File tree

5 files changed

+106
-18
lines changed

5 files changed

+106
-18
lines changed

src/config/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default () => ({
2020
},
2121
arweave: {
2222
address: process.env.ARWEAVE_ADDRESS,
23+
pk: process.env.ARWEAVE_PK,
2324
},
2425
solana: {
2526
address: process.env.SOLANA_ADDRESS,
@@ -34,6 +35,7 @@ export default () => ({
3435
addresses: {
3536
evm: process.env.FEE_ADDRESS_EVM,
3637
solana: process.env.FEE_ADDRESS_SOLANA,
38+
arweave: process.env.FEE_ADDRESS_ARWEAVE,
3739
},
3840
feePercentage: process.env.FEE_PERCENTAGE,
3941
},

src/modules/auth/auth.service.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,33 @@ export class AuthService {
168168
throw new BadRequestException('Invalid Arweave signature: nonce missing or mismatch');
169169
}
170170

171-
const verified = this.getArweave().crypto.verify(publicKey, Uint8Array.from(Buffer.from(signedMessage)), Uint8Array.from(Buffer.from(signature)));
171+
const publicJWK = {
172+
e: "AQAB",
173+
ext: true,
174+
kty: "RSA",
175+
n: publicKey
176+
};
172177

173-
if (!verified) {
178+
const verificationKey = await crypto.subtle.importKey(
179+
"jwk",
180+
publicJWK,
181+
{
182+
name: "RSA-PSS",
183+
hash: "SHA-256"
184+
},
185+
false,
186+
["verify"]
187+
);
188+
189+
const hash = await crypto.subtle.digest("SHA-256", Buffer.from(signedMessage));
190+
const isValidSignature = await crypto.subtle.verify(
191+
{ name: "RSA-PSS", saltLength: 32 },
192+
verificationKey,
193+
Buffer.from(signature, 'base64'),
194+
hash
195+
);
196+
197+
if (!isValidSignature) {
174198
throw new BadRequestException('Invalid Arweave signature: verification failed');
175199
}
176200
}

src/modules/upload/upload.controller.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,15 +154,25 @@ export class UploadController {
154154
throw new BadRequestException('Completed upload already exists with receipt id: ' + receipt.id);
155155
}
156156

157-
const verified = await this.uploadService.verifyPayment({
158-
paymentTx: txnHash,
159-
chainType: token.chainType,
160-
network: token.network,
161-
chainId: +token.chainId,
162-
senderAddress: (req as any).user.walletAddress,
163-
amount: paymentTransaction.amountInSubUnits,
164-
tokenAddress: token.address
165-
});
157+
let verified = false;
158+
let retries = 30;
159+
while (!verified && retries > 0) {
160+
verified = await this.uploadService.verifyPayment({
161+
paymentTx: txnHash,
162+
chainType: token.chainType,
163+
network: token.network,
164+
chainId: +token.chainId,
165+
senderAddress: (req as any).user.walletAddress,
166+
amount: paymentTransaction.amountInSubUnits,
167+
tokenAddress: token.address
168+
});
169+
if (!verified) {
170+
retries--;
171+
if (retries > 0) {
172+
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 second between retries
173+
}
174+
}
175+
}
166176
if (!verified) {
167177
throw new BadRequestException('Payment verification failed. Invalid transaction hash or amount');
168178
}

src/modules/upload/upload.service.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { BadRequestException, Injectable } from '@nestjs/common';
22
import { ConfigService } from '@nestjs/config';
33
import { ChainType, Network, ReceiptStatus, TokenTicker, TransactionStatus, UploadStatus, } from '@prisma/client';
44
import { Connection, ParsedInstruction } from '@solana/web3.js';
5+
import Arweave from 'arweave/node/common';
6+
import axios from 'axios';
57
import BigNumber from 'bignumber.js';
68
import { Contract, getAddress, Interface, Provider } from 'ethers';
79
import * as fs from 'fs';
@@ -110,8 +112,8 @@ export class UploadService {
110112
data: {
111113
userWalletAddress: user.walletAddress,
112114
tokenId: validToken.id,
113-
amount: costInToken.amount,
114-
amountInSubUnits: costInToken.amountInSubUnits,
115+
amount: typeof costInToken.amount === 'string' ? costInToken.amount : BigNumber(costInToken.amount).toFixed(),
116+
amountInSubUnits: typeof costInToken.amountInSubUnits === 'string' ? costInToken.amountInSubUnits : BigNumber(costInToken.amountInSubUnits).toString(),
115117
}
116118
})
117119

@@ -122,7 +124,7 @@ export class UploadService {
122124
fileName: fileName,
123125
size: size,
124126
uploadType: uploadType,
125-
uploadEstimate: costInToken.amount,
127+
uploadEstimate: typeof costInToken.amount === 'string' ? costInToken.amount : BigNumber(costInToken.amount).toFixed(),
126128
uploadEstimateUSD: BigNumber(costEstimate.usd).toFixed(),
127129
mimeType: mimeType,
128130
totalChunks: totalChunks,
@@ -375,6 +377,28 @@ export class UploadService {
375377
}
376378
}
377379

380+
if (chainType === ChainType.arweave) {
381+
//
382+
console.log({ paymentTx })
383+
const tx = await axios.post('https://arweave.net/graphql', {
384+
query: `query{ transaction(id:"${paymentTx}"){ id, quantity{ winston ar }, owner{ address }, recipient } }`,
385+
});
386+
const data = tx.data;
387+
const transaction = data.data.transaction;
388+
if (!transaction) {
389+
return false;
390+
}
391+
console.log({ transaction })
392+
393+
const { quantity, owner, recipient } = transaction;
394+
const quantityInWinston = quantity.winston;
395+
const ownerAddress = owner.address;
396+
const recipientAddress = recipient;
397+
398+
const valid = BigInt(quantityInWinston) === BigInt(amount) && recipientAddress === systemAddress && ownerAddress === senderAddress;
399+
return valid;
400+
}
401+
378402
return false;
379403
}
380404

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

575+
if (ChainType.arweave === token.chainType) {
576+
const jwk = JSON.parse(systemPrivateKey)
577+
578+
const arweave = Arweave.init({
579+
host: 'arweave.net',
580+
port: 443,
581+
protocol: 'https',
582+
})
583+
console.log({ feeAmount })
584+
const txn = await arweave.createTransaction({ quantity: feeAmount, target: feeAddress })
585+
await arweave.transactions.sign(txn, jwk)
586+
const res = await arweave.transactions.post(txn)
587+
console.log({ res })
588+
589+
if (res.status !== 200) {
590+
throw new BadRequestException("Fee transaction failed");
591+
}
551592

552-
593+
await this.databaseService.feeTransaction.update({
594+
where: { id: feeTransactionId },
595+
data: { status: TransactionStatus.SUCCEEDED, transactionHash: txn.id }
596+
});
597+
}
553598
}
554599

555600
private async validateToken(chainType: ChainType, tokenTicker: TokenTicker, chainId: number, network: Network) {

src/modules/upload/utils/Web3Provider.util.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BadRequestException } from '@nestjs/common';
22
import { ChainType, Network } from '@prisma/client';
33
import { Connection } from '@solana/web3.js';
4+
import Arweave from "arweave/node";
45
import { ethers, Provider, Signer } from 'ethers';
56

67

@@ -57,11 +58,17 @@ export class Web3Provider {
5758
* @param chainType - The type of blockchain (EVM or SOLANA).
5859
* @param network - The network to connect to (MAINNET or TESTNET).
5960
*/
60-
public static getProvider(chainType: ChainType, network: Network, chainId: number): Provider | Connection {
61+
public static getProvider(chainType: ChainType, network: Network, chainId: number): Provider | Connection | Arweave {
6162
if (chainType === ChainType.evm) {
6263
return this.getEvmProvider(chainId);
6364
} else if (chainType === ChainType.solana) {
6465
return this.getSolanaConnection(network);
66+
} else if (chainType === ChainType.arweave) {
67+
return Arweave.init({
68+
host: 'arweave.net',
69+
port: 443,
70+
protocol: 'https',
71+
});
6572
} else {
6673
throw new Error('Unsupported chain type');
6774
}
@@ -71,8 +78,8 @@ export class Web3Provider {
7178
if (chainType === ChainType.evm) {
7279
return new ethers.Wallet(privateKey, this.getEvmProvider(chainId));
7380
} else if (chainType === ChainType.solana) {
74-
// TODO: Implement solana signer
75-
throw new BadRequestException("Solana signer not implemented");
81+
// TODO: Implement solana signer
82+
throw new BadRequestException("Solana signer not implemented");
7683
} else {
7784
throw new Error('Unsupported chain type');
7885
}

0 commit comments

Comments
 (0)