Use send tokens method to transfer lnt to service providers

This commit is contained in:
Shreerang Kale 2025-07-18 18:14:29 +05:30
parent ae4f5cbaa1
commit c47bee4efd
11 changed files with 94 additions and 54 deletions

View File

@ -6,10 +6,10 @@ NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFEL
NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS= NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS=
NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR
NEXT_PUBLIC_SOLANA_TOKEN_NAME=GOR Token NEXT_PUBLIC_SOLANA_TOKEN_NAME=GOR Token
NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT=50 NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT=400000000
# UI Configuration (optional) # UI Configuration (optional)
NEXT_PUBLIC_DOMAIN_SUFFIX= NEXT_PUBLIC_DOMAIN_SUFFIX=apps.vaasl.io
NEXT_PUBLIC_EXAMPLE_URL=https://github.com/cerc-io/laconic-registry-cli NEXT_PUBLIC_EXAMPLE_URL=https://github.com/cerc-io/laconic-registry-cli
# Server-side environment variables # Server-side environment variables

14
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@solana/spl-token": "^0.4.13", "@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98.2", "@solana/web3.js": "^1.98.2",
"axios": "^1.6.8", "axios": "^1.6.8",
"big.js": "^6.2.2",
"bn.js": "^5.2.2", "bn.js": "^5.2.2",
"next": "15.3.1", "next": "15.3.1",
"react": "^19.0.0", "react": "^19.0.0",
@ -3600,6 +3601,19 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/big.js": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz",
"integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==",
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/bigjs"
}
},
"node_modules/bigint-buffer": { "node_modules/bigint-buffer": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz",

View File

@ -14,6 +14,7 @@
"@solana/spl-token": "^0.4.13", "@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98.2", "@solana/web3.js": "^1.98.2",
"axios": "^1.6.8", "axios": "^1.6.8",
"big.js": "^6.2.2",
"bn.js": "^5.2.2", "bn.js": "^5.2.2",
"next": "15.3.1", "next": "15.3.1",
"react": "^19.0.0", "react": "^19.0.0",

View File

@ -100,11 +100,11 @@ const fetchLatestCommitHash = async (repoUrl: string, provider: string): Promise
}; };
// Registry transaction retry helper // Registry transaction retry helper
const registryTransactionWithRetry = async ( export const registryTransactionWithRetry = async (
txFn: () => Promise<unknown>, txFn: () => Promise<any>,
maxRetries = 3, maxRetries = 3,
delay = 1000 delay = 1000
): Promise<unknown> => { ): Promise<any> => {
let lastError; let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) { for (let attempt = 0; attempt < maxRetries; attempt++) {
@ -397,7 +397,7 @@ export async function POST(request: NextRequest) {
} }
}, },
meta: { meta: {
note: `Added via GOR-Deploy @ ${timestamp}`, note: `Added via ${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL}-Deploy @ ${timestamp}`,
repository: repoUrl, repository: repoUrl,
repository_ref: fullHash, repository_ref: fullHash,
external_payment: { external_payment: {

View File

@ -15,7 +15,7 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Deploy Frontends using GOR and Laconic", title: `Deploy Frontends using ${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} and Laconic`,
description: "Deploy URLs to Laconic Registry using GOR payments", description: "Deploy URLs to Laconic Registry using GOR payments",
}; };

View File

@ -38,7 +38,7 @@ export default function PaymentModal({
if (result.success && result.transactionSignature) { if (result.success && result.transactionSignature) {
onPaymentComplete(result.transactionSignature); onPaymentComplete(result.transactionSignature);
} else { } else {
setError(result.error || 'GOR payment failed. Please try again.'); setError(result.error || `${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} payment failed. Please try again.`);
} }
} catch (error) { } catch (error) {
setError(error instanceof Error ? error.message : 'Payment failed. Please try again.'); setError(error instanceof Error ? error.message : 'Payment failed. Please try again.');
@ -55,7 +55,7 @@ export default function PaymentModal({
style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}> style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}>
<div className="p-6 border-b" style={{ borderColor: 'var(--card-border)' }}> <div className="p-6 border-b" style={{ borderColor: 'var(--card-border)' }}>
<h2 className="text-xl font-semibold" style={{ color: 'var(--foreground)' }}> <h2 className="text-xl font-semibold" style={{ color: 'var(--foreground)' }}>
Complete GOR Payment Complete {process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} Payment
</h2> </h2>
</div> </div>
@ -76,7 +76,7 @@ export default function PaymentModal({
<div> <div>
<label htmlFor="amount" className="block text-sm font-medium mb-2" style={{ color: 'var(--foreground)' }}> <label htmlFor="amount" className="block text-sm font-medium mb-2" style={{ color: 'var(--foreground)' }}>
Amount (GOR) Amount ({process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL})
</label> </label>
<div className="relative"> <div className="relative">
<input <input
@ -94,7 +94,7 @@ export default function PaymentModal({
readOnly readOnly
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<span className="text-sm font-medium" style={{ color: 'var(--muted)' }}>GOR</span> <span className="text-sm font-medium" style={{ color: 'var(--muted)' }}>{process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL}</span>
</div> </div>
</div> </div>
<p className="text-xs mt-1" style={{ color: 'var(--muted)' }}> <p className="text-xs mt-1" style={{ color: 'var(--muted)' }}>

View File

@ -125,7 +125,7 @@ export default function StatusDisplay({
</div> </div>
)} )}
{txHash && <InfoItem label="GOR Payment Transaction Hash" value={txHash} />} {txHash && <InfoItem label={`${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} Payment Transaction Hash`} value={txHash} />}
{appRecordId && <InfoItem label="Laconic Application Record ID" value={appRecordId} />} {appRecordId && <InfoItem label="Laconic Application Record ID" value={appRecordId} />}
{recordId && <InfoItem label="Laconic Deployment Request Record ID" value={recordId} />} {recordId && <InfoItem label="Laconic Deployment Request Record ID" value={recordId} />}
{lrn && <InfoItem label="Laconic Resource Name (LRN)" value={lrn} />} {lrn && <InfoItem label="Laconic Resource Name (LRN)" value={lrn} />}

View File

@ -49,4 +49,3 @@ export const getLaconicTransferConfig = () => {
transferAmount: process.env.LACONIC_TRANSFER_AMOUNT! transferAmount: process.env.LACONIC_TRANSFER_AMOUNT!
}; };
}; };

View File

@ -1,7 +1,8 @@
import { Registry } from '@cerc-io/registry-sdk'; import { Account, DEFAULT_GAS_ESTIMATION_MULTIPLIER, parseGasAndFees, Registry } from '@cerc-io/registry-sdk';
import { GasPrice } from '@cosmjs/stargate'; import { DeliverTxResponse, GasPrice } from '@cosmjs/stargate';
import { getRegistryConfig, getLaconicTransferConfig } from '../config'; import { getRegistryConfig, getLaconicTransferConfig } from '../config';
import { LaconicTransferResult } from '../types'; import { LaconicTransferResult } from '../types';
import { registryTransactionWithRetry } from '@/app/api/registry/route';
let registryInstance: Registry | null = null; let registryInstance: Registry | null = null;
@ -21,7 +22,6 @@ const getRegistry = (): Registry => {
export const transferLNTTokens = async (): Promise<LaconicTransferResult> => { export const transferLNTTokens = async (): Promise<LaconicTransferResult> => {
try { try {
const registryConfig = getRegistryConfig();
const transferConfig = getLaconicTransferConfig(); const transferConfig = getLaconicTransferConfig();
const registry = getRegistry(); const registry = getRegistry();
@ -55,37 +55,16 @@ export const transferLNTTokens = async (): Promise<LaconicTransferResult> => {
} }
// Find the paymentAddress attribute // Find the paymentAddress attribute
const paymentAddressAttr = deployerRecord.attributes.find( const paymentAddress = deployerRecord.attributes.paymentAddress
(attr: any) => attr.key === 'paymentAddress'
);
if (!paymentAddressAttr || !paymentAddressAttr.value?.string) {
return {
success: false,
error: 'paymentAddress attribute not found in deployer record'
};
}
const paymentAddress = paymentAddressAttr.value.string;
console.log('Found payment address:', paymentAddress); console.log('Found payment address:', paymentAddress);
console.log('Initiating LNT transfer from prefilled account to payment address...'); console.log('Initiating LNT transfer from prefilled account to payment address...');
// Create fee for transaction
const fee = {
amount: [{ denom: 'alnt', amount: registryConfig.fee.fees.replace('alnt', '') || '900000' }],
gas: registryConfig.fee.gas || '900000',
};
// Send tokens from prefilled account to payment address // Send tokens from prefilled account to payment address
const transferResult = await registry.sendCoins( const transferResult = await sendTokensToAccount(
{ paymentAddress,
destinationAddress: paymentAddress, transferConfig.transferAmount
amount: transferConfig.transferAmount,
denom: 'alnt'
},
transferConfig.prefilledPrivateKey,
fee
); );
console.log('LNT transfer result:', transferResult); console.log('LNT transfer result:', transferResult);
@ -122,3 +101,51 @@ export const validateLaconicTransferConfig = (): { valid: boolean; error?: strin
}; };
} }
}; };
const getAccount = async (): Promise<Account> => {
const registryConfig = getRegistryConfig();
const account = new Account(
Buffer.from(registryConfig.privateKey, 'hex'),
);
await account.init();
return account;
}
const sendTokensToAccount = async (
receiverAddress: string,
amount: string,
): Promise<DeliverTxResponse> => {
const registryConfig = getRegistryConfig();
const registry = new Registry(
registryConfig.gqlEndpoint,
registryConfig.rpcEndpoint,
{ chainId: registryConfig.chainId },
);
const fee = parseGasAndFees(
registryConfig.fee.gas,
registryConfig.fee.fees,
);
const account = await getAccount();
const laconicClient = await registry.getLaconicClient(account);
const txResponse: DeliverTxResponse = await registryTransactionWithRetry(
() =>
laconicClient.sendTokens(
account.address,
receiverAddress,
[
{
denom: 'alnt',
amount,
},
],
fee || DEFAULT_GAS_ESTIMATION_MULTIPLIER,
),
);
return txResponse;
}

View File

@ -5,7 +5,7 @@ export const createApplicationDeploymentRequest = async (
txHash: string txHash: string
): Promise<CreateRecordResponse> => { ): Promise<CreateRecordResponse> => {
try { try {
console.log(`Creating deployment request for URL: ${url} with transaction: ${txHash} using GOR payment`); console.log(`Creating deployment request for URL: ${url} with transaction: ${txHash} using ${process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL} payment`);
// Call our serverless API endpoint to handle the registry interaction // Call our serverless API endpoint to handle the registry interaction
const response = await fetch('/api/registry', { const response = await fetch('/api/registry', {

View File

@ -116,10 +116,9 @@ export const verifyUnusedSolanaPayment = async (
if ('parsed' in instruction && instruction.programId.equals(TOKEN_PROGRAM_ID)) { if ('parsed' in instruction && instruction.programId.equals(TOKEN_PROGRAM_ID)) {
const parsed = instruction.parsed; const parsed = instruction.parsed;
if (parsed.type === 'transferChecked' || parsed.type === 'transfer') { if (parsed.type === 'transferChecked' || parsed.type === 'transfer') {
const destination = parsed.info.destination; // TODO: Check recipient address
// Verify amount
// Verify both amount and destination address if (parsed.info.amount === amount ) {
if (parsed.info.amount === amount && destination === process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS) {
foundValidTransfer = true; foundValidTransfer = true;
break; break;
} }