@@ -370,14 +370,89 @@ describe("Server#requestAirdrop", () => {
370370 expect ( mockPost ) . toHaveBeenCalledTimes ( 3 ) ;
371371 } ) ;
372372
373- it ( "returns ContractFundingResult for contract addresses (C...)" , async ( ) => {
373+ } ) ;
374+
375+ describe ( "Server#fundAddress" , ( ) => {
376+ let server : any ;
377+ let mockPost : any ;
378+
379+ // Valid XDR for transaction meta (from get_transaction.test.ts)
380+ const metaV4Xdr =
381+ "AAAAAgAAAAIAAAADAtL5awAAAAAAAAAAS0CFMhOtWUKJWerx66zxkxORaiH6/3RUq7L8zspD5RoAAAAAAcm9QAKVkpMAAHpMAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAwAAAAAC0vi5AAAAAGTB02oAAAAAAAAAAQLS+WsAAAAAAAAAAEtAhTITrVlCiVnq8eus8ZMTkWoh+v90VKuy/M7KQ+UaAAAAAAHJvUAClZKTAAB6TQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAtL5awAAAABkwdd1AAAAAAAAAAEAAAAGAAAAAwLS+VQAAAACAAAAAG4cwu71zHNXx3jHCzRGOIthcnfwRgfN2f/AoHFLLMclAAAAAEySDkgAAAAAAAAAAkJVU0lORVNTAAAAAAAAAAC3JfDeo9vreItKNPoe74EkFIqWybeUQNFvLvURhHtskAAAAAAeQtHTL5f6TAAAXH0AAAAAAAAAAAAAAAAAAAABAtL5awAAAAIAAAAAbhzC7vXMc1fHeMcLNEY4i2Fyd/BGB83Z/8CgcUssxyUAAAAATJIOSAAAAAAAAAACQlVTSU5FU1MAAAAAAAAAALcl8N6j2+t4i0o0+h7vgSQUipbJt5RA0W8u9RGEe2yQAAAAAB5C0dNHf4CAAACLCQAAAAAAAAAAAAAAAAAAAAMC0vlUAAAAAQAAAABuHMLu9cxzV8d4xws0RjiLYXJ38EYHzdn/wKBxSyzHJQAAAAJCVVNJTkVTUwAAAAAAAAAAtyXw3qPb63iLSjT6Hu+BJBSKlsm3lEDRby71EYR7bJAAAAAAAABAL3//////////AAAAAQAAAAEAE3H3TnhnuQAAAAAAAAAAAAAAAAAAAAAAAAABAtL5awAAAAEAAAAAbhzC7vXMc1fHeMcLNEY4i2Fyd/BGB83Z/8CgcUssxyUAAAACQlVTSU5FU1MAAAAAAAAAALcl8N6j2+t4i0o0+h7vgSQUipbJt5RA0W8u9RGEe2yQAAAAAAAAQC9//////////wAAAAEAAAABABNx9J6Z4RkAAAAAAAAAAAAAAAAAAAAAAAAAAwLS+WsAAAAAAAAAAG4cwu71zHNXx3jHCzRGOIthcnfwRgfN2f/AoHFLLMclAAAAH37+zXQCXdRTAAASZAAAApIAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAABbBXKIigAAABhZWyiOAAAAAgAAAAAAAAAAAAAAAAAAAAMAAAAAAtL0awAAAABkwbqrAAAAAAAAAAEC0vlrAAAAAAAAAABuHMLu9cxzV8d4xws0RjiLYXJ38EYHzdn/wKBxSyzHJQAAAB9+/s10Al3UUwAAEmQAAAKSAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAWwVyiIoAAAAYWVsojgAAAAIAAAAAAAAAAAAAAAAAAAADAAAAAALS9GsAAAAAZMG6qwAAAAAAAAAA" ;
382+
383+ const successTxResponse = ( hash : string ) => ( {
384+ data : {
385+ id : 1 ,
386+ result : {
387+ status : "SUCCESS" ,
388+ txHash : hash ,
389+ latestLedger : 100 ,
390+ latestLedgerCloseTime : 12345 ,
391+ oldestLedger : 50 ,
392+ oldestLedgerCloseTime : 500 ,
393+ ledger : 12345 ,
394+ createdAt : 123456789010 ,
395+ applicationOrder : 2 ,
396+ feeBump : false ,
397+ envelopeXdr :
398+ "AAAAAgAAAAAT/LQZdYz0FcQ4Xwyg8IM17rkUx3pPCCWLu+SowQ/T+gBLB24poiQa9iwAngAAAAEAAAAAAAAAAAAAAABkwdeeAAAAAAAAAAEAAAABAAAAAC/9E8hDhnktyufVBS5tqA734Yz5XrLX2XNgBgH/YEkiAAAADQAAAAAAAAAAAAA1/gAAAAAv/RPIQ4Z5Lcrn1QUubagO9+GM+V6y19lzYAYB/2BJIgAAAAAAAAAAAAA1/gAAAAQAAAACU0lMVkVSAAAAAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAAAVNHWAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AAAACUEFMTEFESVVNAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAAAlNJTFZFUgAAAAAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAAAAAAAACwQ/T+gAAAEA+ztVEKWlqHXNnqy6FXJeHr7TltHzZE6YZm5yZfzPIfLaqpp+5cyKotVkj3d89uZCQNsKsZI48uoyERLne+VwL/2BJIgAAAEA7323gPSaezVSa7Vi0J4PqsnklDH1oHLqNBLwi5EWo5W7ohLGObRVQZ0K0+ufnm4hcm9J4Cuj64gEtpjq5j5cM" ,
399+ resultXdr :
400+ "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAANAAAAAAAAAAUAAAACZ4W6fmN63uhVqYRcHET+D2NEtJvhCIYflFh9GqtY+AwAAAACU0lMVkVSAAAAAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAYW0toL2gAAAAAAAAAAAAANf4AAAACcgyAkXD5kObNTeRYciLh7R6ES/zzKp0n+cIK3Y6TjBkAAAABU0dYAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAGlGnIJrXAAAAAlNJTFZFUgAAAAAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAGFtLaC9oAAAAApmc7UgUBInrDvij8HMSridx2n1w3I8TVEn4sLr1LSpmAAAAAlBBTExBRElVTQAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAIUz88EqYAAAAAVNHWAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABpRpyCa1wAAAAKYUsaaCZ233xB1p+lG7YksShJWfrjsmItbokiR3ifa0gAAAAJTSUxWRVIAAAAAAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABv52PPa5wAAAAJQQUxMQURJVU0AAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AACFM/PBKmAAAAAJnhbp+Y3re6FWphFwcRP4PY0S0m+EIhh+UWH0aq1j4DAAAAAAAAAAAAAA9pAAAAAJTSUxWRVIAAAAAAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABv52PPa5wAAAAAv/RPIQ4Z5Lcrn1QUubagO9+GM+V6y19lzYAYB/2BJIgAAAAAAAAAAAAA9pAAAAAA=" ,
401+ resultMetaXdr : metaV4Xdr ,
402+ events : {
403+ contractEventsXdr : [ ] ,
404+ transactionEventsXdr : [ ] ,
405+ } ,
406+ } ,
407+ } ,
408+ } ) ;
409+
410+ beforeEach ( ( ) => {
411+ server = new Server ( serverUrl ) ;
412+ mockPost = vi . spyOn ( server . httpClient , "post" ) ;
413+ } ) ;
414+
415+ afterEach ( ( ) => {
416+ vi . clearAllMocks ( ) ;
417+ } ) ;
418+
419+ it ( "funds an account address (G...) and returns transaction response" , async ( ) => {
420+ const friendbotUrl = "https://friendbot.stellar.org" ;
421+ const accountId =
422+ "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI" ;
423+ const hash = "ae9f315c048d87a5f853bc15bf284a2c3c89eb0e1cb38c10409b77a877b830a8" ;
424+
425+ const networkResult = {
426+ friendbotUrl,
427+ passphrase : Networks . FUTURENET ,
428+ protocolVersion : 20 ,
429+ } ;
430+ const networkResponse = { data : { result : networkResult } } ;
431+
432+ const friendbotResponse = {
433+ status : 200 ,
434+ data : { hash } ,
435+ } ;
436+
437+ mockPost
438+ . mockResolvedValueOnce ( networkResponse ) // getNetwork call
439+ . mockResolvedValueOnce ( friendbotResponse ) // friendbot call
440+ . mockResolvedValueOnce ( successTxResponse ( hash ) ) ; // getTransaction call
441+
442+ const result = await server . fundAddress ( accountId ) ;
443+
444+ expect ( result . status ) . toBe ( "SUCCESS" ) ;
445+ expect ( result . txHash ) . toBe ( hash ) ;
446+ expect ( mockPost ) . toHaveBeenCalledWith ( `${ friendbotUrl } ?addr=${ accountId } ` ) ;
447+ expect ( mockPost ) . toHaveBeenCalledTimes ( 3 ) ;
448+ } ) ;
449+
450+ it ( "funds a contract address (C...) and returns transaction response" , async ( ) => {
374451 const friendbotUrl = "https://friendbot.stellar.org" ;
375- // Valid contract address (C...)
376452 const contractId = StellarSdk . StrKey . encodeContract (
377453 Buffer . from ( "0" . repeat ( 64 ) , "hex" ) ,
378454 ) ;
379- const hash =
380- "ae9f315c048d87a5f853bc15bf284a2c3c89eb0e1cb38c10409b77a877b830a8" ;
455+ const hash = "ae9f315c048d87a5f853bc15bf284a2c3c89eb0e1cb38c10409b77a877b830a8" ;
381456
382457 const networkResult = {
383458 friendbotUrl,
@@ -386,55 +461,119 @@ describe("Server#requestAirdrop", () => {
386461 } ;
387462 const networkResponse = { data : { result : networkResult } } ;
388463
389- // Mock the friendbot call - contract funding returns hash but no account creation
390464 const friendbotResponse = {
391465 status : 200 ,
392- data : {
393- hash,
394- result_meta_xdr : "someXdr" , // Doesn't matter - should not be parsed for contracts
395- } ,
466+ data : { hash } ,
396467 } ;
397468
398469 mockPost
399470 . mockResolvedValueOnce ( networkResponse ) // getNetwork call
400- . mockResolvedValueOnce ( friendbotResponse ) ; // friendbot call
471+ . mockResolvedValueOnce ( friendbotResponse ) // friendbot call
472+ . mockResolvedValueOnce ( successTxResponse ( hash ) ) ; // getTransaction call
401473
402- const result = await server . requestAirdrop ( contractId ) ;
474+ const result = await server . fundAddress ( contractId ) ;
403475
404- // Should return ContractFundingResult, not Account
405- expect ( result ) . not . toBeInstanceOf ( StellarSdk . Account ) ;
406- expect ( result ) . toHaveProperty ( "contractId" , contractId ) ;
407- expect ( result ) . toHaveProperty ( "hash" , hash ) ;
408- expect ( mockPost ) . toHaveBeenCalledWith ( serverUrl , {
409- jsonrpc : "2.0" ,
410- id : 1 ,
411- method : "getNetwork" ,
412- params : null ,
413- } ) ;
476+ expect ( result . status ) . toBe ( "SUCCESS" ) ;
477+ expect ( result . txHash ) . toBe ( hash ) ;
414478 expect ( mockPost ) . toHaveBeenCalledWith ( `${ friendbotUrl } ?addr=${ contractId } ` ) ;
415- expect ( mockPost ) . toHaveBeenCalledTimes ( 2 ) ;
479+ expect ( mockPost ) . toHaveBeenCalledTimes ( 3 ) ;
416480 } ) ;
417481
418- it ( "does not throw 'No account created' for contract addresses " , async ( ) => {
482+ it ( "uses custom friendbot URL when provided " , async ( ) => {
419483 const customFriendbotUrl = "https://custom-friendbot.example.com" ;
420- const contractId = StellarSdk . StrKey . encodeContract (
421- Buffer . from ( "1" . repeat ( 64 ) , "hex" ) ,
484+ const accountId =
485+ "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI" ;
486+ const hash = "somehash123" ;
487+
488+ const friendbotResponse = {
489+ status : 200 ,
490+ data : { hash } ,
491+ } ;
492+
493+ mockPost
494+ . mockResolvedValueOnce ( friendbotResponse ) // friendbot call
495+ . mockResolvedValueOnce ( successTxResponse ( hash ) ) ; // getTransaction call
496+
497+ const result = await server . fundAddress ( accountId , customFriendbotUrl ) ;
498+
499+ expect ( result . status ) . toBe ( "SUCCESS" ) ;
500+ // Should not call getNetwork when custom URL provided
501+ expect ( mockPost ) . not . toHaveBeenCalledWith (
502+ expect . anything ( ) ,
503+ expect . objectContaining ( { method : "getNetwork" } ) ,
504+ ) ;
505+ expect ( mockPost ) . toHaveBeenCalledWith (
506+ `${ customFriendbotUrl } ?addr=${ accountId } ` ,
422507 ) ;
508+ expect ( mockPost ) . toHaveBeenCalledTimes ( 2 ) ;
509+ } ) ;
510+
511+ it ( "throws error when no friendbot URL is configured" , async ( ) => {
512+ const accountId =
513+ "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI" ;
514+
515+ const networkResult = {
516+ friendbotUrl : undefined ,
517+ passphrase : Networks . FUTURENET ,
518+ protocolVersion : 20 ,
519+ } ;
520+ const networkResponse = { data : { result : networkResult } } ;
521+
522+ mockPost . mockResolvedValueOnce ( networkResponse ) ;
523+
524+ await expect ( server . fundAddress ( accountId ) ) . rejects . toThrow (
525+ "No friendbot URL configured" ,
526+ ) ;
527+ } ) ;
528+
529+ it ( "throws error when funding fails" , async ( ) => {
530+ const friendbotUrl = "https://friendbot.stellar.org" ;
531+ const accountId =
532+ "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI" ;
423533 const hash = "somehash123" ;
424534
535+ const networkResult = {
536+ friendbotUrl,
537+ passphrase : Networks . FUTURENET ,
538+ protocolVersion : 20 ,
539+ } ;
540+ const networkResponse = { data : { result : networkResult } } ;
541+
425542 const friendbotResponse = {
426543 status : 200 ,
427544 data : { hash } ,
428545 } ;
429546
430- mockPost . mockResolvedValueOnce ( friendbotResponse ) ;
547+ const failedTxResponse = {
548+ data : {
549+ id : 1 ,
550+ result : {
551+ status : "FAILED" ,
552+ txHash : hash ,
553+ latestLedger : 100 ,
554+ latestLedgerCloseTime : 12345 ,
555+ oldestLedger : 50 ,
556+ oldestLedgerCloseTime : 500 ,
557+ ledger : 12345 ,
558+ createdAt : 123456789010 ,
559+ applicationOrder : 2 ,
560+ feeBump : false ,
561+ envelopeXdr :
562+ "AAAAAgAAAAAT/LQZdYz0FcQ4Xwyg8IM17rkUx3pPCCWLu+SowQ/T+gBLB24poiQa9iwAngAAAAEAAAAAAAAAAAAAAABkwdeeAAAAAAAAAAEAAAABAAAAAC/9E8hDhnktyufVBS5tqA734Yz5XrLX2XNgBgH/YEkiAAAADQAAAAAAAAAAAAA1/gAAAAAv/RPIQ4Z5Lcrn1QUubagO9+GM+V6y19lzYAYB/2BJIgAAAAAAAAAAAAA1/gAAAAQAAAACU0lMVkVSAAAAAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAAAVNHWAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AAAACUEFMTEFESVVNAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAAAlNJTFZFUgAAAAAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAAAAAAAACwQ/T+gAAAEA+ztVEKWlqHXNnqy6FXJeHr7TltHzZE6YZm5yZfzPIfLaqpp+5cyKotVkj3d89uZCQNsKsZI48uoyERLne+VwL/2BJIgAAAEA7323gPSaezVSa7Vi0J4PqsnklDH1oHLqNBLwi5EWo5W7ohLGObRVQZ0K0+ufnm4hcm9J4Cuj64gEtpjq5j5cM" ,
563+ resultXdr :
564+ "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAANAAAAAAAAAAUAAAACZ4W6fmN63uhVqYRcHET+D2NEtJvhCIYflFh9GqtY+AwAAAACU0lMVkVSAAAAAAAAAAAAAFDutWuu6S6UPJBrotNSgfmXa27M++63OT7TYn1qjgy+AAAYW0toL2gAAAAAAAAAAAAANf4AAAACcgyAkXD5kObNTeRYciLh7R6ES/zzKp0n+cIK3Y6TjBkAAAABU0dYAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAGlGnIJrXAAAAAlNJTFZFUgAAAAAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAGFtLaC9oAAAAApmc7UgUBInrDvij8HMSridx2n1w3I8TVEn4sLr1LSpmAAAAAlBBTExBRElVTQAAAAAAAABQ7rVrrukulDyQa6LTUoH5l2tuzPvutzk+02J9ao4MvgAAIUz88EqYAAAAAVNHWAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABpRpyCa1wAAAAKYUsaaCZ233xB1p+lG7YksShJWfrjsmItbokiR3ifa0gAAAAJTSUxWRVIAAAAAAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABv52PPa5wAAAAJQQUxMQURJVU0AAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AACFM/PBKmAAAAAJnhbp+Y3re6FWphFwcRP4PY0S0m+EIhh+UWH0aq1j4DAAAAAAAAAAAAAA9pAAAAAJTSUxWRVIAAAAAAAAAAAAAUO61a67pLpQ8kGui01KB+Zdrbsz77rc5PtNifWqODL4AABv52PPa5wAAAAAv/RPIQ4Z5Lcrn1QUubagO9+GM+V6y19lzYAYB/2BJIgAAAAAAAAAAAAA9pAAAAAA=" ,
565+ resultMetaXdr : metaV4Xdr ,
566+ } ,
567+ } ,
568+ } ;
431569
432- // Should NOT throw "No account created in transaction"
433- const result = await server . requestAirdrop ( contractId , customFriendbotUrl ) ;
570+ mockPost
571+ . mockResolvedValueOnce ( networkResponse )
572+ . mockResolvedValueOnce ( friendbotResponse )
573+ . mockResolvedValueOnce ( failedTxResponse ) ;
434574
435- expect ( result ) . toEqual ( {
436- contractId,
437- hash,
438- } ) ;
575+ await expect ( server . fundAddress ( accountId ) ) . rejects . toThrow (
576+ `Funding address ${ accountId } failed` ,
577+ ) ;
439578 } ) ;
440579} ) ;
0 commit comments