1- import { Injectable } from '@nestjs/common' ;
1+ import { Injectable , Logger } from '@nestjs/common' ;
2+ import { ConfigService } from '@nestjs/config' ;
3+ import * as StellarSdk from '@stellar/stellar-sdk' ;
24
35export interface RaffleData {
46 raffleId : number ;
@@ -8,15 +10,74 @@ export interface RaffleData {
810
911@Injectable ( )
1012export class ContractService {
13+ private readonly logger = new Logger ( ContractService . name ) ;
14+ private readonly rpcServer : StellarSdk . rpc . Server ;
15+ private readonly contractId : string ;
16+ private readonly networkPassphrase : string ;
17+
18+ constructor ( private readonly configService : ConfigService ) {
19+ const rpcUrl = this . configService . get < string > ( 'SOROBAN_RPC_URL' , 'https://soroban-testnet.stellar.org' ) ;
20+ this . networkPassphrase = this . configService . get < string > ( 'NETWORK_PASSPHRASE' , StellarSdk . Networks . TESTNET ) ;
21+ this . contractId = this . configService . get < string > ( 'RAFFLE_CONTRACT_ID' , '' ) ;
22+
23+ this . rpcServer = new StellarSdk . rpc . Server ( rpcUrl ) ;
24+
25+ if ( ! this . contractId ) {
26+ this . logger . warn ( 'RAFFLE_CONTRACT_ID is not set. ContractService calls will fail.' ) ;
27+ }
28+ }
29+
1130 /**
1231 * Fetches raffle data from the Soroban contract
1332 * @param raffleId The raffle ID
1433 * @returns Raffle data including prize amount
1534 */
1635 async getRaffleData ( raffleId : number ) : Promise < RaffleData > {
17- // TODO: Implement Soroban RPC call to get_raffle_data
18- // This will use stellar-sdk to simulate the read-only call
19- throw new Error ( 'Contract RPC not yet implemented' ) ;
36+ if ( ! this . contractId ) {
37+ throw new Error ( 'RAFFLE_CONTRACT_ID is not configured' ) ;
38+ }
39+
40+ try {
41+ this . logger . debug ( `Fetching raffle data for ID: ${ raffleId } ` ) ;
42+
43+ // Create a dummy source account for simulation
44+ const sourceAccount = new StellarSdk . Account ( 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF' , '0' ) ;
45+ const contract = new StellarSdk . Contract ( this . contractId ) ;
46+
47+ const tx = new StellarSdk . TransactionBuilder ( sourceAccount , {
48+ fee : '100' ,
49+ networkPassphrase : this . networkPassphrase ,
50+ } )
51+ . addOperation ( contract . call ( 'get_raffle_data' , StellarSdk . xdr . ScVal . scvU32 ( raffleId >>> 0 ) ) )
52+ . setTimeout ( 30 )
53+ . build ( ) ;
54+
55+ const simulated = await this . rpcServer . simulateTransaction ( tx ) ;
56+
57+ if ( StellarSdk . rpc . Api . isSimulationError ( simulated ) ) {
58+ throw new Error ( `Simulation failed: ${ JSON . stringify ( simulated . error ) } ` ) ;
59+ }
60+
61+ if ( ! simulated . result ) {
62+ throw new Error ( 'Simulation returned no result' ) ;
63+ }
64+
65+ const resultValue = simulated . result . retval ;
66+ const data = this . decodeScVal ( resultValue ) ;
67+
68+ if ( ! data || typeof data !== 'object' ) {
69+ throw new Error ( 'Failed to decode raffle data from contract response' ) ;
70+ }
71+
72+ return {
73+ raffleId : Number ( data . raffle_id ?? raffleId ) ,
74+ prizeAmount : Number ( data . prize_amount ?? 0 ) ,
75+ status : String ( data . status ?? 'UNKNOWN' ) ,
76+ } ;
77+ } catch ( error : any ) {
78+ this . logger . error ( `Failed to fetch raffle data for ${ raffleId } : ${ error . message } ` ) ;
79+ throw error ;
80+ }
2081 }
2182
2283 /**
@@ -25,15 +86,62 @@ export class ContractService {
2586 * @returns True if already finalized
2687 */
2788 async isRandomnessSubmitted ( raffleId : number ) : Promise < boolean > {
28- const data = await this . getRaffleData ( raffleId ) ;
29- return data . status === 'FINALIZED' || data . status === 'CANCELLED' ;
89+ try {
90+ const data = await this . getRaffleData ( raffleId ) ;
91+ return data . status === 'FINALIZED' || data . status === 'CANCELLED' ;
92+ } catch ( error ) {
93+ // If we can't fetch data, we assume it's safer to retry or log error
94+ this . logger . error ( `Could not determine if randomness is submitted for raffle ${ raffleId } : ${ error . message } ` ) ;
95+ return false ;
96+ }
3097 }
3198
3299 /**
33100 * Pings the Soroban contract to inform the ecosystem that the oracle is alive
34101 */
35102 async ping ( ) : Promise < void > {
36- // TODO: Implement Soroban RPC call to ping
37- // throw new Error('Contract RPC not yet implemented');
103+ if ( ! this . contractId ) return ;
104+
105+ try {
106+ this . logger . log ( 'Sending oracle heartbeat (ping) to contract...' ) ;
107+ // Note: Ping is usually a write operation, so it requires a real account and signing.
108+ // For this MVP, we might just use it to check RPC health.
109+ await this . rpcServer . getLatestLedger ( ) ;
110+ } catch ( error : any ) {
111+ this . logger . error ( `Oracle ping failed: ${ error . message } ` ) ;
112+ }
113+ }
114+
115+ /**
116+ * Helper to decode ScVal into JS types
117+ */
118+ private decodeScVal ( val : StellarSdk . xdr . ScVal ) : any {
119+ switch ( val . switch ( ) ) {
120+ case StellarSdk . xdr . ScValType . scvU32 ( ) : return val . u32 ( ) ;
121+ case StellarSdk . xdr . ScValType . scvU64 ( ) : return val . u64 ( ) . toString ( ) ;
122+ case StellarSdk . xdr . ScValType . scvI32 ( ) : return val . i32 ( ) ;
123+ case StellarSdk . xdr . ScValType . scvI64 ( ) : return val . i64 ( ) . toString ( ) ;
124+ case StellarSdk . xdr . ScValType . scvSymbol ( ) : return val . sym ( ) . toString ( ) ;
125+ case StellarSdk . xdr . ScValType . scvString ( ) : return val . str ( ) . toString ( ) ;
126+ case StellarSdk . xdr . ScValType . scvBytes ( ) : return val . bytes ( ) . toString ( 'hex' ) ;
127+ case StellarSdk . xdr . ScValType . scvAddress ( ) :
128+ const addr = val . address ( ) ;
129+ if ( addr . switch ( ) === StellarSdk . xdr . ScAddressType . scAddressTypeAccount ( ) ) {
130+ return StellarSdk . Address . account ( Buffer . from ( addr . accountId ( ) . ed25519 ( ) as any ) ) . toString ( ) ;
131+ } else {
132+ return StellarSdk . Address . contract ( Buffer . from ( addr . contractId ( ) as any ) ) . toString ( ) ;
133+ }
134+ case StellarSdk . xdr . ScValType . scvBool ( ) : return val . b ( ) ;
135+ case StellarSdk . xdr . ScValType . scvMap ( ) :
136+ const result : Record < string , any > = { } ;
137+ for ( const entry of val . map ( ) ?? [ ] ) {
138+ const key = entry . key ( ) . sym ( ) . toString ( ) ;
139+ result [ key ] = this . decodeScVal ( entry . val ( ) ) ;
140+ }
141+ return result ;
142+ case StellarSdk . xdr . ScValType . scvVec ( ) :
143+ return ( val . vec ( ) ?? [ ] ) . map ( v => this . decodeScVal ( v ) ) ;
144+ default : return null ;
145+ }
38146 }
39147}
0 commit comments