@@ -31,7 +31,7 @@ describe('recipientUtils', function () {
3131 'defiDeposit' ,
3232 'defiWithdraw' ,
3333 'contractCall' ,
34- // Staking
34+ // Staking — 'delegate' also covers SOL solDelegateIntent
3535 'delegate' ,
3636 'undelegate' ,
3737 'switchValidator' ,
@@ -53,6 +53,8 @@ describe('recipientUtils', function () {
5353 // Avalanche / Flare cross-chain atomic imports
5454 'import' ,
5555 'importtoc' ,
56+ // SOL: deactivate stake account (solDeactivateIntent)
57+ 'deactivate' ,
5658 ] ;
5759 expected . forEach ( ( t ) => assert . ok ( NO_RECIPIENT_TX_TYPES . has ( t ) , `${ t } should be in NO_RECIPIENT_TX_TYPES` ) ) ;
5860 assert . strictEqual ( NO_RECIPIENT_TX_TYPES . size , expected . length ) ;
@@ -77,12 +79,7 @@ describe('recipientUtils', function () {
7779 const txRequest = makeTxRequest ( {
7880 intent : {
7981 intentType : 'payment' ,
80- recipients : [
81- {
82- address : { address : '0xabc' } ,
83- amount : { value : '500' , symbol : 'eth' } ,
84- } ,
85- ] ,
82+ recipients : [ { address : { address : '0xabc' } , amount : { value : '500' , symbol : 'eth' } } ] ,
8683 } as any ,
8784 } ) ;
8885 const result = resolveEffectiveTxParams ( txRequest , { } ) ;
@@ -92,9 +89,7 @@ describe('recipientUtils', function () {
9289 } ) ;
9390
9491 it ( 'resolves txType from intent.intentType when txParams.type is absent' , function ( ) {
95- const txRequest = makeTxRequest ( {
96- intent : { intentType : 'consolidate' } as any ,
97- } ) ;
92+ const txRequest = makeTxRequest ( { intent : { intentType : 'consolidate' } as any } ) ;
9893 const result = resolveEffectiveTxParams ( txRequest , { } ) ;
9994 assert . strictEqual ( result . type , 'consolidate' ) ;
10095 } ) ;
@@ -110,6 +105,7 @@ describe('recipientUtils', function () {
110105 'pledge' ,
111106 'import' ,
112107 'importtoc' ,
108+ 'deactivate' ,
113109 ] ) {
114110 const txRequest = makeTxRequest ( ) ;
115111 assert . doesNotThrow ( ( ) => resolveEffectiveTxParams ( txRequest , { type : txType } ) ) ;
@@ -182,5 +178,192 @@ describe('recipientUtils', function () {
182178 const result = resolveEffectiveTxParams ( txRequest , txParams ) ;
183179 assert . strictEqual ( result . recipients ?. [ 0 ] . address , '0xcaller' ) ;
184180 } ) ;
181+
182+ it ( 'preserves data field from intent recipients' , function ( ) {
183+ const txRequest = makeTxRequest ( {
184+ intent : {
185+ intentType : 'payment' ,
186+ recipients : [ { address : { address : '0xabc' } , amount : { value : '100' , symbol : 'eth' } , data : '0xdeadbeef' } ] ,
187+ } as any ,
188+ } ) ;
189+ const result = resolveEffectiveTxParams ( txRequest , { } ) ;
190+ assert . strictEqual ( result . recipients ?. [ 0 ] . data , '0xdeadbeef' ) ;
191+ } ) ;
192+
193+ describe ( 'tokenName preservation regression tests' , function ( ) {
194+ it ( 'preserves tokenName from amount.symbol when it differs from chainName' , function ( ) {
195+ const txRequest = makeTxRequest ( {
196+ intent : {
197+ intentType : 'payment' ,
198+ recipients : [
199+ {
200+ address : { address : 'UserWalletAddress111111111111111111111111111' } ,
201+ amount : { value : '1000000' , symbol : 'tsol:usdc' } ,
202+ } ,
203+ ] ,
204+ } as any ,
205+ } ) ;
206+ const result = resolveEffectiveTxParams ( txRequest , { } , 'tsol' ) ;
207+ assert . strictEqual ( result . recipients ?. length , 1 ) ;
208+ assert . strictEqual ( result . recipients ?. [ 0 ] . tokenName , 'tsol:usdc' ) ;
209+ } ) ;
210+
211+ it ( 'does NOT set tokenName when symbol equals chainName (native SOL transfer)' , function ( ) {
212+ const txRequest = makeTxRequest ( {
213+ intent : {
214+ intentType : 'payment' ,
215+ recipients : [
216+ {
217+ address : { address : 'RecipientAddress111111111111111111111111111' } ,
218+ amount : { value : '5000000000' , symbol : 'tsol' } ,
219+ } ,
220+ ] ,
221+ } as any ,
222+ } ) ;
223+ const result = resolveEffectiveTxParams ( txRequest , { } , 'tsol' ) ;
224+ assert . strictEqual ( result . recipients ?. [ 0 ] . tokenName , undefined ) ;
225+ } ) ;
226+
227+ it ( 'prefers tokenData.tokenName over amount.symbol (uses distinct values to verify)' , function ( ) {
228+ const txRequest = makeTxRequest ( {
229+ intent : {
230+ intentType : 'payment' ,
231+ recipients : [
232+ {
233+ address : { address : 'RecipientAddress111111111111111111111111111' } ,
234+ amount : { value : '500000' , symbol : 'tsol:usdc-alt' } ,
235+ tokenData : { tokenType : 'fungible' , tokenQuantity : '500000' , tokenName : 'canonical-token-name' } ,
236+ } ,
237+ ] ,
238+ } as any ,
239+ } ) ;
240+ const result = resolveEffectiveTxParams ( txRequest , { } , 'tsol' ) ;
241+ assert . strictEqual ( result . recipients ?. [ 0 ] . tokenName , 'canonical-token-name' ) ;
242+ } ) ;
243+
244+ it ( 'falls back to amount.symbol when tokenData.tokenName is absent' , function ( ) {
245+ const txRequest = makeTxRequest ( {
246+ intent : {
247+ intentType : 'payment' ,
248+ recipients : [
249+ {
250+ address : { address : 'RecipientAddress111111111111111111111111111' } ,
251+ amount : { value : '200000' , symbol : 'sol:usdc' } ,
252+ tokenData : { tokenType : 'fungible' , tokenQuantity : '200000' } ,
253+ } ,
254+ ] ,
255+ } as any ,
256+ } ) ;
257+ const result = resolveEffectiveTxParams ( txRequest , { } , 'sol' ) ;
258+ assert . strictEqual ( result . recipients ?. [ 0 ] . tokenName , 'sol:usdc' ) ;
259+ } ) ;
260+
261+ it ( 'falls back to amount.symbol when tokenData.tokenName is empty string' , function ( ) {
262+ const txRequest = makeTxRequest ( {
263+ intent : {
264+ intentType : 'payment' ,
265+ recipients : [
266+ {
267+ address : { address : 'RecipientAddress111111111111111111111111111' } ,
268+ amount : { value : '100000' , symbol : 'tsol:usdc' } ,
269+ tokenData : { tokenType : 'fungible' , tokenQuantity : '100000' , tokenName : '' } ,
270+ } ,
271+ ] ,
272+ } as any ,
273+ } ) ;
274+ const result = resolveEffectiveTxParams ( txRequest , { } , 'tsol' ) ;
275+ assert . strictEqual ( result . recipients ?. [ 0 ] . tokenName , 'tsol:usdc' ) ;
276+ } ) ;
277+
278+ it ( 'does NOT set tokenName when chainName is absent (legacy ECDSA callers, no tokenData)' , function ( ) {
279+ const txRequest = makeTxRequest ( {
280+ intent : {
281+ intentType : 'payment' ,
282+ recipients : [ { address : { address : '0xabc' } , amount : { value : '100' , symbol : 'eth' } } ] ,
283+ } as any ,
284+ } ) ;
285+ const result = resolveEffectiveTxParams ( txRequest , { } ) ;
286+ assert . strictEqual ( result . recipients ?. [ 0 ] . tokenName , undefined ) ;
287+ assert . strictEqual ( result . recipients ?. [ 0 ] . address , '0xabc' ) ;
288+ } ) ;
289+
290+ it ( 'preserves tokenData.tokenName when chainName is absent (legacy ECDSA with tokenData)' , function ( ) {
291+ const txRequest = makeTxRequest ( {
292+ intent : {
293+ intentType : 'transferToken' ,
294+ recipients : [
295+ {
296+ address : { address : '0xabc' } ,
297+ amount : { value : '1000' , symbol : 'erc20:usdc' } ,
298+ tokenData : { tokenType : 'fungible' , tokenQuantity : '1000' , tokenName : 'eth:usdc' } ,
299+ } ,
300+ ] ,
301+ } as any ,
302+ } ) ;
303+ const result = resolveEffectiveTxParams ( txRequest , { } ) ;
304+ assert . strictEqual ( result . recipients ?. [ 0 ] . tokenName , 'eth:usdc' ) ;
305+ } ) ;
306+
307+ it ( 'sendMany tsol:usdc: does not throw and preserves tokenName in full round-trip' , function ( ) {
308+ const txRequest = makeTxRequest ( {
309+ intent : {
310+ intentType : 'payment' ,
311+ recipients : [
312+ {
313+ address : { address : 'SolUserWallet1111111111111111111111111111111' } ,
314+ amount : { value : '2000000' , symbol : 'tsol:usdc' } ,
315+ } ,
316+ {
317+ address : { address : 'SolUserWallet2222222222222222222222222222222' } ,
318+ amount : { value : '3000000' , symbol : 'tsol:usdc' } ,
319+ } ,
320+ ] ,
321+ } as any ,
322+ } ) ;
323+ const result = resolveEffectiveTxParams ( txRequest , { } , 'tsol' ) ;
324+ assert . strictEqual ( result . recipients ?. length , 2 ) ;
325+ result . recipients ! . forEach ( ( r ) => assert . strictEqual ( r . tokenName , 'tsol:usdc' ) ) ;
326+ assert . strictEqual ( result . recipients ! [ 0 ] . amount , '2000000' ) ;
327+ assert . strictEqual ( result . recipients ! [ 1 ] . amount , '3000000' ) ;
328+ } ) ;
329+
330+ it ( 'handles mixed native + token recipients correctly' , function ( ) {
331+ const txRequest = makeTxRequest ( {
332+ intent : {
333+ intentType : 'payment' ,
334+ recipients : [
335+ {
336+ address : { address : 'SolNative111111111111111111111111111111111' } ,
337+ amount : { value : '1000000000' , symbol : 'tsol' } ,
338+ } ,
339+ {
340+ address : { address : 'SolToken111111111111111111111111111111111' } ,
341+ amount : { value : '500000' , symbol : 'tsol:usdc' } ,
342+ } ,
343+ ] ,
344+ } as any ,
345+ } ) ;
346+ const result = resolveEffectiveTxParams ( txRequest , { } , 'tsol' ) ;
347+ assert . strictEqual ( result . recipients ! [ 0 ] . tokenName , undefined ) ;
348+ assert . strictEqual ( result . recipients ! [ 1 ] . tokenName , 'tsol:usdc' ) ;
349+ } ) ;
350+ } ) ;
351+
352+ describe ( 'SOL no-recipient intent types' , function ( ) {
353+ it ( 'does not throw for "delegate" (solDelegateIntent)' , function ( ) {
354+ const txRequest = makeTxRequest ( { intent : { intentType : 'delegate' } as any } ) ;
355+ assert . doesNotThrow ( ( ) => resolveEffectiveTxParams ( txRequest , { } ) ) ;
356+ } ) ;
357+
358+ it ( 'does not throw for "deactivate" (solDeactivateIntent)' , function ( ) {
359+ const txRequest = makeTxRequest ( { intent : { intentType : 'deactivate' } as any } ) ;
360+ assert . doesNotThrow ( ( ) => resolveEffectiveTxParams ( txRequest , { } ) ) ;
361+ } ) ;
362+
363+ it ( 'throws for stakingAuthorize — must be validated at coin layer' , function ( ) {
364+ const txRequest = makeTxRequest ( { intent : { intentType : 'stakingAuthorize' } as any } ) ;
365+ assert . throws ( ( ) => resolveEffectiveTxParams ( txRequest , { } ) , InvalidTransactionError ) ;
366+ } ) ;
367+ } ) ;
185368 } ) ;
186369} ) ;
0 commit comments