11import { Server , Networks , Account , TransactionBuilder , Operation , BASE_FEE , StrKey } from '@stellar/stellar-sdk'
2+ import { createValidationError , parseContractError , ContractErrorCode } from '@/lib/errors/contract-errors'
23
34const HORIZON_URL = process . env . HORIZON_URL || 'https://horizon-testnet.stellar.org'
45const NETWORK_PASSPHRASE = process . env . NETWORK_PASSPHRASE || Networks . TESTNET
@@ -13,36 +14,80 @@ function validatePublicKey(pk: string) {
1314}
1415
1516async function loadAccount ( accountId : string ) {
16- if ( ! validatePublicKey ( accountId ) ) throw new Error ( 'invalid-account' )
17- return await server . loadAccount ( accountId )
17+ if ( ! validatePublicKey ( accountId ) ) {
18+ throw createValidationError (
19+ ContractErrorCode . INVALID_ACCOUNT ,
20+ 'Invalid Stellar account address' ,
21+ { contractId : 'bill-payments' , metadata : { accountId } }
22+ )
23+ }
24+ try {
25+ return await server . loadAccount ( accountId )
26+ } catch ( error ) {
27+ throw parseContractError ( error , {
28+ contractId : 'bill-payments' ,
29+ method : 'loadAccount'
30+ } )
31+ }
1832}
1933
2034export async function buildCreateBillTx ( owner : string , name : string , amount : number , dueDate : string , recurring : boolean , frequencyDays ?: number ) {
21- if ( ! validatePublicKey ( owner ) ) throw new Error ( 'invalid-owner' )
22- if ( ! ( amount > 0 ) ) throw new Error ( 'invalid-amount' )
23- if ( recurring && ! ( frequencyDays && frequencyDays > 0 ) ) throw new Error ( 'invalid-frequency' )
35+ if ( ! validatePublicKey ( owner ) ) {
36+ throw createValidationError (
37+ ContractErrorCode . INVALID_ADDRESS ,
38+ 'Invalid owner address' ,
39+ { contractId : 'bill-payments' , method : 'buildCreateBillTx' , metadata : { owner } }
40+ )
41+ }
42+ if ( ! ( amount > 0 ) ) {
43+ throw createValidationError (
44+ ContractErrorCode . INVALID_AMOUNT ,
45+ 'Amount must be greater than zero' ,
46+ { contractId : 'bill-payments' , method : 'buildCreateBillTx' , metadata : { amount } }
47+ )
48+ }
49+ if ( recurring && ! ( frequencyDays && frequencyDays > 0 ) ) {
50+ throw createValidationError (
51+ ContractErrorCode . INVALID_FREQUENCY ,
52+ 'Frequency days must be greater than zero for recurring bills' ,
53+ { contractId : 'bill-payments' , method : 'buildCreateBillTx' , metadata : { frequencyDays } }
54+ )
55+ }
2456 // basic date validation
25- if ( Number . isNaN ( Date . parse ( dueDate ) ) ) throw new Error ( 'invalid-dueDate' )
26-
27- const acctResp = await loadAccount ( owner )
28- const source = new Account ( owner , acctResp . sequence )
29-
30- const txBuilder = new TransactionBuilder ( source , {
31- fee : BASE_FEE ,
32- networkPassphrase : NETWORK_PASSPHRASE ,
33- } )
34-
35- // Use several ManageData ops to keep values under the 64-byte limit per entry
36- txBuilder . addOperation ( Operation . manageData ( { name : 'bill:name' , value : name . slice ( 0 , 64 ) } ) )
37- txBuilder . addOperation ( Operation . manageData ( { name : 'bill:amount' , value : String ( amount ) } ) )
38- txBuilder . addOperation ( Operation . manageData ( { name : 'bill:dueDate' , value : new Date ( dueDate ) . toISOString ( ) } ) )
39- txBuilder . addOperation ( Operation . manageData ( { name : 'bill:recurring' , value : recurring ? '1' : '0' } ) )
40- if ( recurring && frequencyDays ) {
41- txBuilder . addOperation ( Operation . manageData ( { name : 'bill:frequencyDays' , value : String ( frequencyDays ) } ) )
57+ if ( Number . isNaN ( Date . parse ( dueDate ) ) ) {
58+ throw createValidationError (
59+ ContractErrorCode . INVALID_DUE_DATE ,
60+ 'Invalid due date format' ,
61+ { contractId : 'bill-payments' , method : 'buildCreateBillTx' , metadata : { dueDate } }
62+ )
4263 }
4364
44- const tx = txBuilder . setTimeout ( 300 ) . build ( )
45- return tx . toXDR ( )
65+ try {
66+ const acctResp = await loadAccount ( owner )
67+ const source = new Account ( owner , acctResp . sequence )
68+
69+ const txBuilder = new TransactionBuilder ( source , {
70+ fee : BASE_FEE ,
71+ networkPassphrase : NETWORK_PASSPHRASE ,
72+ } )
73+
74+ // Use several ManageData ops to keep values under the 64-byte limit per entry
75+ txBuilder . addOperation ( Operation . manageData ( { name : 'bill:name' , value : name . slice ( 0 , 64 ) } ) )
76+ txBuilder . addOperation ( Operation . manageData ( { name : 'bill:amount' , value : String ( amount ) } ) )
77+ txBuilder . addOperation ( Operation . manageData ( { name : 'bill:dueDate' , value : new Date ( dueDate ) . toISOString ( ) } ) )
78+ txBuilder . addOperation ( Operation . manageData ( { name : 'bill:recurring' , value : recurring ? '1' : '0' } ) )
79+ if ( recurring && frequencyDays ) {
80+ txBuilder . addOperation ( Operation . manageData ( { name : 'bill:frequencyDays' , value : String ( frequencyDays ) } ) )
81+ }
82+
83+ const tx = txBuilder . setTimeout ( 300 ) . build ( )
84+ return tx . toXDR ( )
85+ } catch ( error ) {
86+ throw parseContractError ( error , {
87+ contractId : 'bill-payments' ,
88+ method : 'buildCreateBillTx'
89+ } )
90+ }
4691}
4792
4893export async function buildPayBillTx ( caller : string , billId : string ) {
0 commit comments